UNPKG

@gaonengwww/jose

Version:

JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes

400 lines (392 loc) 12.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/lib/jwt_claims_set.ts var jwt_claims_set_exports = {}; __export(jwt_claims_set_exports, { JWTClaimsBuilder: () => JWTClaimsBuilder, validateClaimsSet: () => validateClaimsSet }); module.exports = __toCommonJS(jwt_claims_set_exports); // src/util/errors.ts var JOSEError = class extends Error { /** * A unique error code for the particular error subclass. * * @ignore */ static code = "ERR_JOSE_GENERIC"; /** A unique error code for {@link JOSEError}. */ code = "ERR_JOSE_GENERIC"; /** @ignore */ constructor(message, options) { super(message, options); this.name = this.constructor.name; Error.captureStackTrace?.(this, this.constructor); } }; var JWTClaimValidationFailed = class extends JOSEError { /** @ignore */ static code = "ERR_JWT_CLAIM_VALIDATION_FAILED"; /** A unique error code for {@link JWTClaimValidationFailed}. */ code = "ERR_JWT_CLAIM_VALIDATION_FAILED"; /** The Claim for which the validation failed. */ claim; /** Reason code for the validation failure. */ reason; /** * The parsed JWT Claims Set (aka payload). Other JWT claims may or may not have been verified at * this point. The JSON Web Signature (JWS) or a JSON Web Encryption (JWE) structures' integrity * has however been verified. Claims Set verification happens after the JWS Signature or JWE * Decryption processes. */ payload; /** @ignore */ constructor(message, payload, claim = "unspecified", reason = "unspecified") { super(message, { cause: { claim, reason, payload } }); this.claim = claim; this.reason = reason; this.payload = payload; } }; var JWTExpired = class extends JOSEError { /** @ignore */ static code = "ERR_JWT_EXPIRED"; /** A unique error code for {@link JWTExpired}. */ code = "ERR_JWT_EXPIRED"; /** The Claim for which the validation failed. */ claim; /** Reason code for the validation failure. */ reason; /** * The parsed JWT Claims Set (aka payload). Other JWT claims may or may not have been verified at * this point. The JSON Web Signature (JWS) or a JSON Web Encryption (JWE) structures' integrity * has however been verified. Claims Set verification happens after the JWS Signature or JWE * Decryption processes. */ payload; /** @ignore */ constructor(message, payload, claim = "unspecified", reason = "unspecified") { super(message, { cause: { claim, reason, payload } }); this.claim = claim; this.reason = reason; this.payload = payload; } }; var JWTInvalid = class extends JOSEError { /** @ignore */ static code = "ERR_JWT_INVALID"; /** A unique error code for {@link JWTInvalid}. */ code = "ERR_JWT_INVALID"; }; // src/lib/buffer_utils.ts var encoder = new TextEncoder(); var decoder = new TextDecoder(); var MAX_INT32 = 2 ** 32; // src/lib/epoch.ts var epoch_default = (date) => Math.floor(date.getTime() / 1e3); // src/lib/secs.ts var minute = 60; var hour = minute * 60; var day = hour * 24; var week = day * 7; var year = day * 365.25; var REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i; var secs_default = (str) => { const matched = REGEX.exec(str); if (!matched || matched[4] && matched[1]) { throw new TypeError("Invalid time period format"); } const value = parseFloat(matched[2]); const unit = matched[3].toLowerCase(); let numericDate; switch (unit) { case "sec": case "secs": case "second": case "seconds": case "s": numericDate = Math.round(value); break; case "minute": case "minutes": case "min": case "mins": case "m": numericDate = Math.round(value * minute); break; case "hour": case "hours": case "hr": case "hrs": case "h": numericDate = Math.round(value * hour); break; case "day": case "days": case "d": numericDate = Math.round(value * day); break; case "week": case "weeks": case "w": numericDate = Math.round(value * week); break; // years matched default: numericDate = Math.round(value * year); break; } if (matched[1] === "-" || matched[4] === "ago") { return -numericDate; } return numericDate; }; // src/lib/is_object.ts function isObjectLike(value) { return typeof value === "object" && value !== null; } var is_object_default = (input) => { if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") { return false; } if (Object.getPrototypeOf(input) === null) { return true; } let proto = input; while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto); } return Object.getPrototypeOf(input) === proto; }; // src/lib/jwt_claims_set.ts function validateInput(label, input) { if (!Number.isFinite(input)) { throw new TypeError(`Invalid ${label} input`); } return input; } var normalizeTyp = (value) => value.toLowerCase().replace(/^application\//, ""); var checkAudiencePresence = (audPayload, audOption) => { if (typeof audPayload === "string") { return audOption.includes(audPayload); } if (Array.isArray(audPayload)) { return audOption.some(Set.prototype.has.bind(new Set(audPayload))); } return false; }; function validateClaimsSet(protectedHeader, encodedPayload, options = {}) { let payload; try { payload = JSON.parse(decoder.decode(encodedPayload)); } catch { } if (!is_object_default(payload)) { throw new JWTInvalid("JWT Claims Set must be a top-level JSON object"); } const { typ } = options; if (typ && (typeof protectedHeader.typ !== "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) { throw new JWTClaimValidationFailed( 'unexpected "typ" JWT header value', payload, "typ", "check_failed" ); } const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options; const presenceCheck = [...requiredClaims]; if (maxTokenAge !== void 0) presenceCheck.push("iat"); if (audience !== void 0) presenceCheck.push("aud"); if (subject !== void 0) presenceCheck.push("sub"); if (issuer !== void 0) presenceCheck.push("iss"); for (const claim of new Set(presenceCheck.reverse())) { if (!(claim in payload)) { throw new JWTClaimValidationFailed( `missing required "${claim}" claim`, payload, claim, "missing" ); } } if (issuer && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) { throw new JWTClaimValidationFailed( 'unexpected "iss" claim value', payload, "iss", "check_failed" ); } if (subject && payload.sub !== subject) { throw new JWTClaimValidationFailed( 'unexpected "sub" claim value', payload, "sub", "check_failed" ); } if (audience && !checkAudiencePresence(payload.aud, typeof audience === "string" ? [audience] : audience)) { throw new JWTClaimValidationFailed( 'unexpected "aud" claim value', payload, "aud", "check_failed" ); } let tolerance; switch (typeof options.clockTolerance) { case "string": tolerance = secs_default(options.clockTolerance); break; case "number": tolerance = options.clockTolerance; break; case "undefined": tolerance = 0; break; default: throw new TypeError("Invalid clockTolerance option type"); } const { currentDate } = options; const now = epoch_default(currentDate || /* @__PURE__ */ new Date()); if ((payload.iat !== void 0 || maxTokenAge) && typeof payload.iat !== "number") { throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, "iat", "invalid"); } if (payload.nbf !== void 0) { if (typeof payload.nbf !== "number") { throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, "nbf", "invalid"); } if (payload.nbf > now + tolerance) { throw new JWTClaimValidationFailed( '"nbf" claim timestamp check failed', payload, "nbf", "check_failed" ); } } if (payload.exp !== void 0) { if (typeof payload.exp !== "number") { throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, "exp", "invalid"); } if (payload.exp <= now - tolerance) { throw new JWTExpired('"exp" claim timestamp check failed', payload, "exp", "check_failed"); } } if (maxTokenAge) { const age = now - payload.iat; const max = typeof maxTokenAge === "number" ? maxTokenAge : secs_default(maxTokenAge); if (age - tolerance > max) { throw new JWTExpired( '"iat" claim timestamp check failed (too far in the past)', payload, "iat", "check_failed" ); } if (age < 0 - tolerance) { throw new JWTClaimValidationFailed( '"iat" claim timestamp check failed (it should be in the past)', payload, "iat", "check_failed" ); } } return payload; } var JWTClaimsBuilder = class { #payload; constructor(payload) { if (!is_object_default(payload)) { throw new TypeError("JWT Claims Set MUST be an object"); } this.#payload = structuredClone(payload); } data() { return encoder.encode(JSON.stringify(this.#payload)); } get iss() { return this.#payload.iss; } set iss(value) { this.#payload.iss = value; } get sub() { return this.#payload.sub; } set sub(value) { this.#payload.sub = value; } get aud() { return this.#payload.aud; } set aud(value) { this.#payload.aud = value; } get jti() { return this.#payload.jti; } set jti(value) { this.#payload.jti = value; } get nbf() { return this.#payload.nbf; } set nbf(value) { if (typeof value === "number") { this.#payload.nbf = validateInput("setNotBefore", value); } else if (value instanceof Date) { this.#payload.nbf = validateInput("setNotBefore", epoch_default(value)); } else { this.#payload.nbf = epoch_default(/* @__PURE__ */ new Date()) + secs_default(value); } } get exp() { return this.#payload.exp; } set exp(value) { if (typeof value === "number") { this.#payload.exp = validateInput("setExpirationTime", value); } else if (value instanceof Date) { this.#payload.exp = validateInput("setExpirationTime", epoch_default(value)); } else { this.#payload.exp = epoch_default(/* @__PURE__ */ new Date()) + secs_default(value); } } get iat() { return this.#payload.iat; } set iat(value) { if (typeof value === "undefined") { this.#payload.iat = epoch_default(/* @__PURE__ */ new Date()); } else if (value instanceof Date) { this.#payload.iat = validateInput("setIssuedAt", epoch_default(value)); } else if (typeof value === "string") { this.#payload.iat = validateInput("setIssuedAt", epoch_default(/* @__PURE__ */ new Date()) + secs_default(value)); } else { this.#payload.iat = validateInput("setIssuedAt", value); } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { JWTClaimsBuilder, validateClaimsSet });