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.

138 lines 4.63 kB
import { base64url } from 'multiformats/bases/base64'; import { ZodIssueCode } from 'zod'; export const isDefined = (i) => i !== undefined; export const preferredOrderCmp = (order) => (a, b) => { const aIdx = order.indexOf(a); const bIdx = order.indexOf(b); if (aIdx === bIdx) return 0; if (aIdx === -1) return 1; if (bIdx === -1) return -1; return aIdx - bIdx; }; /* eslint-disable @typescript-eslint/no-unused-vars -- `v` is used at runtime in the returned type guards; v8 false-positive */ export function matchesAny(value) { return value == null ? (v) => true : Array.isArray(value) ? (v) => value.includes(v) : (v) => v === value; } /* eslint-enable @typescript-eslint/no-unused-vars */ /** * Decorator to cache the result of a getter on a class instance. */ export const cachedGetter = (target, _context) => { return function () { const value = target.call(this); Object.defineProperty(this, target.name, { get: () => value, enumerable: true, configurable: true, }); return value; }; }; const decoder = new TextDecoder(); export function parseB64uJson(input) { const inputBytes = base64url.baseDecode(input); const json = decoder.decode(inputBytes); return JSON.parse(json); } /** * @example * ```ts * // jwtSchema will only allow base64url chars & "." (dot) * const jwtSchema = z.string().superRefine(jwtCharsRefinement) * ``` */ export const jwtCharsRefinement = (data, ctx) => { // Note: this is a hot path, let's avoid using a RegExp let char; for (let i = 0; i < data.length; i++) { char = data.charCodeAt(i); if ( // Base64 URL encoding (most frequent) (65 <= char && char <= 90) || // A-Z (97 <= char && char <= 122) || // a-z (48 <= char && char <= 57) || // 0-9 char === 45 || // - char === 95 || // _ // Boundary (least frequent, check last) char === 46 // . ) { // continue } else { // Invalid char might be a surrogate pair const invalidChar = String.fromCodePoint(data.codePointAt(i)); return ctx.addIssue({ code: ZodIssueCode.custom, message: `Invalid character "${invalidChar}" in JWT at position ${i}`, }); } } }; /** * @example * ```ts * const jwtSchema = z.string().superRefine(segmentedStringRefinementFactory(3)) * type Jwt = z.infer<typeof jwtSchema> // `${string}.${string}.${string}` * ``` */ export const segmentedStringRefinementFactory = (count, minPartLength = 2) => { if (!Number.isFinite(count) || count < 1 || (count | 0) !== count) { throw new TypeError(`Count must be a natural number (got ${count})`); } const minTotalLength = count * minPartLength + (count - 1); const errorPrefix = `Invalid JWT format`; return (data, ctx) => { if (data.length < minTotalLength) { ctx.addIssue({ code: ZodIssueCode.custom, message: `${errorPrefix}: too short`, }); return false; } let currentStart = 0; for (let i = 0; i < count - 1; i++) { const nextDot = data.indexOf('.', currentStart); if (nextDot === -1) { ctx.addIssue({ code: ZodIssueCode.custom, message: `${errorPrefix}: expected ${count} segments, got ${i + 1}`, }); return false; } if (nextDot - currentStart < minPartLength) { ctx.addIssue({ code: ZodIssueCode.custom, message: `${errorPrefix}: segment ${i + 1} is too short`, }); return false; } currentStart = nextDot + 1; } if (data.indexOf('.', currentStart) !== -1) { ctx.addIssue({ code: ZodIssueCode.custom, message: `${errorPrefix}: too many segments`, }); return false; } if (data.length - currentStart < minPartLength) { ctx.addIssue({ code: ZodIssueCode.custom, message: `${errorPrefix}: last segment is too short`, }); return false; } return true; }; }; export function isLastOccurrence(v, i, arr) { return arr.indexOf(v, i + 1) === -1; } //# sourceMappingURL=util.js.map