Something went wrong. Try again.
Browser extension: detect and subscribe to standard.site publications on ATProto
Something went wrong. Try again.
2.8 kB · 74 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475// The one thing about the OAuth client that can be tested without a browser:// that both halves of the flow name the same redirect URI, and that it is this// build's own id rather than whichever id the client metadata happens to list// first. Getting that wrong fails only at the token exchange, only for the ids// that are not first, and only in a real sign-in — so it is worth pinning here.import { beforeEach, describe, expect, it, vi } from 'vitest'
interface RedirectOption { redirect_uri?: string}
const authorize = vi.fn( async (_input: string, _options?: RedirectOption) => new URL('https://pds.example.com/authorize?x=1'),)const callback = vi.fn(async (_params: URLSearchParams, _options?: RedirectOption) => ({ session: { sub: 'did:plc:abc123' },}))
vi.mock('@atproto/oauth-client-browser', () => ({ BrowserOAuthClient: class { authorize = authorize callback = callback },}))
// buildSessionInfo resolves the handle and avatar; both are cosmetic and this// test is about the arguments, not the profile.vi.mock('./atproto', () => ({ resolveDid: vi.fn(async () => { throw new Error('no network in tests') }), profileAvatarUrl: vi.fn(),}))
const EXTENSION_ID = 'degljbilkggdpbobomfbgnellecgbkjj'const REDIRECT = `https://${EXTENSION_ID}.chromiumapp.org/oauth2`
vi.stubGlobal('chrome', { runtime: { id: EXTENSION_ID } })
const { completeAuthorization, startAuthorization } = await import('./oauth')
// The store id, listed first in oauth/client-metadata.json — what the client// falls back to when a call does not say otherwise.const metadata = (await import('../../oauth/client-metadata.json')).default
beforeEach(() => { authorize.mockClear() callback.mockClear()})
describe('the interactive sign-in flow', () => { it('authorizes with the redirect uri of the id this build runs under', async () => { await startAuthorization('reader.example.com') expect(authorize).toHaveBeenCalledWith('reader.example.com', { redirect_uri: REDIRECT }) })
it('exchanges the code with the same redirect uri it authorized with', async () => { await completeAuthorization(`${REDIRECT}?code=abc&state=xyz`) const [params, options] = callback.mock.calls[0] ?? [] expect(params?.get('code')).toBe('abc') expect(options).toEqual({ redirect_uri: REDIRECT }) })
it('does not leave the exchange to fall back to the first listed id', async () => { // The regression: with no options the client sends redirect_uris[0], so an // unpacked build authorizes as itself and exchanges as the store. expect(metadata.redirect_uris[0]).not.toBe(REDIRECT) await completeAuthorization(`${REDIRECT}?code=abc`) const sent = callback.mock.calls[0]?.[1]?.redirect_uri expect(sent).toBe(REDIRECT) expect(sent).not.toBe(metadata.redirect_uris[0]) })})