@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
JavaScript
;
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
});