import { env, createExecutionContext, fetchMock } from 'cloudflare:test'; import { describe, it, expect, beforeAll } from 'vitest'; import { generateKeyPair, SignJWT, exportJWK } from 'jose'; import worker from '../src/index'; const IncomingRequest = Request; let privateKey: CryptoKey; let jwksResponse: object; beforeAll(async () => { const { publicKey, privateKey: pk } = await generateKeyPair('RS256'); privateKey = pk; const publicJwk = await exportJWK(publicKey); publicJwk.alg = 'RS256'; publicJwk.use = 'sig'; publicJwk.kid = 'test-key'; jwksResponse = { keys: [publicJwk] }; fetchMock.activate(); fetchMock.disableNetConnect(); fetchMock .get('https://id.guardianproject.info') .intercept({ path: '/realms/gp/protocol/openid-connect/certs' }) .reply(200, JSON.stringify(jwksResponse), { headers: { 'content-type': 'application/json' }, }) .persist(); }); async function makeJWT(overrides: Record = {}): Promise { const now = Math.floor(Date.now() / 1000); const defaults = { iss: env.KEYCLOAK_ISSUER, aud: env.KEYCLOAK_AUDIENCE, sub: 'test-user', groups: [env.REQUIRED_GROUP], iat: now, exp: now + 3600, }; const claims = { ...defaults, ...overrides }; let builder = new SignJWT(claims) .setProtectedHeader({ alg: 'RS256', kid: 'test-key' }); if (claims.iss !== undefined) builder = builder.setIssuer(claims.iss as string); if (claims.aud !== undefined) builder = builder.setAudience(claims.aud as string); if (claims.exp !== undefined) builder = builder.setExpirationTime(claims.exp as number); return builder.sign(privateKey); } describe('nix-cache auth', () => { it('OPTIONS without auth returns 200', async () => { const request = new IncomingRequest('http://example.com/', { method: 'OPTIONS' }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(200); }); it('GET with no Authorization header returns 401', async () => { const request = new IncomingRequest('http://example.com/nix-cache-info'); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(401); expect(response.headers.get('WWW-Authenticate')).toBe('Bearer realm="nix-cache"'); }); it('GET with unrecognized auth scheme returns 401', async () => { const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: 'Token xyz' }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(401); }); it('GET with Bearer + valid JWT passes auth', async () => { const token = await makeJWT(); const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).not.toBe(401); }); it('GET with Basic auth :token format passes auth', async () => { const token = await makeJWT(); const basic = btoa(':' + token); const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: `Basic ${basic}` }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).not.toBe(401); }); it('GET with Basic auth user:token format passes auth', async () => { const token = await makeJWT(); const basic = btoa('user:' + token); const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: `Basic ${basic}` }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).not.toBe(401); }); it('GET with invalid Bearer token returns 401', async () => { const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: 'Bearer garbage-token' }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(401); }); it('GET with expired JWT returns 401', async () => { const token = await makeJWT({ exp: Math.floor(Date.now() / 1000) - 3600 }); const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(401); }); it('GET with JWT missing groups claim returns 401', async () => { const token = await makeJWT({ groups: undefined }); const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(401); }); it('GET with JWT where groups does not contain required group returns 401', async () => { const token = await makeJWT({ groups: ['some-other-group'] }); const request = new IncomingRequest('http://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(401); }); });