The site's move (moved.ts) against real SQL: what the old host answers, and a session carried to the new one exactly once.
3import { beforeEach, describe, expect, it } from 'vitest'; 4import { testD1 } from '../test/d1'; 5import { d1Accounts } from './accounts-store'; 6import { arrived, HANDOFF_TTL_MS, isOldHost, moved, MOVED_PATH, SITE_HOST } from './moved'; 7import worker from './index'; 8import { SESSION_COOKIE, SESSION_TTL_MS } from './session';
@ts-expect-error a plain module of the website's, with no types
10import { siteUrl } from '../../website/src/jev/site.mjs';
12const OLD = 'https://jevstrudel.deizel.workers.dev'; 13const NEW = `https://${SITE_HOST}`; 14const USER = { id: 'u'.repeat(22), displayName: 'Link' }; 15const SESSION = 's'.repeat(43); 16 17let clock: number; 18let db: D1Database; 19const env = () => ({ DB: db }) as never; 20const accounts = () => d1Accounts(db, () => clock); 21const passkey = (id: string, rpId: string) => ({ id: id.repeat(16), publicKey: Uint8Array.of(1), signCount: 0, transports: [], rpId }); 22 23const navigate = (url: string, cookie?: string) => 24 new Request(url, { headers: { 'Sec-Fetch-Mode': 'navigate', ...(cookie ? { Cookie: `${SESSION_COOKIE}=${cookie}` } : {}) } }); 25 26beforeEach(async () => { 27 clock = Date.UTC(2026, 9, 4, 12); 28 db = testD1(); 29 await accounts().createUser(USER, passkey('a', 'jevstrudel.deizel.workers.dev'), SESSION, SESSION_TTL_MS); 30}); 31 32describe('the old address', () => { 33 it('is the workers.dev host and its previews, never the site or localhost', () => { 34 // one address: the Worker's custom domain (wrangler.json) is where the 35 // website says it is (link previews, /ai/, the deploy's release check) 36 expect(siteUrl).toBe(`https://${SITE_HOST}`); 37 expect(isOldHost('jevstrudel.deizel.workers.dev')).toBe(true); 38 expect(isOldHost('abc123-jevstrudel.deizel.workers.dev')).toBe(true); 39 expect(isOldHost(SITE_HOST)).toBe(false); 40 expect(isOldHost('localhost')).toBe(false); 41 }); 42 43 it('redirects a page for good, to the same path and query', async () => { 44 const res = await moved(navigate(`${OLD}/songs/jev/cosmic-knot/?performance=abc`), env(), accounts()); 45 expect(res.status).toBe(301); 46 expect(res.headers.get('Location')).toBe(`${NEW}/songs/jev/cosmic-knot/?performance=abc`); 47 }); 48 49 it('redirects a write with its method kept, and answers nothing itself', async () => { 50 const res = await worker.fetch(new Request(`${OLD}/jev/v1/systemone`, { method: 'POST', body: '{}' }), env(), {} as never); 51 expect(res.status).toBe(308); 52 expect(res.headers.get('Location')).toBe(`${NEW}/jev/v1/systemone`); 53 const me = await worker.fetch(new Request(`${OLD}/jev/auth/me`), env(), {} as never); 54 expect(me.status).toBe(301); 55 }); 56 57 it('serves a service worker that removes itself, never a redirect', async () => { 58 const res = await moved(new Request(`${OLD}/sw.js`), env(), accounts()); 59 expect(res.status).toBe(200); 60 expect(res.headers.get('Content-Type')).toMatch(/javascript/); 61 expect(res.headers.get('Cache-Control')).toBe('no-store'); 62 const script = await res.text(); 63 expect(script).toMatch(/registration\.unregister\(\)/); 64 expect(script).toMatch(/caches\.delete/); 65 }); 66}); 67 68describe('a session carried across', () => { 69 const handoff = async (path = '/songs/jev/all-green/?x=1') => { 70 const res = await moved(navigate(OLD + path, SESSION), env(), accounts()); 71 expect(res.status).toBe(302); 72 return new URL(res.headers.get('Location')!); 73 }; 74 const arrive = (url: URL | string) => arrived(new Request(url), env(), accounts()); 75 const cookieOf = (res: Response) => /^jev_session=([^;]+)/.exec(res.headers.get('Set-Cookie') ?? '')?.[1] ?? null; 76 77 it('goes through the site with a one-time token, and lands signed in on the page asked for', async () => { 78 const to = await handoff(); 79 expect(to.origin + to.pathname).toBe(NEW + MOVED_PATH); 80 expect(to.searchParams.get('token')).toMatch(/^[A-Za-z0-9_-]{43}$/); 81 const res = await arrive(to); 82 expect(res.status).toBe(302); 83 expect(res.headers.get('Location')).toBe('/songs/jev/all-green/?x=1'); 84 const session = cookieOf(res)!; 85 expect(await accounts().sessionUser(session)).toEqual(USER); 86 // the old address's session ended with it, so its next visit is a plain redirect 87 expect(await accounts().sessionUser(SESSION)).toBeNull(); 88 expect((await moved(navigate(`${OLD}/`, SESSION), env(), accounts())).status).toBe(301); 89 }); 90 91 it('signs nobody in twice, late, or with a token never issued', async () => { 92 const to = await handoff(); 93 expect(cookieOf(await arrive(to))).not.toBeNull(); 94 expect(cookieOf(await arrive(to))).toBeNull(); 95 // the old address's session ended with the first; another browser's 96 await accounts().createSession(USER.id, SESSION, SESSION_TTL_MS); 97 const late = await handoff('/'); 98 clock += HANDOFF_TTL_MS; 99 expect(cookieOf(await arrive(late))).toBeNull(); 100 expect(cookieOf(await arrive(`${NEW}${MOVED_PATH}?token=${'x'.repeat(43)}`))).toBeNull(); 101 expect(cookieOf(await arrive(`${NEW}${MOVED_PATH}`))).toBeNull(); 102 }); 103 104 it('stores only the token hash', async () => { 105 const token = (await handoff()).searchParams.get('token')!; 106 const rows = await db.prepare('SELECT token_hash, from_session_hash FROM session_handoffs').all<Record<string, string>>(); 107 expect(rows.results).toHaveLength(1); 108 expect(JSON.stringify(rows.results)).not.toContain(token); 109 expect(JSON.stringify(rows.results)).not.toContain(SESSION); 110 }); 111 112 it('is only for a navigation with a live session', async () => { 113 const fetched = await moved(new Request(`${OLD}/release-notes.json`, { headers: { Cookie: `${SESSION_COOKIE}=${SESSION}` } }), env(), accounts()); 114 expect(fetched.status).toBe(301); 115 expect((await moved(navigate(`${OLD}/`, 'n'.repeat(43)), env(), accounts())).status).toBe(301); 116 }); 117 118 it('never lands on another site', async () => { 119 for (const to of ['//evil.example/', '/\\evil.example', 'https://evil.example/', 'evil']) { 120 const res = await arrive(`${NEW}${MOVED_PATH}?to=${encodeURIComponent(to)}`); 121 expect(res.headers.get('Location'), to).toBe('/'); 122 } 123 }); 124}); 125 126describe('a passkey belongs to the host it was made on', () => { 127 it('lists an account’s passkeys per host', async () => { 128 await accounts().addCredential({ ...passkey('b', SITE_HOST), userId: USER.id }); 129 expect((await accounts().credentialIds(USER.id, 'jevstrudel.deizel.workers.dev')).map((c) => c.id)).toEqual(['a'.repeat(16)]); 130 expect((await accounts().credentialIds(USER.id, SITE_HOST)).map((c) => c.id)).toEqual(['b'.repeat(16)]); 131 }); 132});