Something went wrong. Try again.
Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144import { afterEach, describe, expect, test } from 'bun:test'import type { NodeOAuthClient, OAuthSession } from '@atproto/oauth-client-node'import type { Cookie } from 'elysia'import { authenticateRequest, clearSessionCache, invalidateSessionCache, requireAuth, SESSION_COOKIE_NAME,} from './wisp-auth'
const DID = 'did:plc:aaaaaaaaaaaaaaaaaaaaaaaa'
const cookies = (did?: string): Record<string, Cookie<unknown>> => (did ? { [SESSION_COOKIE_NAME]: { value: did } } : {}) as unknown as Record<string, Cookie<unknown>>
interface StubClient { client: NodeOAuthClient restores: string[] settle: (sub: string) => void}
/** * A client whose `restore` only resolves when the test releases it, so a burst * of callers can be observed while the first restore is still in flight. */const stubClient = (options: { fail?: boolean; session?: OAuthSession | null } = {}): StubClient => { const restores: string[] = [] const waiting = new Map<string, () => void>()
const client = { restore: (sub: string) => { restores.push(sub) return new Promise((resolve, reject) => { waiting.set(sub, () => { if (options.fail) reject(new Error('restore failed')) else resolve(options.session === undefined ? ({ did: sub, sub } as unknown as OAuthSession) : options.session) }) }) }, } as unknown as NodeOAuthClient
return { client, restores, settle: (sub) => { waiting.get(sub)?.() waiting.delete(sub) }, }}
afterEach(() => { clearSessionCache()})
describe('authenticateRequest', () => { test('returns null without a session cookie and never restores', async () => { const stub = stubClient() expect(await authenticateRequest(stub.client, cookies())).toBeNull() expect(stub.restores).toEqual([]) })
test('rejects a request carrying the session cookie twice', async () => { const stub = stubClient() const header = `${SESSION_COOKIE_NAME}=${DID}; ${SESSION_COOKIE_NAME}=other`
expect(await authenticateRequest(stub.client, cookies(DID), header)).toBeNull() expect(stub.restores).toEqual([]) })
test('collapses a burst of concurrent requests into one restore', async () => { const stub = stubClient() const pending = Array.from({ length: 8 }, () => authenticateRequest(stub.client, cookies(DID)))
// All eight are now waiting on the same in-flight restore. expect(stub.restores).toEqual([DID])
stub.settle(DID) const results = await Promise.all(pending)
expect(stub.restores).toEqual([DID]) for (const result of results) expect(result?.did).toBe(DID) })
test('answers later requests from the cache', async () => { const stub = stubClient() const first = authenticateRequest(stub.client, cookies(DID)) stub.settle(DID) await first
expect(await authenticateRequest(stub.client, cookies(DID))).not.toBeNull() expect(stub.restores).toEqual([DID]) })
test('invalidating forces the next request to restore again', async () => { const stub = stubClient() const first = authenticateRequest(stub.client, cookies(DID)) stub.settle(DID) await first
invalidateSessionCache(DID) const second = authenticateRequest(stub.client, cookies(DID)) stub.settle(DID) await second
expect(stub.restores).toEqual([DID, DID]) })
test('does not cache a failed restore', async () => { const stub = stubClient({ fail: true }) const first = authenticateRequest(stub.client, cookies(DID)) stub.settle(DID) expect(await first).toBeNull()
const second = authenticateRequest(stub.client, cookies(DID)) stub.settle(DID) expect(await second).toBeNull()
// A user signing in right now must not be held out by a cached failure. expect(stub.restores).toEqual([DID, DID]) })
test('does not cache a missing session', async () => { const stub = stubClient({ session: null }) const first = authenticateRequest(stub.client, cookies(DID)) stub.settle(DID) expect(await first).toBeNull()
const second = authenticateRequest(stub.client, cookies(DID)) stub.settle(DID) expect(await second).toBeNull()
expect(stub.restores).toEqual([DID, DID]) })})
describe('requireAuth', () => { test('throws when there is no session', async () => { const stub = stubClient() await expect(requireAuth(stub.client, cookies())).rejects.toThrow('Authentication required') })})