@nats-io/jwt
Version:
NATS jwt.js
276 lines (274 loc) • 9.94 kB
JavaScript
;
// 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