import { beforeEach, describe, expect, it, vi } from 'vitest'; import { completePlatformAuthorization, resetPlatformAuthorizationForTests, startPlatformAuthorization, } from '@electron/services/platform-auth'; describe('platform OIDC authorization', () => { beforeEach(() => { vi.restoreAllMocks(); resetPlatformAuthorizationForTests(); vi.stubEnv('PLATFORM_AUTH_ISSUER', 'https://square.example.test'); vi.stubEnv('PLATFORM_AUTH_CLIENT_ID', 'makelore'); vi.stubGlobal('fetch', vi.fn()); }); it('uses discovery, state, nonce, and S256 PKCE for the public desktop client', async () => { const fetchMock = vi.mocked(fetch); fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ authorization_endpoint: 'https://square.example.test/api/auth/oauth/authorize', token_endpoint: 'https://square.example.test/api/auth/oauth/token', }), { status: 200 })); const started = await startPlatformAuthorization(); const authorizationUrl = new URL(started.authorizationUrl); expect(authorizationUrl.searchParams.get('client_id')).toBe('makelore'); expect(authorizationUrl.searchParams.get('redirect_uri')).toBe('niancode://auth/callback'); expect(authorizationUrl.searchParams.get('response_type')).toBe('code'); expect(authorizationUrl.searchParams.get('code_challenge_method')).toBe('S256'); expect(authorizationUrl.searchParams.get('code_challenge')).toMatch(/^[A-Za-z0-9_-]{43}$/); expect(authorizationUrl.searchParams.get('state')).toMatch(/^[A-Za-z0-9_-]{43}$/); expect(authorizationUrl.searchParams.get('nonce')).toMatch(/^[A-Za-z0-9_-]{43}$/); expect(authorizationUrl.searchParams.has('request_id')).toBe(false); fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ access_token: 'access-token', refresh_token: 'refresh-token', token_type: 'Bearer', expires_in: 3600, }), { status: 200 })); const payload = await completePlatformAuthorization( null, 'authorization-code', authorizationUrl.searchParams.get('state')!, ); expect(payload).toMatchObject({ access_token: 'access-token', refresh_token: 'refresh-token' }); const [, init] = fetchMock.mock.calls[1] as [string, RequestInit]; expect(init.method).toBe('POST'); const body = new URLSearchParams(String(init.body)); expect(body.get('grant_type')).toBe('authorization_code'); expect(body.get('code')).toBe('authorization-code'); expect(body.get('client_id')).toBe('makelore'); expect(body.get('redirect_uri')).toBe('niancode://auth/callback'); expect(body.get('code_verifier')).toMatch(/^[A-Za-z0-9_-]{86}$/); expect(fetchMock).toHaveBeenCalledTimes(2); }); it('consumes a callback state once', async () => { const fetchMock = vi.mocked(fetch); fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ authorization_endpoint: 'https://square.example.test/api/auth/oauth/authorize', token_endpoint: 'https://square.example.test/api/auth/oauth/token', }), { status: 200 })); const started = await startPlatformAuthorization(); const state = new URL(started.authorizationUrl).searchParams.get('state')!; fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ access_token: 'access-token' }), { status: 200 })); await completePlatformAuthorization(null, 'authorization-code', state); await expect(completePlatformAuthorization(null, 'authorization-code', state)) .rejects.toThrow('OIDC authorization request expired'); }); });