From a9ff1cf94b1de13a9cb317dd7295b0d38aa27808 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Mar 2026 14:32:38 -0700 Subject: [PATCH] feat(encryptid): complete social recovery end-to-end flow Add /recover/social page for users to finalize account recovery after guardian approvals, fix status filter so approved requests remain findable, return requestId from initiation API with tracking link on login page, and add actionUrl to recovery notifications. Co-Authored-By: Claude Opus 4.6 --- src/encryptid/db.ts | 2 +- src/encryptid/server.ts | 225 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 223 insertions(+), 4 deletions(-) diff --git a/src/encryptid/db.ts b/src/encryptid/db.ts index 0d8daa5..a940f28 100644 --- a/src/encryptid/db.ts +++ b/src/encryptid/db.ts @@ -540,7 +540,7 @@ export async function getRecoveryRequest(requestId: string): Promise { const rows = await sql` SELECT * FROM recovery_requests - WHERE user_id = ${userId} AND status = 'pending' AND expires_at > NOW() + WHERE user_id = ${userId} AND status IN ('pending', 'approved') AND expires_at > NOW() ORDER BY initiated_at DESC LIMIT 1 `; if (rows.length === 0) return null; diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index 7719fc0..a608363 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -2257,6 +2257,220 @@ app.get('/recover', (c) => { `); }); +// ============================================================================ +// SOCIAL RECOVERY COMPLETION PAGE +// ============================================================================ + +/** + * GET /recover/social — page where user finalizes social recovery + * After guardians approve, user opens this to register a new passkey + */ +app.get('/recover/social', (c) => { + return c.html(` + + + + + + Social Recovery — rStack Identity + + + +
+
👥
+

Social Recovery

+

Your guardians are helping you recover your account

+ +
Checking recovery status...
+ + + + + + + +
+ + + + + `); +}); + // ============================================================================ // GUARDIAN MANAGEMENT ROUTES // ============================================================================ @@ -2757,7 +2971,7 @@ app.post('/api/recovery/social/initiate', async (c) => { // Check for existing active request const existing = await getActiveRecoveryRequest(user.id); if (existing) { - return c.json({ success: true, message: 'A recovery request is already active. Recovery emails have been re-sent.' }); + return c.json({ success: true, requestId: existing.id, message: 'A recovery request is already active. Recovery emails have been re-sent.' }); } // Create recovery request (7 day expiry, 2-of-3 threshold) @@ -2773,6 +2987,7 @@ app.post('/api/recovery/social/initiate', async (c) => { title: 'Account recovery initiated', body: `A recovery request was initiated for your account. ${accepted.length} guardians have been contacted.`, metadata: { recoveryRequestId: requestId, threshold: 2, totalGuardians: accepted.length }, + actionUrl: `/recover/social?id=${requestId}`, }).catch(() => {}); // Create approval tokens and notify guardians @@ -2854,7 +3069,7 @@ app.post('/api/recovery/social/initiate', async (c) => { } } - return c.json({ success: true, message: 'If the account exists and has guardians, recovery emails have been sent.' }); + return c.json({ success: true, requestId, message: 'If the account exists and has guardians, recovery emails have been sent.' }); }); /** @@ -2973,6 +3188,7 @@ app.post('/api/recovery/social/approve', async (c) => { title: 'Account recovery approved', body: `${request.approvalCount}/${request.threshold} guardians approved. You can now recover your account.`, metadata: { recoveryRequestId: request.id }, + actionUrl: `https://auth.rspace.online/recover/social?id=${request.id}`, }).catch(() => {}); } @@ -7394,7 +7610,10 @@ app.get('/', (c) => { body: JSON.stringify(body), }); const data = await res.json(); - msgEl.textContent = data.message || 'Recovery request sent. Check with your guardians.'; + msgEl.innerHTML = data.message || 'Recovery request sent. Check with your guardians.'; + if (data.requestId) { + msgEl.innerHTML += '

Track your recovery progress →'; + } msgEl.style.color = '#86efac'; msgEl.style.display = 'block'; // Also try email recovery