import * as jose from 'jose' import type { Chain } from 'viem' import * as z from 'zod/mini' import type * as Auth from './Auth.js' namespace schema { export const Token = z.object({ header: z.object({ alg: z.optional(z.string()), kid: z.optional(z.string()), typ: z.optional(z.string()), }), payload: z.object({ aud: z.optional(z.union([z.string(), z.readonly(z.array(z.string()))])), environment: z.optional(z.string()), exp: z.optional(z.number()), iat: z.optional(z.number()), iss: z.optional(z.string()), nbf: z.optional(z.number()), owner: z.optional(z.string()), owner_id: z.optional(z.string()), project: z.optional(z.string()), project_id: z.optional(z.string()), sub: z.optional(z.string()), user_id: z.optional(z.string()), }), }) export const Options = z.object({ enforce: z._default(z.boolean(), false), environments: z .readonly(z.array(z.enum(['development', 'preview', 'production']))) .check(z.minLength(1)), issuer: z.url(), orgIds: z.readonly(z.array(z.string().check(z.minLength(1)))).check(z.minLength(1)), projectIds: z.readonly(z.array(z.string().check(z.minLength(1)))).check(z.minLength(1)), teamId: z.string().check(z.minLength(1)), }) } /** Builds an authorization policy for Vercel workload identities accessing mainnet Zones. */ export function create(options: create.Options, internals: create.Internals = {}): create.Policy { const identity = schema.Options.parse(options) const enforce = identity.enforce const jwks = internals.jwks?.(identity.issuer) ?? jose.createRemoteJWKSet(new URL('/.well-known/jwks', identity.issuer)) async function verify(request: Request, zone: Chain): Promise { const authorization = request.headers.get('authorization') const credential = /^Bearer\s+(.+)$/i.exec(authorization ?? '')?.[1] if (!credential) return result(enforce, zone.id, 'denied', 'missing') const token = decode(credential) if (!token) return result(enforce, zone.id, 'denied', 'invalid') try { const { payload } = await jose.jwtVerify(credential, jwks, { algorithms: ['RS256'], audience: String(zone.id), issuer: identity.issuer, requiredClaims: ['exp'], }) const environment = identity.environments.find( (environment) => environment === payload['environment'], ) if ( environment === undefined || payload['owner_id'] !== identity.teamId || !identity.projectIds.includes(String(payload['project_id'])) ) return result(enforce, zone.id, 'denied', 'forbidden', token) return { ...result(enforce, zone.id, 'authorized', 'authorized', token), environment, } } catch (cause) { if (isInvalidCredentialError(cause)) return result(enforce, zone.id, 'denied', 'invalid', token) return result(enforce, zone.id, 'error', 'verification_unavailable', token) } } return { applies: (principal) => principal?.type === 'api_key' && identity.orgIds.includes(principal.orgId), verify, } } /** Returns the HTTP error for a failed workload-identity evaluation. */ export function authorizationError(result: Result): authorizationError.Options | undefined { if (!result.enforce || result.reason === 'authorized') return undefined if (result.reason === 'forbidden') return { code: 'forbidden', message: 'Access denied.', status: 403, } if (result.reason === 'verification_unavailable') return { code: 'internal_error', message: 'Internal server error.', status: 500, } return { code: 'unauthorized', message: 'Authentication required.', status: 401, } } export declare namespace authorizationError { /** Public error returned when workload-identity authorization fails. */ type Options = | { code: 'forbidden'; message: string; status: 403 } | { code: 'internal_error'; message: string; status: 500 } | { code: 'unauthorized'; message: string; status: 401 } } export declare namespace create { /** Test-only key resolver injection. */ type Internals = { /** Resolves an issuer's signing keys without a remote fetch. */ jwks?: ((issuer: string) => jose.JWTVerifyGetKey) | undefined } /** Vercel OIDC verifier configuration grouped by authoritative Zone. */ type Options = z.input /** Selects organizations for enforcement and verifies their workload identities. */ type Policy = { /** Whether the request principal belongs to a configured organization. */ applies: (principal: Auth.Principal | null) => boolean /** Verifies a request's bearer identity against an authoritative Zone. */ verify: Verifier } /** Verifies a request's bearer identity against an authoritative Zone. */ type Verifier = (request: Request, zone: Chain) => Promise } /** Sanitized Vercel OIDC evaluation emitted with the canonical request log. */ export type Result = { /** Authoritative Zone chain id evaluated by the policy. */ chainId: number /** Whether this evaluation blocks failed requests. */ enforce: boolean /** Verified deployment environment, present after successful verification. */ environment?: 'development' | 'preview' | 'production' | undefined /** Verification outcome. */ outcome: 'authorized' | 'denied' | 'error' /** Sanitized evaluation reason. */ reason: 'authorized' | 'forbidden' | 'invalid' | 'missing' | 'verification_unavailable' /** Decoded Vercel JWT fields. The encoded credential and signature are never logged. */ token?: z.output | undefined } function result( enforce: boolean, chainId: number, outcome: Result['outcome'], reason: Result['reason'], token?: Result['token'], ): Result { if (token) return { chainId, enforce, outcome, reason, token } return { chainId, enforce, outcome, reason } } function decode(credential: string): Result['token'] { try { const parsed = schema.Token.safeParse({ header: jose.decodeProtectedHeader(credential), payload: jose.decodeJwt(credential), }) return parsed.success ? parsed.data : undefined } catch { return undefined } } function isInvalidCredentialError(cause: unknown) { return ( cause instanceof jose.errors.JOSEAlgNotAllowed || cause instanceof jose.errors.JOSENotSupported || cause instanceof jose.errors.JWKSNoMatchingKey || cause instanceof jose.errors.JWSInvalid || cause instanceof jose.errors.JWSSignatureVerificationFailed || cause instanceof jose.errors.JWTClaimValidationFailed || cause instanceof jose.errors.JWTExpired || cause instanceof jose.errors.JWTInvalid ) }