UNPKG

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