74 lines
3.5 KiB
TypeScript
74 lines
3.5 KiB
TypeScript
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');
|
|
});
|
|
});
|