Skip to content
Snippets Groups Projects
jwt.js 3.22 KiB
Newer Older
const { generateKeyPair, createPublicKey, createPrivateKey } = require('crypto')

exports.generateRsaKeys = async (pass = null) => {
	const { config } = require('bootstrap')
	return new Promise((resolve, reject) => {
Louis's avatar
Louis committed
		generateKeyPair(
			'rsa',
			{
				modulusLength: 4096,
				publicKeyEncoding: {
					type: 'spki',
					format: 'pem',
				},
				privateKeyEncoding: {
					type: 'pkcs8',
					format: 'pem',
					cipher: 'aes-256-cbc',
					passphrase: pass ?? config('app.key'),
Louis's avatar
Louis committed
				},
Louis's avatar
Louis committed
			(err, pub, priv) => {
				if (err) {
					reject(err)
				} else {
					resolve({ pub: pub.toString(), priv: priv.toString() })
				}
			},
		)
const generateRsaKeys = exports.generateRsaKeys

exports.getKeys = () => {
	const { config } = require('bootstrap')
	return {
		pub: config('app.security.public_key'),
		priv: config('app.security.private_key'),
	}
}

Louis's avatar
Louis committed
exports.getJWKS = async (type = 'pub') => {
	const { config } = require('bootstrap')
	const keys = exports.getKeys()
Louis's avatar
Louis committed
	const jose = require('jose')
Louis's avatar
Louis committed

	const key = keys[type]
Louis's avatar
Louis committed
	const jwk = await jose.exportJWK(key)
Louis's avatar
Louis committed
	const kid = config('app.security.key_id')

	return {
		keys: [
			{
				kid: `${ exports.jwtOptions.keyid_prefix }${ kid }`,
				use: 'sig',
				...jwk,
				alg: 'RS256',
			},
		],
	}
}

exports.loadKeys = async () => {
	const { env, config, patchConfig } = require('bootstrap')
	let [pub, priv] = [
		config('app.security.public_key'),
		config('app.security.private_key'),
	]

	if (pub != null && priv != null) {
		return {
			pub,
Louis's avatar
Louis committed
			priv,
		}
	}

	if (config('app.security.use_ephemeral')) {
		;({ pub, priv } = await generateRsaKeys())
	} else {
		const publicB64 = config('app.security.public_key_b64')
		const privateB64 = config('app.security.private_key_b64')
		pub = Buffer.from(publicB64, 'base64').toString('utf-8')
		priv = Buffer.from(privateB64, 'base64').toString('utf-8')
	}

	pub = createPublicKey({ key: pub })
Louis's avatar
Louis committed
	priv = createPrivateKey({
		key: priv,
		passphrase: config(
			'app.security.private_key_passphrase',
			env('RSA_PRIVATE_PASSPHRASE', config('app.key')),
		),
	})

	patchConfig('app.security.public_key', pub)
	patchConfig('app.security.private_key', priv)
	return { pub, priv }
Louis's avatar
Louis committed
exports.sign = async payload => {
	const threadContext = require('core/injection/ThreadContext')
	const { config } = require('bootstrap')
Louis's avatar
Louis committed
	const jose = require('jose')
	const { priv } = exports.getKeys()

Louis's avatar
Louis committed
	return await threadContext.profile('jwt.sign', JSON.stringify(payload), () =>
Louis's avatar
Louis committed
		new jose.SignJWT(payload)
Louis's avatar
Louis committed
			.setIssuer(exports.jwtOptions.issuer)
			.setIssuedAt()
			.setProtectedHeader({ alg: 'RS256', kid: exports.jwtOptions.keyid_prefix + config('app.security.key_id') })
Louis's avatar
Louis committed
			.sign(priv),
	)
Louis's avatar
Louis committed
exports.verify = async token => {
	const threadContext = require('core/injection/ThreadContext')
Louis's avatar
Louis committed
	const jose = require('jose')
	const { getKeys, jwtOptions } = exports
	const { pub } = getKeys()

	return await threadContext.profile('jwt.verify', undefined, async () => {
Louis's avatar
Louis committed
		const { payload } = await jose.jwtVerify(token, pub, jwtOptions)
		return payload
	})
}

exports.getClaims = tokenPayload => {
	return tokenPayload[exports.jwtOptions.claims]
}

exports.jwtOptions = {
	issuer: 'urn:jetsam:systems:auth',
	claims: 'urn:jetsam:resources:claims',
Louis's avatar
Louis committed
	keyid_prefix: 'urn:jetsam:resources:jwk:'
Louis's avatar
Louis committed
}