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