UNPKG

@atproto/jwk

Version:

A library for working with JSON Web Keys (JWKs) in TypeScript. This is meant to be extended by environment-specific libraries like @atproto/jwk-jose.

174 lines (152 loc) 5.67 kB
import { z } from 'zod' import { jwkPubSchema } from './jwk.js' import { jwtCharsRefinement, segmentedStringRefinementFactory } from './util.js' export const signedJwtSchema = z .string() .superRefine(jwtCharsRefinement) .superRefine(segmentedStringRefinementFactory(3)) export type SignedJwt = z.infer<typeof signedJwtSchema> export const isSignedJwt = (data: unknown): data is SignedJwt => signedJwtSchema.safeParse(data).success export const unsignedJwtSchema = z .string() .superRefine(jwtCharsRefinement) .superRefine(segmentedStringRefinementFactory(2)) export type UnsignedJwt = z.infer<typeof unsignedJwtSchema> export const isUnsignedJwt = (data: unknown): data is UnsignedJwt => unsignedJwtSchema.safeParse(data).success /** * @see {@link https://www.rfc-editor.org/rfc/rfc7515.html#section-4} */ export const jwtHeaderSchema = z.object({ /** "alg" (Algorithm) Header Parameter */ alg: z.string(), /** "jku" (JWK Set URL) Header Parameter */ jku: z.string().url().optional(), /** "jwk" (JSON Web Key) Header Parameter */ jwk: z .object({ kty: z.string(), crv: z.string().optional(), x: z.string().optional(), y: z.string().optional(), e: z.string().optional(), n: z.string().optional(), }) .optional(), /** "kid" (Key ID) Header Parameter */ kid: z.string().optional(), /** "x5u" (X.509 URL) Header Parameter */ x5u: z.string().optional(), /** "x5c" (X.509 Certificate Chain) Header Parameter */ x5c: z.array(z.string()).optional(), /** "x5t" (X.509 Certificate SHA-1 Thumbprint) Header Parameter */ x5t: z.string().optional(), /** "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Header Parameter */ 'x5t#S256': z.string().optional(), /** "typ" (Type) Header Parameter */ typ: z.string().optional(), /** "cty" (Content Type) Header Parameter */ cty: z.string().optional(), /** "crit" (Critical) Header Parameter */ crit: z.array(z.string()).optional(), }) export type JwtHeader = z.infer<typeof jwtHeaderSchema> // https://www.iana.org/assignments/jwt/jwt.xhtml export const jwtPayloadSchema = z.object({ iss: z.string().optional(), aud: z.union([z.string(), z.array(z.string()).nonempty()]).optional(), sub: z.string().optional(), exp: z.number().int().optional(), nbf: z.number().int().optional(), iat: z.number().int().optional(), jti: z.string().optional(), htm: z.string().optional(), htu: z.string().optional(), ath: z.string().optional(), acr: z.string().optional(), azp: z.string().optional(), amr: z.array(z.string()).optional(), // https://datatracker.ietf.org/doc/html/rfc7800 cnf: z .object({ kid: z.string().optional(), // Key ID jwk: jwkPubSchema.optional(), // JWK jwe: z.string().optional(), // Encrypted key jku: z.string().url().optional(), // JWK Set URI ("kid" should also be provided) // https://datatracker.ietf.org/doc/html/rfc9449#section-6.1 jkt: z.string().optional(), // https://datatracker.ietf.org/doc/html/rfc8705 'x5t#S256': z.string().optional(), // X.509 Certificate SHA-256 Thumbprint // https://datatracker.ietf.org/doc/html/rfc9203 osc: z.string().optional(), // OSCORE_Input_Material carrying the parameters for using OSCORE per-message security with implicit key confirmation }) .optional(), client_id: z.string().optional(), scope: z.string().optional(), nonce: z.string().optional(), at_hash: z.string().optional(), c_hash: z.string().optional(), s_hash: z.string().optional(), auth_time: z.number().int().optional(), // https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims // OpenID: "profile" scope name: z.string().optional(), family_name: z.string().optional(), given_name: z.string().optional(), middle_name: z.string().optional(), nickname: z.string().optional(), preferred_username: z.string().optional(), gender: z.string().optional(), // OpenID only defines "male" and "female" without forbidding other values picture: z.string().url().optional(), profile: z.string().url().optional(), website: z.string().url().optional(), birthdate: z .string() .regex(/\d{4}-\d{2}-\d{2}/) // YYYY-MM-DD .optional(), zoneinfo: z .string() .regex(/^[A-Za-z0-9_/]+$/) .optional(), locale: z .string() .regex(/^[a-z]{2}(-[A-Z]{2})?$/) .optional(), updated_at: z.number().int().optional(), // OpenID: "email" scope email: z.string().optional(), email_verified: z.boolean().optional(), // OpenID: "phone" scope phone_number: z.string().optional(), phone_number_verified: z.boolean().optional(), // OpenID: "address" scope // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim address: z .object({ formatted: z.string().optional(), street_address: z.string().optional(), locality: z.string().optional(), region: z.string().optional(), postal_code: z.string().optional(), country: z.string().optional(), }) .optional(), // https://datatracker.ietf.org/doc/html/rfc9396#section-14.2 authorization_details: z .array( z .object({ type: z.string(), // https://datatracker.ietf.org/doc/html/rfc9396#section-2.2 locations: z.array(z.string()).optional(), actions: z.array(z.string()).optional(), datatypes: z.array(z.string()).optional(), identifier: z.string().optional(), privileges: z.array(z.string()).optional(), }) .passthrough(), ) .optional(), }) export type JwtPayload = z.infer<typeof jwtPayloadSchema>