feat(auth): TOTP setup endpoint, recovery codes, passkeys and master key rotation
- GET /auth/totp/setup returns the pending secret and otpauth URL (created on first call, reused on refresh) for the console to render as a QR code. - Enabling TOTP issues 8 one-time recovery codes (argon2id hashes in admin_recovery_code); login accepts recoveryCode in place of totpCode, disable accepts either, and recovery_codes regenerates with the password. - Passkeys via @simplewebauthn/server: register/list/rename/delete under the console session, public login options/verify that reuse the login rate limit and session cookies. Challenges are one-time rows in webauthn_challenge; RP ID and origins come from WEBAUTHN_RP_ID / WEBAUTHN_ORIGINS, falling back to the request host. Signature counter regressions are rejected. SessionInfo gains passkeyEnabled. - --role rotate-master-key re-encrypts the secret table from SECRET_MASTER_KEY to SECRET_MASTER_KEY_NEW one row per transaction, skips rows already on the new key (resumable, swap keys to roll back), logs progress and audits secret.master_key_rotate. Secrets now record the master key fingerprint in kdf_params.kid. - Runbook: recovery codes, passkey configuration, rotation steps and rollback. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5c6f483420
commit
b0e5d539ed
21 files changed
+1616
-80
No files matched your search
@@ -186,7 +186,11 @@ describe.skipIf(!hasPg)("管理员登录门禁(真实 PG)", () => {
|
||||
it("TOTP:绑定 → 启用 → 登录需第二步(不计失败)→ 验证码防重放 → 停用", async () => {
|
||||
const a = new Browser(h.app);
|
||||
await login(a);
|
||||
expect((await json(await a.req("GET", "/auth/totp"))).data).toEqual({ enabled: false, pending: false });
|
||||
expect((await json(await a.req("GET", "/auth/totp"))).data).toEqual({
|
||||
enabled: false,
|
||||
pending: false,
|
||||
recoveryCodesRemaining: 0,
|
||||
});
|
||||
const begin = (await json(await a.req("POST", "/auth/totp", { action: "begin" }))).data;
|
||||
expect(begin.otpauthUri).toMatch(/^otpauth:\/\/totp\//);
|
||||
const secret: string = begin.secret;
|
||||
|
||||
Reference in new issue
Block a user