Skip to content
Snippets Groups Projects
jwt.js 2.8 KiB
Newer Older
const { generateKeyPair, createPublicKey, createPrivateKey } = require('crypto')
async function generateRsaKeys() {
	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: config('app.key'),
				},
Louis's avatar
Louis committed
			(err, pub, priv) => {
				if (err) {
					reject(err)
				} else {
					resolve({ pub: pub.toString(), priv: priv.toString() })
				}
			},
		)
	})
}

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

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')
	const { default: SignJWT } = require('jose/jwt/sign')
	const { priv } = exports.getKeys()

Louis's avatar
Louis committed
	return await threadContext.profile('jwt.sign', JSON.stringify(payload), () =>
		new SignJWT(payload)
			.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')
	const { default: jwtVerify } = require('jose/jwt/verify')
	const { getKeys, jwtOptions } = exports
	const { pub } = getKeys()

	return await threadContext.profile('jwt.verify', undefined, async () => {
		const { payload } = await 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
}