Last active
August 17, 2021 18:42
-
-
Save rayjanoka/d48f2c55470098ec8945cb7a0f6d3224 to your computer and use it in GitHub Desktop.
Example code for the nats jwt in nodejs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const nkeys = require("nkeys.js"); | |
const base32Encode = require('base32-encode'); | |
const base64Url = require('base64url'); | |
const shajs = require('sha.js'); | |
const utf8 = require('utf8'); | |
const path = require('path'); | |
const fs = require("fs"); | |
async function setupClaim(issuedAt, expiresAt, accountSigningKeyPub, jti, userPublicKey, accountId, pub, sub) { | |
return `{ | |
"iat": ${issuedAt}, | |
"exp": ${expiresAt}, | |
"iss": "${accountSigningKeyPub}", | |
"jti": "${jti}", | |
"name": "${userPublicKey}", | |
"nats": { | |
"data": -1, | |
"issuer_account": "${accountId}", | |
"payload": -1, | |
"pub": ${JSON.stringify(pub)}, | |
"sub": ${JSON.stringify(sub)}, | |
"subs": -1, | |
"type": "user", | |
"version": 2 | |
}, | |
"sub": "${userPublicKey}" | |
}` | |
} | |
async function setupCreds(jwt, keyPair) { | |
return `-----BEGIN NATS USER JWT----- | |
${jwt} | |
------END NATS USER JWT------ | |
************************* IMPORTANT ************************* | |
NKEY Seed printed below can be used to sign and prove identity. | |
NKEYs are sensitive and should be treated as secrets. | |
-----BEGIN USER NKEY SEED----- | |
${new TextDecoder().decode(keyPair.getSeed())} | |
------END USER NKEY SEED------ | |
************************************************************* | |
` | |
} | |
async function issueUserJWT(userPublicKey, n) { | |
const textEncoder = new TextEncoder; | |
const accountSigningSeed = global.accountSigningSeed; | |
const accountId = global.accountId; | |
const subject = global.subject | |
// use the supplied account signing key to create new users | |
const accountSigningKeyPair = nkeys.fromSeed(Buffer.from(accountSigningSeed)); | |
// generate a new public key from our account signing seed (will become the user claim's iss) | |
const accountSigningKeyPub = accountSigningKeyPair.getPublicKey() | |
// set the time window | |
const issuedAt = Math.floor(new Date().getTime() / 1000) | |
const expiresAt = issuedAt + 86400 // seconds | |
const subjectPerms = { "allow": [subject] } | |
// Create a claim without the jti so we can hash it | |
let userClaim = await setupClaim( | |
issuedAt, | |
expiresAt, | |
accountSigningKeyPub, | |
"", // TBD | |
userPublicKey, | |
accountId, | |
subjectPerms, | |
subjectPerms | |
); | |
// Encode with utf-8 and minify | |
const encUserClaim = utf8.encode(JSON.stringify(JSON.parse(userClaim))) | |
// Create sha-256 digest of user claim | |
const userClaimDigest = shajs('sha256').update(encUserClaim).digest('hex') | |
// Encode the user claim digest base32 with RFC4648 | |
const jti = base32Encode(textEncoder.encode(userClaimDigest), 'RFC4648', { padding: false }); | |
// Rebuild the claim with the user claim digest (jti) | |
userClaim = await setupClaim( | |
issuedAt, | |
expiresAt, | |
accountSigningKeyPub, | |
jti, | |
userPublicKey, | |
accountId, | |
subjectPerms, | |
subjectPerms | |
) | |
if (n === 1) { console.log("New User Claim: " + userClaim); } | |
const header = `{"typ":"JWT","alg":"ed25519-nkey"}`; | |
// Encode all the things | |
const encHeader = base64Url(header, "utf8") | |
const encBody = base64Url(JSON.stringify(JSON.parse(userClaim)), "utf8") | |
// Sign the full message with the account signing key | |
const sigArray = textEncoder.encode(`${encHeader}.${encBody}`); | |
const encSig = base64Url(accountSigningKeyPair.sign(sigArray), "utf8") | |
// Concatenate the final JWT | |
return `${encHeader}.${encBody}.${encSig}` | |
} | |
async function issueUserCreds(n) { | |
// create a fresh key pair | |
const userKeyPair = nkeys.createUser(); | |
const userPublicKey = userKeyPair.getPublicKey(); | |
// console.log("New Public Key: " + userPublicKey) | |
const userJwt = await issueUserJWT(userPublicKey, n) | |
// console.log("New JWT: " + jwt) | |
const userCreds = await setupCreds(userJwt, userKeyPair) | |
return [userCreds, userPublicKey] | |
} | |
async function batchAddUser(n) { | |
for (let i = 0; i < n; i++) { | |
await issueUserCreds(n).then((data) => { | |
const userCreds = data[0]; | |
const userPublicKey = data[1]; | |
if (n === 1) { | |
console.log(`New NATS Credentials: \n${userCreds}`) | |
} | |
console.log(`${i.toLocaleString()}: Generated new creds: ${userPublicKey}`) | |
fs.writeFileSync(path.join(__dirname, 'gen/', `${userPublicKey}.creds`), userCreds) | |
}) | |
} | |
console.log('complete') | |
} | |
if (!fs.existsSync(path.join(__dirname, 'gen/'))) { | |
fs.mkdirSync(path.join(__dirname, 'gen/')); | |
} | |
const args = process.argv.slice(2); | |
global.accountId = args[0] | |
global.accountSigningSeed = args[1] | |
global.subject = args[2] | |
batchAddUser(parseFloat(args[3])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was adapted from the NATS documentation's .NET example. I did not verify the safety of the libraries used.
package.json
usage