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.

143 lines 4.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.segmentedStringRefinementFactory = exports.jwtCharsRefinement = exports.parseB64uJson = exports.cachedGetter = exports.matchesAny = exports.preferredOrderCmp = exports.isDefined = void 0; const base64_1 = require("multiformats/bases/base64"); const zod_1 = require("zod"); const isDefined = (i) => i !== undefined; exports.isDefined = isDefined; 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; }; exports.preferredOrderCmp = preferredOrderCmp; function matchesAny(value) { return value == null ? (v) => true : Array.isArray(value) ? (v) => value.includes(v) : (v) => v === value; } exports.matchesAny = matchesAny; /** * Decorator to cache the result of a getter on a class instance. */ const cachedGetter = (target, _context) => { return function () { const value = target.call(this); Object.defineProperty(this, target.name, { get: () => value, enumerable: true, configurable: true, }); return value; }; }; exports.cachedGetter = cachedGetter; const decoder = new TextDecoder(); function parseB64uJson(input) { const inputBytes = base64_1.base64url.baseDecode(input); const json = decoder.decode(inputBytes); return JSON.parse(json); } exports.parseB64uJson = parseB64uJson; /** * @example * ```ts * // jwtSchema will only allow base64url chars & "." (dot) * const jwtSchema = z.string().superRefine(jwtCharsRefinement) * ``` */ 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: zod_1.ZodIssueCode.custom, message: `Invalid character "${invalidChar}" in JWT at position ${i}`, }); } } }; exports.jwtCharsRefinement = jwtCharsRefinement; /** * @example * ```ts * const jwtSchema = z.string().superRefine(segmentedStringRefinementFactory(3)) * type Jwt = z.infer<typeof jwtSchema> // `${string}.${string}.${string}` * ``` */ 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: zod_1.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: zod_1.ZodIssueCode.custom, message: `${errorPrefix}: expected ${count} segments, got ${i + 1}`, }); return false; } if (nextDot - currentStart < minPartLength) { ctx.addIssue({ code: zod_1.ZodIssueCode.custom, message: `${errorPrefix}: segment ${i + 1} is too short`, }); return false; } currentStart = nextDot + 1; } if (data.indexOf('.', currentStart) !== -1) { ctx.addIssue({ code: zod_1.ZodIssueCode.custom, message: `${errorPrefix}: too many segments`, }); return false; } if (data.length - currentStart < minPartLength) { ctx.addIssue({ code: zod_1.ZodIssueCode.custom, message: `${errorPrefix}: last segment is too short`, }); return false; } return true; }; }; exports.segmentedStringRefinementFactory = segmentedStringRefinementFactory; //# sourceMappingURL=util.js.map