slice 2a COMPLETE: TOTP 2FA (enable2fa/confirm2fa/login-2fa + protectedProcedure)
totp.ts + backup-codes.ts produced by ornith (remote model, reviewed+approved); router/context/schema integration by orchestrator (delicate edit-existing — ornith timed out on a dispatch of that size in-session). 58/58 backend tests green. 2FA flow: enable2fa -> confirm2fa -> login now requires a valid TOTP.
This commit is contained in:
@@ -100,3 +100,30 @@ test('market.snapshot serves cached values (not stale) after drain populates cac
|
||||
assert.equal(snap.stale.candles, false);
|
||||
assert.equal(snap.stale.sector, false);
|
||||
});
|
||||
|
||||
// --- Slice 2: 2FA flow ---
|
||||
import { totp as computeTotp } from '../../auth/totp.ts';
|
||||
|
||||
test('2FA: enable2fa -> confirm2fa -> login requires totp', async () => {
|
||||
const { freshCtx } = setup();
|
||||
// signup
|
||||
const signupCtx = freshCtx();
|
||||
const { userId } = await appRouter.createCaller(signupCtx).auth.signup({ email: 'a@b.co', password: 'password123' });
|
||||
// enable2fa (protected: set ctx.userId)
|
||||
const eCtx = freshCtx(); eCtx.userId = userId;
|
||||
const en = await appRouter.createCaller(eCtx).auth.enable2fa({});
|
||||
assert.ok(en.totpSecret);
|
||||
assert.ok(en.qrUrl.startsWith('otpauth://TOTP/'));
|
||||
assert.equal(en.backupCodes.length, 10);
|
||||
// confirm2fa with a valid code
|
||||
const code = computeTotp(en.totpSecret);
|
||||
const cCtx = freshCtx(); cCtx.userId = userId;
|
||||
const c = await appRouter.createCaller(cCtx).auth.confirm2fa({ totp: code });
|
||||
assert.equal(c.ok, true);
|
||||
// login WITHOUT totp now fails
|
||||
await assert.rejects(() => appRouter.createCaller(freshCtx()).auth.login({ email: 'a@b.co', password: 'password123' }), (e: { code: string }) => e.code === 'UNAUTHORIZED');
|
||||
// login WITH valid totp succeeds
|
||||
const code2 = computeTotp(en.totpSecret);
|
||||
const res = await appRouter.createCaller(freshCtx()).auth.login({ email: 'a@b.co', password: 'password123', totp: code2 });
|
||||
assert.equal(res.userId, userId);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user