import { buildPushNotification, isServiceRoleAuthorization, isServiceRoleApiKey, parsePushRequest, providerIsSupported, PushContractError, readFcmConfig, sendFcmMessage, } from './push-contract.ts' function assert(condition: boolean, message: string): asserts condition { if (!condition) throw new Error(message) } function assertPushError(action: () => unknown, code: string, status: number): void { let actual: unknown try { action() } catch (error) { actual = error } assert(actual instanceof PushContractError, `expected PushContractError for ${code}`) assert(actual.code === code, `expected ${code}, received ${actual.code}`) assert(actual.status === status, `expected status ${status}, received ${actual.status}`) } const resourceId = '11111111-2222-4333-8444-555555555555' const fcmConfig = { clientEmail: 'firebase@example.iam.gserviceaccount.com', privateKey: 'unused-in-injected-test', projectId: 'd3ro-test-project', tokenUri: 'https://oauth2.googleapis.com/token', } Deno.test('push providers fcm, webpush, and apns are supported', () => { assert(providerIsSupported('fcm'), 'fcm must be supported') assert(providerIsSupported('webpush'), 'webpush must be supported') assert(providerIsSupported('apns'), 'apns must be supported') }) Deno.test('push input accepts only event_type and resource_id', () => { const parsed = parsePushRequest({ event_type: 'transcription.completed', resource_id: resourceId, }) assert(parsed.eventType === 'transcription.completed', 'event must be preserved') assert(parsed.resourceId === resourceId, 'resource must be preserved') assertPushError( () => parsePushRequest({ event_type: 'transcription.completed', resource_id: resourceId, title: 'attacker supplied', }), 'invalid_push_request', 400, ) assertPushError( () => parsePushRequest({ event_type: 'meeting.comment.created', resource_id: resourceId, }), 'unsupported_push_event', 400, ) }) Deno.test('notification templates expose only the mobile route allowlist', () => { const history = buildPushNotification('transcription.completed', resourceId, 'ko') assert(history.data.route === 'HistoryDetail', 'history route required') assert(history.data.history_id === resourceId, 'history id must be server-derived') assert(Object.keys(history.data).sort().join(',') === 'event_type,history_id,resource_id,route,schema_version', 'history data must be exact') const subscription = buildPushNotification('billing.status.changed', resourceId, 'en') assert(subscription.data.route === 'ProPaywall', 'billing route required') assert(subscription.data.subscription_id === resourceId, 'subscription id must be server-derived') const invite = buildPushNotification('team.invite.created', resourceId, 'en', { inviteToken: 'abcdefghijklmnopqrstuvwx_123456', teamId: 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee', }) assert(invite.data.route === 'InviteAccept', 'invite route required') assert(invite.data.team_id === 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee', 'team id required') assert(invite.data.invite_token === 'abcdefghijklmnopqrstuvwx_123456', 'server token required') assert(!('title' in invite.data) && !('body' in invite.data), 'arbitrary copy cannot enter data') }) Deno.test('FCM configuration fails closed when credentials are absent or inconsistent', () => { assertPushError(() => readFcmConfig(() => undefined), 'fcm_not_configured', 503) const credentials = JSON.stringify({ client_email: 'firebase@example.iam.gserviceaccount.com', private_key: 'key', project_id: 'first-project', }) assertPushError( () => readFcmConfig((name) => name === 'FCM_SERVICE_ACCOUNT_JSON' ? credentials : 'second-project'), 'fcm_credentials_invalid', 503, ) const redirectedTokenEndpoint = JSON.stringify({ client_email: 'firebase@example.iam.gserviceaccount.com', private_key: 'key', project_id: 'first-project', token_uri: 'https://attacker.example/token', }) assertPushError( () => readFcmConfig((name) => name === 'FCM_SERVICE_ACCOUNT_JSON' ? redirectedTokenEndpoint : undefined), 'fcm_credentials_invalid', 503, ) }) Deno.test('FCM HTTP v1 request uses OAuth bearer auth and a fixed payload', async () => { let requestedUrl = '' let requestedInit: RequestInit | undefined const fetchImpl: typeof fetch = (input, init) => { requestedUrl = String(input) requestedInit = init return Promise.resolve(new Response(JSON.stringify({ name: 'projects/d3ro-test-project/messages/123', }), { status: 200, headers: { 'Content-Type': 'application/json' } })) } const notification = buildPushNotification('transcription.completed', resourceId, 'en') const result = await sendFcmMessage('registered-device-token-123456789', notification, { fetchImpl, config: fcmConfig, getAccessToken: () => Promise.resolve('short-lived-oauth-token'), }) assert(requestedUrl === 'https://fcm.googleapis.com/v1/projects/d3ro-test-project/messages:send', 'HTTP v1 endpoint required') const headers = new Headers(requestedInit?.headers) assert(headers.get('Authorization') === 'Bearer short-lived-oauth-token', 'OAuth bearer required') const payload = JSON.parse(String(requestedInit?.body)) as Record const message = payload.message as Record assert(message.token === 'registered-device-token-123456789', 'registration token must target one device') assert(JSON.stringify(message.data) === JSON.stringify(notification.data), 'only fixed server data must be sent') assert(!('notification' in message), 'FCM payload must be data-only so native validation runs first') const android = message.android as Record assert(android.priority === 'high', 'user-visible event must be high priority') assert(android.ttl === '3600s', 'transcription TTL must be bounded') assert(android.collapse_key === `transcription:${resourceId}`, 'collapse key must derive from event and resource') assert(android.restricted_package_name === 'com.d3ro.voice', 'package target must be fixed') assert(result.messageId.endsWith('/123'), 'provider message id must normalize') }) Deno.test('FCM UNREGISTERED response is classified for stale-token deletion', async () => { const fetchImpl: typeof fetch = () => Promise.resolve(new Response(JSON.stringify({ error: { code: 404, status: 'NOT_FOUND', details: [{ '@type': 'type.googleapis.com/google.firebase.fcm.v1.FcmError', errorCode: 'UNREGISTERED', }], }, }), { status: 404, headers: { 'Content-Type': 'application/json' } })) let actual: unknown try { await sendFcmMessage( 'registered-device-token-123456789', buildPushNotification('transcription.completed', resourceId, 'en'), { fetchImpl, config: fcmConfig, getAccessToken: () => Promise.resolve('short-lived-oauth-token'), }, ) } catch (error) { actual = error } assert(actual instanceof PushContractError, 'provider error must normalize') assert(actual.code === 'fcm_registration_stale', 'unregistered token must be stale') assert(actual.staleRegistration, 'stale flag must permit server-owned deletion') }) Deno.test('FCM sender rejects any data key outside the mobile allowlist', async () => { const notification = buildPushNotification('transcription.completed', resourceId, 'en') notification.data.title = 'attacker-controlled copy' let actual: unknown try { await sendFcmMessage('registered-device-token-123456789', notification, { fetchImpl: () => Promise.reject(new Error('fetch must not run')), config: fcmConfig, getAccessToken: () => Promise.resolve('short-lived-oauth-token'), }) } catch (error) { actual = error } assert(actual instanceof PushContractError, 'outbound validation must reject') assert(actual.code === 'invalid_push_payload', 'extra outbound keys must fail closed') }) Deno.test('internal dispatcher auth requires an exact service-role bearer', async () => { const legacySecret = 'service-role-secret-with-high-entropy-123456' const secret = 'sb_secret_service-role-secret-with-high-entropy-123456' assert( await isServiceRoleAuthorization(`Bearer ${legacySecret}`, legacySecret), 'exact service bearer must authenticate', ) assert( !(await isServiceRoleAuthorization(`Bearer ${legacySecret}x`, legacySecret)), 'near match must be rejected', ) assert( !(await isServiceRoleAuthorization(`bearer ${legacySecret}`, legacySecret)), 'non-canonical scheme must be rejected', ) assert( !(await isServiceRoleAuthorization(null, legacySecret)), 'missing authorization must be rejected', ) const readEnv = (name: string): string | undefined => { if (name === 'SUPABASE_SECRET_KEYS') { return JSON.stringify({ default: secret, rotated: 'sb_secret_another-service-secret-1234567890', publishable: 'sb_publishable_must-never-authenticate-1234567890', }) } return undefined } assert(await isServiceRoleApiKey(secret, readEnv), 'exact modern secret API key must authenticate') assert( !(await isServiceRoleApiKey('sb_publishable_not_a_service_secret', readEnv)), 'publishable API key must not authenticate as a service', ) assert( !(await isServiceRoleApiKey('sb_publishable_must-never-authenticate-1234567890', readEnv)), 'a publishable value in the runtime key map must still be rejected', ) assert( !(await isServiceRoleApiKey(secret, () => '{malformed')), 'malformed runtime secret map must fail closed', ) })