chore: update src/pages/api/user/settings/2fa.ts
swadhinbiswas committed 8 months ago
54451ac
1 parent 861ca9f
commit 54451ac
1 file changed
+20 -13
Showing 1 changed file+20-13
▼src/pages/api/user/settings/2fa.ts
+20-13
| @@ -32,13 +32,13 @@ export const GET: APIRoute = async ({ request }) => { | ||
32 | 32 | // Generate QR Code |
33 | 33 | const qrCodeUrl = await QRCode.toDataURL(uri); |
34 | 34 | |
35 | - // We don't save the secret yet, the client must verify it first | |
36 | - // But we need to verify it in the POST request. | |
37 | - // Stateless approach: Send secret to client, client sends it back with code? | |
38 | - // Security risk? Kind of. | |
39 | - // Better: Store in DB temporarily? Or just return it and let client send it back signed? | |
40 | - // Simple approach for now: Return secret to client, client sends it back in POST to "enable". | |
41 | - // The secret is sensitive but it's being sent to the authenticated user. | |
35 | + await (db as NodePgDatabase<typeof schema>) | |
36 | + .update(users) | |
37 | + .set({ | |
38 | + twoFactorEnabled: false, | |
39 | + twoFactorSecret: secret, | |
40 | + }) | |
41 | + .where(eq(users.id, user.userId)); | |
42 | 42 | |
43 | 43 | return success({ |
44 | 44 | enabled: false, |
| @@ -63,17 +63,26 @@ export const POST: APIRoute = async ({ request }) => { | ||
63 | 63 | const db = getDatabase() as NodePgDatabase<typeof schema>; |
64 | 64 | |
65 | 65 | if (action === "enable") { |
66 | - if (!token || !secret) return badRequest("Token and secret are required"); | |
66 | + if (!token) return badRequest("Authentication code is required"); | |
67 | 67 | |
68 | - // Verify the token against the provided secret | |
69 | - const isValid = verify2FAToken(token, secret); | |
68 | + const currentUser = await db.query.users.findFirst({ | |
69 | + where: eq(users.id, user.userId) | |
70 | + }); | |
71 | + if (!currentUser) return unauthorized(); | |
72 | + | |
73 | + const effectiveSecret = currentUser.twoFactorSecret || secret; | |
74 | + if (!effectiveSecret) return badRequest("No pending 2FA setup found"); | |
75 | + | |
76 | + // Verify the token against server-side pending secret. | |
77 | + // `secret` from request is accepted only as a compatibility fallback. | |
78 | + const isValid = verify2FAToken(token, effectiveSecret); | |
70 | 79 | if (!isValid) return badRequest("Invalid authentication code"); |
71 | 80 | |
72 | 81 | // Enable 2FA |
73 | 82 | await db.update(users) |
74 | 83 | .set({ |
75 | 84 | twoFactorEnabled: true, |
76 | - twoFactorSecret: secret | |
85 | + twoFactorSecret: effectiveSecret | |
77 | 86 | }) |
78 | 87 | .where(eq(users.id, user.userId)); |
79 | 88 | |
| @@ -82,8 +91,6 @@ export const POST: APIRoute = async ({ request }) => { | ||
82 | 91 | } |
83 | 92 | |
84 | 93 | if (action === "disable") { |
85 | - // To disable, we should require a valid token (or password) for security | |
86 | - // For now, requiring the current 2FA token | |
87 | 94 | if (!token) return badRequest("Authentication code required to disable 2FA"); |
88 | 95 | |
89 | 96 | const currentUser = await db.query.users.findFirst({ |
90 | 97 | |