Merge pull request #1294 from MBombeck/fix/throttle-test-clock

fix(auth): read a remembered browser's expiry on the throttle's clock
This commit is contained in:
Marc-André Bombeck authored and GitHub committed 2026-10-26 15:36:37 +01:00
commit 35a0ceb12c
2 files changed
+23 -10

No files matched your search

+20 -9
View File
@@ -125,6 +125,15 @@ describe("waitAfter", () => {
});
});
/** The owner's browser, remembered as a trusted device until `expiresAt`. */
function rememberBrowser(expiresAt: Date) {
deviceCookie = "device-token";
vi.mocked(prisma.trustedDevice.findUnique).mockResolvedValue({
userId: "owner-1",
expiresAt,
} as never);
}
describe("somebody guessing from two addresses", () => {
async function tenGuesses() {
const answers: string[] = [];
@@ -159,18 +168,20 @@ describe("somebody guessing from two addresses", () => {
it("the owner's remembered browser still gets through", async () => {
await tenGuesses();
deviceCookie = "device-token";
// The device's expiry is compared against the wall clock, not the mocked
// rate-limit clock `now`, so it is anchored to the wall clock too: an
// expiry fixed relative to `now` expired for real once the calendar
// passed it.
vi.mocked(prisma.trustedDevice.findUnique).mockResolvedValue({
userId: "owner-1",
expiresAt: new Date(Date.now() + 30 * 86_400_000),
} as never);
// The device's expiry is read against the same clock as the wait, the
// mocked `now`. It used to be compared with `new Date()`, which the mock
// does not reach, so this expiry ran out for real on 2026-10-26 at noon
// UTC, 30 days after `now`, and the test failed from then on.
rememberBrowser(new Date(now + 30 * 86_400_000));
expect(await guess("203.0.113.51", { right: true })).toBe("checked");
});
it("a remembered browser whose device has expired is throttled like anyone", async () => {
await tenGuesses();
rememberBrowser(new Date(now - 1));
expect(await guess("203.0.113.52", { right: true })).toBe("waiting");
});
it("an address the account signed in from is no exemption: a guesser can claim it", async () => {
// A session of the owner's from this address. The guesser shares the
// network, or names the address in a forwarding header; either way the
+3 -1
View File
@@ -67,7 +67,9 @@ export async function knownSignInSource(
userId: string,
request: Request,
): Promise<KnownSource | null> {
const now = new Date();
// The same clock as the throttle's own wait (`Date.now()`), so one instant
// decides both.
const now = new Date(Date.now());
let deviceToken: string | undefined;
try {