mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
Make every login refusal cost the same verification
Sign-in returned the moment it found no account, or an account carrying no password hash, before it reached the verifier. An account that did have a password paid a full Argon2id verification at 19 MiB and t=2. The three refusals answered with the identical 401 body and three very different durations, so the clock told an anonymous caller whether an address is registered and whether it carries a password. `verifyPasswordOrDummy` verifies against a stand-in hash minted once per process at the same cost parameters real hashes use, and returns false whenever there is nothing to verify, so the passkey-only account takes the same path as one with a password. The dummy arm discards its own verdict and swallows a throw, so no outcome there can be read as a success or answer with a different status.
This commit is contained in:
1 parent
6ef139c7b2
commit
6a5fb338b1
8 files changed
+332
-17
No files matched your search
@@ -12,7 +12,7 @@ vi.mock("@/lib/db", () => ({
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/auth/password", () => ({
|
||||
verifyPassword: vi.fn().mockResolvedValue(true),
|
||||
verifyPasswordOrDummy: vi.fn().mockResolvedValue(true),
|
||||
}));
|
||||
vi.mock("@/lib/auth/audit", () => ({
|
||||
auditLog: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -142,8 +142,8 @@ describe("login MFA branch", () => {
|
||||
});
|
||||
|
||||
it("wrong password never reaches the MFA branch", async () => {
|
||||
const { verifyPassword } = await import("@/lib/auth/password");
|
||||
vi.mocked(verifyPassword).mockResolvedValueOnce(false);
|
||||
const { verifyPasswordOrDummy } = await import("@/lib/auth/password");
|
||||
vi.mocked(verifyPasswordOrDummy).mockResolvedValueOnce(false);
|
||||
vi.mocked(prisma.user.findFirst).mockResolvedValue({
|
||||
...BASE_USER,
|
||||
totpConfirmedAt: new Date(),
|
||||
|
||||
Reference in new issue
Block a user