mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(auth): read a remembered browser's expiry on the throttle's clock
knownSignInSource compared a trusted device's expiry with new Date() while the throttle around it times its waits with Date.now(). The test mocks Date.now() only, so it set the device to expire 30 days after its fixed clock, which the real date passed on 2026-10-26 at noon UTC; from then on the remembered browser read as expired and the test failed on every run. One clock decides both now, and the test's expiry is fixed against it. A further case pins that an expired device gets no exemption.
This commit is contained in:
1 parent
460a4fa8f9
commit
b180d09fae
2 files changed
+23
-10
No files matched your search
@@ -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", () => {
|
describe("somebody guessing from two addresses", () => {
|
||||||
async function tenGuesses() {
|
async function tenGuesses() {
|
||||||
const answers: string[] = [];
|
const answers: string[] = [];
|
||||||
@@ -159,18 +168,20 @@ describe("somebody guessing from two addresses", () => {
|
|||||||
|
|
||||||
it("the owner's remembered browser still gets through", async () => {
|
it("the owner's remembered browser still gets through", async () => {
|
||||||
await tenGuesses();
|
await tenGuesses();
|
||||||
deviceCookie = "device-token";
|
// The device's expiry is read against the same clock as the wait, the
|
||||||
// The device's expiry is compared against the wall clock, not the mocked
|
// mocked `now`. It used to be compared with `new Date()`, which the mock
|
||||||
// rate-limit clock `now`, so it is anchored to the wall clock too: an
|
// does not reach, so this expiry ran out for real on 2026-10-26 at noon
|
||||||
// expiry fixed relative to `now` expired for real once the calendar
|
// UTC, 30 days after `now`, and the test failed from then on.
|
||||||
// passed it.
|
rememberBrowser(new Date(now + 30 * 86_400_000));
|
||||||
vi.mocked(prisma.trustedDevice.findUnique).mockResolvedValue({
|
|
||||||
userId: "owner-1",
|
|
||||||
expiresAt: new Date(Date.now() + 30 * 86_400_000),
|
|
||||||
} as never);
|
|
||||||
expect(await guess("203.0.113.51", { right: true })).toBe("checked");
|
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 () => {
|
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
|
// 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
|
// network, or names the address in a forwarding header; either way the
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ export async function knownSignInSource(
|
|||||||
userId: string,
|
userId: string,
|
||||||
request: Request,
|
request: Request,
|
||||||
): Promise<KnownSource | null> {
|
): 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;
|
let deviceToken: string | undefined;
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in new issue
Block a user