@mpoonuru/paseto
Version:
Modern PASETO (Platform-Agnostic Security Tokens) for Node.js with TypeScript support and V4 LOCAL encryption
830 lines (817 loc) • 26.9 kB
JavaScript
"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/index.ts
var index_exports = {};
__export(index_exports, {
PasetoClaimInvalid: () => PasetoClaimInvalid,
PasetoDecryptionFailed: () => PasetoDecryptionFailed,
PasetoError: () => PasetoError,
PasetoInvalid: () => PasetoInvalid,
PasetoNotSupported: () => PasetoNotSupported,
PasetoVerificationFailed: () => PasetoVerificationFailed,
V4: () => V4,
consume: () => consume,
decode: () => decode,
errors: () => errors,
pack: () => pack,
pae: () => pae,
timingSafeEqual: () => timingSafeEqual,
v4Decrypt: () => decrypt,
v4Encrypt: () => encrypt,
v4GenerateKey: () => generateKey,
v4Sign: () => sign,
v4Verify: () => verify
});
module.exports = __toCommonJS(index_exports);
// src/types/errors.ts
var PasetoError = class extends Error {
constructor(message, options) {
super(message, options);
this.name = "PasetoError";
}
};
var PasetoClaimInvalid = class extends PasetoError {
constructor(message, options) {
super(message, options);
this.name = "PasetoClaimInvalid";
}
};
var PasetoDecryptionFailed = class extends PasetoError {
constructor(message = "decryption failed", options) {
super(message, options);
this.name = "PasetoDecryptionFailed";
}
};
var PasetoInvalid = class extends PasetoError {
constructor(message, options) {
super(message, options);
this.name = "PasetoInvalid";
}
};
var PasetoNotSupported = class extends PasetoError {
constructor(message, options) {
super(message, options);
this.name = "PasetoNotSupported";
}
};
var PasetoVerificationFailed = class extends PasetoError {
constructor(message = "verification failed", options) {
super(message, options);
this.name = "PasetoVerificationFailed";
}
};
var errors = {
PasetoError,
PasetoClaimInvalid,
PasetoDecryptionFailed,
PasetoInvalid,
PasetoNotSupported,
PasetoVerificationFailed
};
// src/v4/encrypt.ts
var import_random = require("@stablelib/random");
var import_blake2b = require("@stablelib/blake2b");
var import_xchacha20poly1305 = require("@stablelib/xchacha20poly1305");
// src/utils/crypto.ts
var import_node_crypto = require("crypto");
function pae(...pieces) {
const output = [];
const lengthBytes = new Uint8Array(8);
const lengthView = new DataView(lengthBytes.buffer);
lengthView.setBigUint64(0, BigInt(pieces.length), true);
output.push(lengthBytes);
for (const piece of pieces) {
const pieceLengthBytes = new Uint8Array(8);
const pieceLengthView = new DataView(pieceLengthBytes.buffer);
pieceLengthView.setBigUint64(0, BigInt(piece.length), true);
output.push(pieceLengthBytes, piece);
}
const totalLength = output.reduce((sum, arr) => sum + arr.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of output) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
function pack(header, footer, ...pieces) {
const totalLength = pieces.reduce((sum, piece) => sum + piece.length, 0);
const body = new Uint8Array(totalLength);
let offset = 0;
for (const piece of pieces) {
body.set(piece, offset);
offset += piece.length;
}
const bodyBuffer = Buffer.from(body);
const encoded = header + bodyBuffer.toString("base64url");
if (footer.length > 0) {
return encoded + "." + footer.toString("base64url");
}
return encoded;
}
function consume(token, expectedHeader) {
if (!token.startsWith(expectedHeader)) {
throw new Error(`Invalid token header, expected ${expectedHeader}`);
}
const parts = token.split(".");
if (parts.length < 3) {
throw new Error("Invalid token format");
}
const bodyPart = token.slice(expectedHeader.length);
const dotIndex = bodyPart.lastIndexOf(".");
let body;
let footer = Buffer.alloc(0);
if (dotIndex !== -1) {
body = bodyPart.slice(0, dotIndex);
footer = Buffer.from(bodyPart.slice(dotIndex + 1), "base64url");
} else {
body = bodyPart;
}
const raw = Buffer.from(body, "base64url");
return { raw, footer };
}
function timingSafeEqual(a, b) {
if (a.length !== b.length) {
return false;
}
const bufferA = Buffer.from(a);
const bufferB = Buffer.from(b);
return (0, import_node_crypto.timingSafeEqual)(bufferA, bufferB);
}
// src/v4/utils.ts
var import_node_crypto3 = require("crypto");
// src/v4/keys.ts
var import_node_crypto2 = require("crypto");
var import_node_util = require("util");
var generateKeyPairAsync = (0, import_node_util.promisify)(import_node_crypto2.generateKeyPair);
var generateSecretKeyAsync = (0, import_node_util.promisify)(import_node_crypto2.generateKey);
async function generateKey(purpose, options = { format: "keyobject" }) {
const { format = "keyobject" } = options;
if (format !== "keyobject" && format !== "paserk") {
throw new TypeError('Invalid format, must be "keyobject" or "paserk"');
}
switch (purpose) {
case "local": {
const keyObject = await generateSecretKeyAsync("aes", { length: 256 });
if (format === "paserk") {
return `k4.local.${keyObject.export().toString("base64url")}`;
}
return keyObject;
}
case "public": {
const { privateKey, publicKey } = await generateKeyPairAsync("ed25519");
if (format === "paserk") {
return {
secretKey: `k4.secret.${keyObjectToBytes(privateKey).toString("base64url")}`,
publicKey: `k4.public.${keyObjectToBytes(publicKey).toString("base64url")}`
};
}
return {
privateKey,
publicKey
};
}
default:
throw new PasetoNotSupported(`Unsupported v4 purpose: ${purpose}`);
}
}
function keyObjectToBytes(keyObject) {
if (keyObject.type === "secret") {
return keyObject.export();
}
if (keyObject.asymmetricKeyType === "ed25519") {
if (keyObject.type === "public") {
const jwk = keyObject.export({ format: "jwk" });
return Buffer.from(jwk.x, "base64url");
} else if (keyObject.type === "private") {
const jwk = keyObject.export({ format: "jwk" });
const privateKey = Buffer.from(jwk.d, "base64url");
const publicKey = Buffer.from(jwk.x, "base64url");
return Buffer.concat([privateKey, publicKey]);
}
}
throw new TypeError("Unsupported key type for v4");
}
function bytesToKeyObject(bytes) {
switch (bytes.length) {
case 32:
try {
return createEd25519PublicKey(bytes);
} catch {
return (0, import_node_crypto2.createSecretKey)(bytes);
}
case 64:
return createEd25519PrivateKey(bytes);
default:
throw new TypeError(
`Invalid key length: ${bytes.length}. Expected 32 bytes (secret/public key) or 64 bytes (private key)`
);
}
}
function createEd25519PublicKey(bytes) {
const prefix = Buffer.from("302a300506032b6570032100", "hex");
const derKey = Buffer.concat([prefix, bytes]);
return (0, import_node_crypto2.createPublicKey)({
key: derKey,
format: "der",
type: "spki"
});
}
function createEd25519PrivateKey(bytes) {
const privateKey = bytes.subarray(0, 32);
const prefix = Buffer.from("302e020100300506032b657004220420", "hex");
const derKey = Buffer.concat([prefix, privateKey]);
return (0, import_node_crypto2.createPrivateKey)({
key: derKey,
format: "der",
type: "pkcs8"
});
}
// src/v4/utils.ts
function validateSymmetricKey(key) {
if (typeof key === "string" && key.startsWith("k4.local.")) {
try {
const keyData = Buffer.from(key.slice(9), "base64url");
if (keyData.length !== 32) {
throw new Error("Invalid key length");
}
return (0, import_node_crypto3.createSecretKey)(keyData);
} catch {
throw new TypeError("Invalid PASERK k4.local key format");
}
}
if (Buffer.isBuffer(key)) {
if (key.length !== 32) {
throw new TypeError("v4.local symmetric key must be exactly 32 bytes");
}
return (0, import_node_crypto3.createSecretKey)(key);
}
if (isKeyObject(key)) {
if (key.type !== "secret" || key.symmetricKeySize !== 32) {
throw new TypeError("v4.local symmetric key must be a 32-byte secret key");
}
return key;
}
throw new TypeError("Invalid symmetric key format for v4.local - must be PASERK string, Buffer, or KeyObject");
}
function processPayload(payload, options = {}) {
if (Buffer.isBuffer(payload)) {
return payload;
}
const claims = { ...payload };
const now = options.now || /* @__PURE__ */ new Date();
if (options.iat === true) {
claims.iat = Math.floor(now.getTime() / 1e3);
}
if (options.expiresIn) {
const expiration = parseTimespan(options.expiresIn);
claims.exp = Math.floor((now.getTime() + expiration) / 1e3);
}
if (options.notBefore) {
const notBefore = parseTimespan(options.notBefore);
claims.nbf = Math.floor((now.getTime() + notBefore) / 1e3);
}
if (options.audience) claims.aud = options.audience;
if (options.issuer) claims.iss = options.issuer;
if (options.subject) claims.sub = options.subject;
if (options.jti) claims.jti = options.jti;
if (options.kid) claims.kid = options.kid;
return Buffer.from(JSON.stringify(claims), "utf8");
}
function processFooter(footer) {
if (!footer) {
return Buffer.alloc(0);
}
if (Buffer.isBuffer(footer)) {
return footer;
}
if (typeof footer === "string") {
return Buffer.from(footer, "utf8");
}
return Buffer.from(JSON.stringify(footer), "utf8");
}
function processAssertion(assertion) {
if (!assertion) {
return Buffer.alloc(0);
}
if (Buffer.isBuffer(assertion)) {
return assertion;
}
return Buffer.from(assertion, "utf8");
}
function validatePrivateKey(key) {
if (typeof key === "string" && key.startsWith("k4.secret.")) {
try {
const keyData = Buffer.from(key.slice(10), "base64url");
if (keyData.length !== 64) {
throw new Error("Invalid key length");
}
return bytesToKeyObject(keyData);
} catch {
throw new TypeError("Invalid PASERK k4.secret key format");
}
}
if (isKeyObject(key)) {
if (key.type !== "private" || key.asymmetricKeyType !== "ed25519") {
throw new TypeError("v4.public private key must be an Ed25519 private key");
}
return key;
}
throw new TypeError("Invalid private key format for v4.public - must be Ed25519 KeyObject");
}
function validatePublicKey(key) {
if (typeof key === "string" && key.startsWith("k4.public.")) {
try {
const keyData = Buffer.from(key.slice(10), "base64url");
if (keyData.length !== 32) {
throw new Error("Invalid key length");
}
return bytesToKeyObject(keyData);
} catch {
throw new TypeError("Invalid PASERK k4.public key format");
}
}
if (isKeyObject(key)) {
if (key.type !== "public" || key.asymmetricKeyType !== "ed25519") {
throw new TypeError("v4.public public key must be an Ed25519 public key");
}
return key;
}
throw new TypeError("Invalid public key format for v4.public - must be Ed25519 KeyObject");
}
function isKeyObject(value) {
return Boolean(value && typeof value === "object" && value !== null && value.constructor?.name?.endsWith("KeyObject"));
}
function parseTimespan(timespan) {
const match = timespan.match(/^(\d+)([smhd]?)$/);
if (!match) {
throw new PasetoError(`Invalid timespan format: ${timespan}`);
}
const value = parseInt(match[1], 10);
const unit = match[2] || "s";
switch (unit) {
case "s":
return value * 1e3;
case "m":
return value * 60 * 1e3;
case "h":
return value * 60 * 60 * 1e3;
case "d":
return value * 24 * 60 * 60 * 1e3;
default:
throw new PasetoError(`Invalid timespan unit: ${unit}`);
}
}
// src/v4/encrypt.ts
async function encrypt(payload, key, options = {}) {
const { footer, assertion, ...payloadOptions } = options;
const processedPayload = processPayload(payload, payloadOptions);
const validatedKey = validateSymmetricKey(key);
const processedFooter = processFooter(footer);
const processedAssertion = processAssertion(assertion);
const keyBytes = new Uint8Array(validatedKey.export());
if (keyBytes.length !== 32) {
throw new Error("v4.local key must be exactly 32 bytes");
}
const payloadBytes = new Uint8Array(processedPayload);
const footerBytes = new Uint8Array(processedFooter);
const assertionBytes = new Uint8Array(processedAssertion);
const n = (0, import_random.randomBytes)(32);
const EK_INFO = new TextEncoder().encode("paseto-encryption-key");
const AK_INFO = new TextEncoder().encode("paseto-auth-key-for-aead");
const N2_INFO = new TextEncoder().encode("paseto-nonce");
const ekDeriver = new import_blake2b.BLAKE2b(32, { key: keyBytes });
ekDeriver.update(n);
ekDeriver.update(EK_INFO);
const Ek = ekDeriver.digest();
const akDeriver = new import_blake2b.BLAKE2b(32, { key: keyBytes });
akDeriver.update(n);
akDeriver.update(AK_INFO);
const Ak = akDeriver.digest();
const n2Deriver = new import_blake2b.BLAKE2b(32, { key: keyBytes });
n2Deriver.update(n);
n2Deriver.update(N2_INFO);
const n2Full = n2Deriver.digest();
const n2 = n2Full.slice(0, 24);
const aead = new import_xchacha20poly1305.XChaCha20Poly1305(Ek);
const c = aead.seal(n2, payloadBytes);
const header = "v4.local.";
const headerBytes = new TextEncoder().encode(header);
const preAuth = pae(headerBytes, n, c, footerBytes, assertionBytes);
const auth = new import_blake2b.BLAKE2b(32, { key: Ak });
auth.update(preAuth);
const t = auth.digest();
return pack(header, processedFooter, n, c, t);
}
// src/v4/decrypt.ts
var import_blake2b2 = require("@stablelib/blake2b");
var import_xchacha20poly13052 = require("@stablelib/xchacha20poly1305");
async function decrypt(token, key, options = {}) {
const {
assertion,
complete = false,
buffer = false,
...claimOptions
} = options;
const validatedKey = validateSymmetricKey(key);
const processedAssertion = processAssertion(assertion);
const { raw, footer } = consume(token, "v4.local.");
if (raw.length < 80) {
throw new PasetoDecryptionFailed("Invalid token format - insufficient length");
}
const n = new Uint8Array(raw.subarray(0, 32));
const t = new Uint8Array(raw.subarray(-32));
const c = new Uint8Array(raw.subarray(32, -32));
const keyBytes = new Uint8Array(validatedKey.export());
if (keyBytes.length !== 32) {
throw new Error("v4.local key must be exactly 32 bytes");
}
const EK_INFO = new TextEncoder().encode("paseto-encryption-key");
const AK_INFO = new TextEncoder().encode("paseto-auth-key-for-aead");
const N2_INFO = new TextEncoder().encode("paseto-nonce");
const ekDeriver = new import_blake2b2.BLAKE2b(32, { key: keyBytes });
ekDeriver.update(n);
ekDeriver.update(EK_INFO);
const Ek = ekDeriver.digest();
const akDeriver = new import_blake2b2.BLAKE2b(32, { key: keyBytes });
akDeriver.update(n);
akDeriver.update(AK_INFO);
const Ak = akDeriver.digest();
const n2Deriver = new import_blake2b2.BLAKE2b(32, { key: keyBytes });
n2Deriver.update(n);
n2Deriver.update(N2_INFO);
const n2Full = n2Deriver.digest();
const n2 = n2Full.slice(0, 24);
const header = "v4.local.";
const headerBytes = new TextEncoder().encode(header);
const footerBytes = new Uint8Array(footer);
const assertionBytes = new Uint8Array(processedAssertion);
const preAuth = pae(headerBytes, n, c, footerBytes, assertionBytes);
const auth = new import_blake2b2.BLAKE2b(32, { key: Ak });
auth.update(preAuth);
const expectedTag = auth.digest();
if (!timingSafeEqual(t, expectedTag)) {
throw new PasetoDecryptionFailed("Authentication verification failed");
}
const aead = new import_xchacha20poly13052.XChaCha20Poly1305(Ek);
let payload;
try {
const decryptedPayload = aead.open(n2, c);
if (decryptedPayload === null) {
throw new PasetoDecryptionFailed("XChaCha20-Poly1305 decryption failed");
}
payload = decryptedPayload;
} catch (error) {
throw new PasetoDecryptionFailed("XChaCha20-Poly1305 decryption failed");
}
const payloadBuffer = Buffer.from(payload);
if (buffer) {
if (complete) {
return {
payload: payloadBuffer,
footer: footer.length > 0 ? footer : void 0,
purpose: "local",
version: "v4"
};
}
return payloadBuffer;
}
let parsedPayload;
try {
parsedPayload = JSON.parse(payloadBuffer.toString("utf8"));
} catch {
throw new PasetoDecryptionFailed("Invalid JSON payload");
}
validateClaims(parsedPayload, claimOptions);
if (complete) {
return {
payload: parsedPayload,
footer: footer.length > 0 ? footer : void 0,
purpose: "local",
version: "v4"
};
}
return parsedPayload;
}
function normalizeTimestamp(value) {
if (value === void 0 || value === null) {
return null;
}
if (typeof value === "number") {
return value;
}
if (typeof value === "string" || value instanceof Date) {
const timestamp = new Date(value).getTime();
if (isNaN(timestamp)) {
return null;
}
return Math.floor(timestamp / 1e3);
}
return null;
}
function validateClaims(payload, options) {
if (!payload || typeof payload !== "object") {
return;
}
const now = Math.floor((options.now || /* @__PURE__ */ new Date()).getTime() / 1e3);
const DEFAULT_CLOCK_TOLERANCE = 60;
const tolerance = options.clockTolerance ? parseTimespan2(options.clockTolerance) / 1e3 : DEFAULT_CLOCK_TOLERANCE;
if (!options.ignoreExp && payload.exp !== void 0) {
const expTimestamp = normalizeTimestamp(payload.exp);
if (expTimestamp === null) {
throw new PasetoClaimInvalid("Invalid expiration time format");
}
if (now > expTimestamp + tolerance) {
throw new PasetoClaimInvalid("Token has expired");
}
}
if (!options.ignoreNbf && payload.nbf !== void 0) {
const nbfTimestamp = normalizeTimestamp(payload.nbf);
if (nbfTimestamp === null) {
throw new PasetoClaimInvalid("Invalid not-before time format");
}
if (nbfTimestamp > now + tolerance) {
throw new PasetoClaimInvalid("Token not yet valid");
}
}
if (!options.ignoreIat && payload.iat !== void 0) {
const iatTimestamp = normalizeTimestamp(payload.iat);
if (iatTimestamp === null) {
throw new PasetoClaimInvalid("Invalid issued-at time format");
}
if (iatTimestamp > now + tolerance) {
throw new PasetoClaimInvalid("Token issued in the future");
}
if (options.maxTokenAge) {
const maxAge = parseTimespan2(options.maxTokenAge) / 1e3;
if (now - iatTimestamp > maxAge) {
throw new PasetoClaimInvalid("Token is too old");
}
}
}
if (options.audience && payload.aud !== options.audience) {
throw new PasetoClaimInvalid(`Invalid audience, expected ${options.audience}`);
}
if (options.issuer && payload.iss !== options.issuer) {
throw new PasetoClaimInvalid(`Invalid issuer, expected ${options.issuer}`);
}
if (options.subject && payload.sub !== options.subject) {
throw new PasetoClaimInvalid(`Invalid subject, expected ${options.subject}`);
}
}
function parseTimespan2(timespan) {
const match = timespan.match(/^(\d+)([smhd]?)$/);
if (!match) {
throw new Error(`Invalid timespan format: ${timespan}`);
}
const value = parseInt(match[1], 10);
const unit = match[2] || "s";
switch (unit) {
case "s":
return value * 1e3;
case "m":
return value * 60 * 1e3;
case "h":
return value * 60 * 60 * 1e3;
case "d":
return value * 24 * 60 * 60 * 1e3;
default:
throw new Error(`Invalid timespan unit: ${unit}`);
}
}
// src/v4/sign.ts
var import_node_crypto4 = require("crypto");
var import_node_util2 = require("util");
var signAsync = (0, import_node_util2.promisify)(import_node_crypto4.sign);
async function sign(payload, key, options = {}) {
const { footer, assertion, ...payloadOptions } = options;
const validatedKey = validatePrivateKey(key);
const processedPayload = processPayload(payload, payloadOptions);
const processedFooter = processFooter(footer);
const processedAssertion = processAssertion(assertion);
const header = "v4.public.";
const message = Buffer.concat([
Buffer.from(header, "utf8"),
processedPayload,
processedFooter,
processedAssertion
]);
const signature = await signAsync(null, message, validatedKey);
return pack(header, processedFooter, processedPayload, signature);
}
// src/v4/verify.ts
var import_node_crypto5 = require("crypto");
var import_node_util3 = require("util");
var verifyAsync = (0, import_node_util3.promisify)(import_node_crypto5.verify);
async function verify(token, key, options = {}) {
const {
assertion,
complete = false,
buffer = false,
...claimOptions
} = options;
const validatedKey = validatePublicKey(key);
const processedAssertion = processAssertion(assertion);
const { raw, footer } = consume(token, "v4.public.");
if (raw.length < 64) {
throw new PasetoVerificationFailed("Invalid token format");
}
const signature = raw.subarray(-64);
const payload = raw.subarray(0, -64);
const header = "v4.public.";
const message = Buffer.concat([
Buffer.from(header, "utf8"),
payload,
footer,
processedAssertion
]);
const isValid = await verifyAsync(null, message, validatedKey, signature);
if (!isValid) {
throw new PasetoVerificationFailed("Signature verification failed");
}
if (buffer) {
if (complete) {
return {
payload,
footer: footer.length > 0 ? footer : void 0,
purpose: "public",
version: "v4"
};
}
return payload;
}
let parsedPayload;
try {
parsedPayload = JSON.parse(payload.toString("utf8"));
} catch {
throw new PasetoVerificationFailed("Invalid JSON payload");
}
validateClaims2(parsedPayload, claimOptions);
if (complete) {
return {
payload: parsedPayload,
footer: footer.length > 0 ? footer : void 0,
purpose: "public",
version: "v4"
};
}
return parsedPayload;
}
function normalizeTimestamp2(value) {
if (value === void 0 || value === null) {
return null;
}
if (typeof value === "number") {
return value;
}
if (typeof value === "string" || value instanceof Date) {
const timestamp = new Date(value).getTime();
if (isNaN(timestamp)) {
return null;
}
return Math.floor(timestamp / 1e3);
}
return null;
}
function validateClaims2(payload, options) {
if (!payload || typeof payload !== "object") {
return;
}
const now = Math.floor((options.now || /* @__PURE__ */ new Date()).getTime() / 1e3);
const DEFAULT_CLOCK_TOLERANCE = 60;
const tolerance = options.clockTolerance ? parseTimespan3(options.clockTolerance) / 1e3 : DEFAULT_CLOCK_TOLERANCE;
if (!options.ignoreExp && payload.exp !== void 0) {
const expTimestamp = normalizeTimestamp2(payload.exp);
if (expTimestamp === null) {
throw new PasetoClaimInvalid("Invalid expiration time format");
}
if (now > expTimestamp + tolerance) {
throw new PasetoClaimInvalid("Token has expired");
}
}
if (!options.ignoreNbf && payload.nbf !== void 0) {
const nbfTimestamp = normalizeTimestamp2(payload.nbf);
if (nbfTimestamp === null) {
throw new PasetoClaimInvalid("Invalid not-before time format");
}
if (nbfTimestamp > now + tolerance) {
throw new PasetoClaimInvalid("Token not yet valid");
}
}
if (!options.ignoreIat && payload.iat !== void 0) {
const iatTimestamp = normalizeTimestamp2(payload.iat);
if (iatTimestamp === null) {
throw new PasetoClaimInvalid("Invalid issued-at time format");
}
if (iatTimestamp > now + tolerance) {
throw new PasetoClaimInvalid("Token issued in the future");
}
if (options.maxTokenAge) {
const maxAge = parseTimespan3(options.maxTokenAge) / 1e3;
if (now - iatTimestamp > maxAge) {
throw new PasetoClaimInvalid("Token is too old");
}
}
}
if (options.audience && payload.aud !== options.audience) {
throw new PasetoClaimInvalid(`Invalid audience, expected ${options.audience}`);
}
if (options.issuer && payload.iss !== options.issuer) {
throw new PasetoClaimInvalid(`Invalid issuer, expected ${options.issuer}`);
}
if (options.subject && payload.sub !== options.subject) {
throw new PasetoClaimInvalid(`Invalid subject, expected ${options.subject}`);
}
}
function parseTimespan3(timespan) {
const match = timespan.match(/^(\d+)([smhd]?)$/);
if (!match) {
throw new Error(`Invalid timespan format: ${timespan}`);
}
const value = parseInt(match[1], 10);
const unit = match[2] || "s";
switch (unit) {
case "s":
return value * 1e3;
case "m":
return value * 60 * 1e3;
case "h":
return value * 60 * 60 * 1e3;
case "d":
return value * 24 * 60 * 60 * 1e3;
default:
throw new Error(`Invalid timespan unit: ${unit}`);
}
}
// src/index.ts
var V4 = {
encrypt,
decrypt,
sign,
verify,
generateKey
};
function decode(token) {
if (typeof token !== "string" || token.length === 0) {
throw new PasetoInvalid("Token must be a non-empty string");
}
const parts = token.split(".");
if (parts.length < 3) {
throw new PasetoInvalid("Invalid token format");
}
const header = parts[0] + "." + parts[1] + ".";
let version;
let purpose;
if (header.startsWith("v4.local.")) {
version = "v4";
purpose = "local";
} else if (header.startsWith("v4.public.")) {
version = "v4";
purpose = "public";
} else {
throw new PasetoNotSupported("Unsupported token version");
}
const result = {
version,
purpose
};
return result;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
PasetoClaimInvalid,
PasetoDecryptionFailed,
PasetoError,
PasetoInvalid,
PasetoNotSupported,
PasetoVerificationFailed,
V4,
consume,
decode,
errors,
pack,
pae,
timingSafeEqual,
v4Decrypt,
v4Encrypt,
v4GenerateKey,
v4Sign,
v4Verify
});