This change makes the client-side expiration in sync with the server-side expiration. Previously, if the client-side window is opened later than the server's start time, the expirations would be de-synced. It is also a maintenance burden to keep the duration constants in sync between the two.
<p>Create or select a passkey, or <button class="link">click here</button> if no prompt appeared. This window will expire in 2 minutes.</p>
<script type="module">
- // Extract and delete challenge from URL
- const { challenge } = Object.fromEntries(new URLSearchParams(location.search));
+ const { challenge, exp } = Object.fromEntries(new URLSearchParams(location.search));
history.replaceState(null, '', location.pathname);
const p = document.querySelector('p');
- const WINDOW_EXPIRATION_DURATION = 2 * 60 * 1000; // 2 minutes in milliseconds
- const timeoutId = setTimeout(() => p.textContent = 'This window has expired.', WINDOW_EXPIRATION_DURATION);
+ const expiration = Number(exp) - Date.now();
+ const timeoutId = setTimeout(() => p.textContent = 'This window has expired.', expiration);
async function createPrf(signal) {
signal.throwIfAborted();
// Flow expires after 2 minutes or canceled
const timeoutId = setTimeout(() => server.close(() => abortResolve(null)), WINDOW_EXPIRATION_DURATION);
+ const timeoutExp = Date.now() + WINDOW_EXPIRATION_DURATION;
if (signal !== undefined) {
signal.addEventListener('abort', () => {
clearTimeout(timeoutId);
server.listen(port, 'localhost');
server.on('listening', () => {
const { port } = /** @type {AddressInfo} */(server.address());
- const url = `http://localhost:${port}?challenge=${challenge}`;
+ const url = `http://localhost:${port}?challenge=${challenge}&exp=${timeoutExp}`;
urlResolve(url);
if (autoOpen) { BROWSER.open(url); }
});
await page.clock.runFor(WINDOW_EXPIRATION_DURATION);
match(await page.locator('p').textContent() ?? '', /^This window has expired/);
});
+ test('window expiration is calculated from a set date, not from hardcoded duration', async (t) => {
+ const { setAutomaticSignIn, page } = await setupPlaywright(t);
+ const { url } = await setupServer(t);
+
+ await setAutomaticSignIn(false);
+
+ await page.clock.install();
+ await page.clock.runFor(WINDOW_EXPIRATION_DURATION / 2);
+ await page.goto(url);
+
+ match(await page.locator('p').textContent() ?? '', /^Create or select a passkey/);
+ await page.clock.runFor(WINDOW_EXPIRATION_DURATION / 2);
+ match(await page.locator('p').textContent() ?? '', /^This window has expired/);
+ });
test('create passkey on page load works', async (t) => {
const { page } = await setupPlaywright(t);
const { url, prf } = await setupServer(t);