UNPKG

@nats-io/jwt

Version:
276 lines (274 loc) 9.94 kB
"use strict"; // Copyright 2021-2024 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. Object.defineProperty(exports, "__esModule", { value: true }); exports.parseCreds = exports.fmtCreds = exports.newScopedSigner = exports.encode = exports.decode = exports.encodeAuthorizationResponse = exports.encodeGeneric = exports.encodeActivation = exports.encodeUser = exports.encodeAccount = exports.encodeOperator = exports.Algorithms = void 0; const types_1 = require("./types"); const util_1 = require("./util"); const keys_1 = require("./keys"); const base64_1 = require("./base64"); /** * Enum capturing the JWT algorithm */ var Algorithms; (function (Algorithms) { Algorithms["v1"] = "ed25519"; Algorithms["v2"] = "ed25519-nkey"; })(Algorithms || (exports.Algorithms = Algorithms = {})); function initClaim(opts) { const { exp, nbf, aud } = opts; return (0, util_1.extend)({}, { exp, nbf, aud }); } function initAlgorithm(opts = {}) { if (!opts.algorithm) { opts.algorithm = Algorithms.v2; } return opts; } /** * Generates an operator JWT * @param name - the operator name * @param okp - a key representing the operator * @param operator - operator options * @param opts - encoding options */ async function encodeOperator(name, okp, operator = {}, opts = {}) { okp = (0, keys_1.checkKey)(okp, "O", !opts.signer); let signer = okp; if (opts.signer) { signer = (0, keys_1.checkKey)(opts.signer, ["O"], true); } const claim = initClaim(opts); claim.name = name; claim.sub = okp.getPublicKey(); claim.nats = operator; const o = initAlgorithm(opts); setVersionType(o.algorithm, types_1.Types.Operator, claim); return await encode(o.algorithm, claim, signer); } exports.encodeOperator = encodeOperator; /** * Generates an account JWT with the specified name having the identity of the * providedd Key. * @param name * @param akp * @param account * @param opts */ async function encodeAccount(name, akp, account = {}, opts = {}) { akp = (0, keys_1.checkKey)(akp, "A", !opts.signer); let signer = akp; if (opts.signer) { signer = (0, keys_1.checkKey)(opts.signer, ["O", "A"], true); } const claim = initClaim(opts); claim.name = name; claim.sub = akp.getPublicKey(); claim.nats = account; const o = initAlgorithm(opts); setVersionType(o.algorithm, types_1.Types.Account, claim); return await encode(o.algorithm, claim, signer); } exports.encodeAccount = encodeAccount; async function encodeUser(name, ukp, issuer, user = {}, opts = {}) { issuer = (0, keys_1.checkKey)(issuer, "A", !opts.signer); let signer = issuer; if (opts.signer) { signer = (0, keys_1.checkKey)(opts.signer, "A", true); } ukp = (0, keys_1.checkKey)(ukp, "U"); const claim = initClaim(opts); claim.name = name; claim.sub = ukp.getPublicKey(); claim.nats = opts.scopedUser ? user : (0, util_1.defaultUser)(user); if (opts.signer) { claim.nats.issuer_account = issuer.getPublicKey(); } const o = initAlgorithm(opts); setVersionType(o.algorithm, types_1.Types.User, claim); return await encode(o.algorithm, claim, signer); } exports.encodeUser = encodeUser; function encodeActivation(name, subject, issuer, kind, data = {}, opts = {}) { subject = (0, keys_1.checkKey)(subject, "", false); issuer = (0, keys_1.checkKey)(issuer, "", !opts.signer); let signer = issuer; if (opts.signer) { signer = (0, keys_1.checkKey)(opts.signer, "", true); } const claim = initClaim(opts); claim.name = name; claim.sub = subject.getPublicKey(); claim.nats = data; if (opts.signer) { claim.nats.issuer_account = issuer.getPublicKey(); } const o = initAlgorithm(opts); const key = o.algorithm === Algorithms.v2 ? "kind" : "type"; claim.nats[key] = kind; setVersionType(o.algorithm, types_1.Types.Activation, claim); return encode(o.algorithm, claim, signer); } exports.encodeActivation = encodeActivation; async function encodeGeneric(name, akp, kind, data = {}, opts = {}) { akp = (0, keys_1.checkKey)(akp, ""); const claim = initClaim(opts); claim.name = name; claim.nats = data; claim.sub = akp.getPublicKey(); akp = (0, keys_1.checkKey)(akp, "", !opts.signer); let signer = akp; if (opts.signer) { signer = (0, keys_1.checkKey)(opts.signer, "", true); } if (opts.signer) { claim.nats.issuer_account = akp.getPublicKey(); } const o = initAlgorithm(opts); setVersionType(o.algorithm, kind, claim); return await encode(o.algorithm, claim, signer); } exports.encodeGeneric = encodeGeneric; async function encodeAuthorizationResponse(user, server, issuer, data, opts) { opts = opts || {}; // this is only in v2 opts.algorithm = Algorithms.v2; user = (0, keys_1.checkKey)(user, "U", false); server = (0, keys_1.checkKey)(server, "N", false); issuer = (0, keys_1.checkKey)(issuer, "A", false); let signer = issuer; if (opts.signer) { signer = (0, keys_1.checkKey)(opts.signer, "A", true); } const claim = initClaim(opts); claim.sub = user.getPublicKey(); claim.aud = server.getPublicKey(); claim.nats = data; if (opts.signer) { claim.nats.issuer_account = issuer.getPublicKey(); } setVersionType(opts.algorithm, types_1.Types.AuthorizationResponse, claim); return await encode(opts.algorithm, claim, signer); } exports.encodeAuthorizationResponse = encodeAuthorizationResponse; function setVersionType(version, type, claim) { claim.aud = claim.aud || "NATS"; if (version === Algorithms.v2) { claim.nats.type = type; } else { claim.type = type; } } function decode(jwt) { const chunks = jwt.split("."); if (chunks.length !== 3) { throw new Error(`invalid jwt - ${chunks.length} chunks: ${jwt}`); } const h = JSON.parse(base64_1.Base64UrlCodec.decode(chunks[0])); if (h.typ !== "jwt" && h.typ !== "JWT") { throw new Error(`not a nats jwt - typ ${h.type}`); } if (h.alg !== Algorithms.v1 && h.alg !== Algorithms.v2) { throw new Error(`not a nats jwt - alg ${h.alg}`); } const b = JSON.parse(base64_1.Base64UrlCodec.decode(chunks[1])); const ipk = (0, keys_1.checkKey)(b.iss); const sig = base64_1.Base64UrlCodec.decode(chunks[2], true); const te = new TextEncoder(); const payload = h.alg === Algorithms.v2 ? `${chunks[0]}.${chunks[1]}` : chunks[1]; if (!ipk.verify(te.encode(payload), sig)) { throw new Error("sig verification failed"); } return b; } exports.decode = decode; async function encode(version, claim, kp) { claim.iss = kp.getPublicKey(); claim.iat = Math.floor(Date.now() / 1000); const gc = claim; if (version === Algorithms.v2) { gc.nats.version = 2; } const te = new TextEncoder(); const data = te.encode(JSON.stringify(claim)); // this should be a crypto hash - on browser: if (globalThis.crypto && globalThis.crypto.subtle) { //@ts-ignore: this is a global object on a browser const hash = await globalThis.crypto.subtle.digest("SHA-512", data); claim.jti = base64_1.Base64Codec.encode(new Uint8Array(hash)); } else { claim.jti = (0, util_1.randomID)(); } const header = { typ: "JWT", alg: version, }; const hstr = base64_1.Base64UrlCodec.encode(JSON.stringify(header)); const bstr = base64_1.Base64UrlCodec.encode(JSON.stringify(claim)); const payload = version === Algorithms.v2 ? `${hstr}.${bstr}` : bstr; const sig = base64_1.Base64UrlCodec.encode(kp.sign(te.encode(payload))); return `${hstr}.${bstr}.${sig}`; } exports.encode = encode; function newScopedSigner(signer, role, limits) { signer = (0, keys_1.checkKey)(signer, "A", false); limits = (0, util_1.defaultUserPermissionsLimits)(limits); const s = {}; s.key = signer.getPublicKey(); s.role = role; s.kind = "user_scope"; s.template = limits; return s; } exports.newScopedSigner = newScopedSigner; function fmtCreds(token, kp) { const s = new TextDecoder().decode(kp.getSeed()); const creds = `-----BEGIN NATS USER JWT----- ${token} ------END NATS USER JWT------ ************************* IMPORTANT ************************* NKEY Seed printed below can be used sign and prove identity. NKEYs are sensitive and should be treated as secrets. -----BEGIN USER NKEY SEED----- ${s} ------END USER NKEY SEED------ `; return new TextEncoder().encode(creds); } exports.fmtCreds = fmtCreds; async function parseCreds(creds) { const TD = new TextDecoder(); const CREDS = /\s*(?:(?:[-]{3,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))/ig; const s = TD.decode(creds); // get the JWT let m = CREDS.exec(s); if (!m) { return Promise.reject(new Error("bad credentials")); } const jwt = m[1].trim(); const uc = await decode(jwt); const aid = uc.nats.issuer_account ? uc.nats.issuer_account : uc.iss; // next match is the key m = CREDS.exec(s); if (!m) { return Promise.reject(new Error("bad credentials")); } const key = m[1].trim(); return Promise.resolve({ key, jwt, uc, aid }); } exports.parseCreds = parseCreds; //# sourceMappingURL=jwt.js.map