import { IdentityManagementService, MANAGED_OAUTH_PROVIDERS, } from '../src/features/auth/identity-linking' jest.mock('../src/lib/supabase', () => ({ supabase: { auth: {} } })) jest.mock('../src/lib/auth-redirect', () => ({ AUTH_REDIRECT_URL: 'd3ro-voice://auth-callback' })) const USER_ID = '11111111-1111-4111-8111-111111111111' function identity(provider: string, identityId = `${provider}-identity`) { return { id: USER_ID, user_id: USER_ID, identity_id: identityId, provider, } } function createAuth(overrides: Record = {}) { const getUser = jest.fn(async () => ({ data: { user: { id: USER_ID, app_metadata: { provider: 'email' } } }, error: null, })) const getUserIdentities = jest.fn(async () => ({ data: { identities: [identity('email'), identity('google')] }, error: null, })) const linkIdentity = jest.fn(async () => ({ data: { provider: 'github', url: 'https://provider.example/authorize' }, error: null, })) const unlinkIdentity = jest.fn(async () => ({ data: {}, error: null })) return { getUser, getUserIdentities, linkIdentity, unlinkIdentity, ...overrides, } } describe('manual identity linking safety boundary', () => { it('only exposes the three mobile capability providers', () => { expect(MANAGED_OAUTH_PROVIDERS).toEqual(['google', 'github', 'apple']) }) it('uses the canonical PKCE callback and blocks duplicate provider linking', async () => { const auth = createAuth() const service = new IdentityManagementService(auth as never) await expect(service.createLinkUrl('github', USER_ID)).resolves.toEqual({ ok: true, value: { url: 'https://provider.example/authorize' }, }) expect(auth.linkIdentity).toHaveBeenCalledWith({ provider: 'github', options: { redirectTo: 'd3ro-voice://auth-callback', skipBrowserRedirect: true }, }) await expect(service.createLinkUrl('google', USER_ID)).resolves.toEqual({ ok: false, code: 'already_linked', }) expect(auth.linkIdentity).toHaveBeenCalledTimes(1) }) it('fails closed if the account changes during the provider URL request', async () => { const getUser = jest .fn() .mockResolvedValueOnce({ data: { user: { id: USER_ID, app_metadata: { provider: 'email' } } }, error: null }) .mockResolvedValueOnce({ data: { user: { id: USER_ID, app_metadata: { provider: 'email' } } }, error: null }) .mockResolvedValueOnce({ data: { user: { id: 'other-account', app_metadata: { provider: 'email' } } }, error: null }) const auth = createAuth({ getUser }) const service = new IdentityManagementService(auth as never) await expect(service.createLinkUrl('github', USER_ID)).resolves.toEqual({ ok: false, code: 'account_changed', }) }) it('never unlinks email, the last identity, or the provider used for the current login', async () => { const auth = createAuth() const service = new IdentityManagementService(auth as never) await expect(service.unlink('email-identity', USER_ID)).resolves.toEqual({ ok: false, code: 'email_identity_protected', }) expect(auth.unlinkIdentity).not.toHaveBeenCalled() auth.getUserIdentities.mockResolvedValueOnce({ data: { identities: [identity('google')] }, error: null, }) await expect(service.unlink('google-identity', USER_ID)).resolves.toEqual({ ok: false, code: 'last_identity_protected', }) auth.getUser.mockImplementation(async () => ({ data: { user: { id: USER_ID, app_metadata: { provider: 'google' } } }, error: null, })) auth.getUserIdentities.mockResolvedValueOnce({ data: { identities: [identity('email'), identity('google')] }, error: null, }) await expect(service.unlink('google-identity', USER_ID)).resolves.toEqual({ ok: false, code: 'last_login_protected', }) expect(auth.unlinkIdentity).not.toHaveBeenCalled() }) it('maps session failures to a sanitized reauthentication requirement', async () => { const auth = createAuth({ getUser: jest.fn(async () => ({ data: { user: null }, error: Object.assign(new Error('JWT expired with provider internals'), { code: 'jwt_expired' }), })), }) const service = new IdentityManagementService(auth as never) await expect(service.list(USER_ID)).resolves.toEqual({ ok: false, code: 'reauthentication_required', }) }) })