// Copyright Abridged, Inc. 2021,2024. All Rights Reserved. // Node module: @collabland/common // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import {BinaryToTextEncoding, createHmac} from 'crypto'; import jwt, {Secret, SignOptions, VerifyOptions} from 'jsonwebtoken'; import {getApiServerURL, getEnvVar, getEnvVarAsObject} from './env.js'; import {generateIdSync} from './id-generator.js'; /** * Symmetric JWT secret */ export type JWTSecret = {secret: string; keyid?: string}; /** * Encode a json object into JWT */ export function jwtSign( payload: string | Buffer | object, secretOrPrivateKey: Secret = getJwtSecret(), options?: SignOptions, ): string { return jwt.sign(payload, secretOrPrivateKey, { issuer: getJwtIssuer(), ...options, }); } /** * Decode a JWT into a json object */ export function jwtVerify( token: string, secretOrPublicKey: Secret, options?: VerifyOptions, ): object | string { return jwt.verify(token, secretOrPublicKey, options); } export function createHmacHash( seed: string, keyid?: string, encoding: BinaryToTextEncoding = 'base64url', ) { return createHmac('sha256', seed) .update(keyid ?? 'collab.land') .digest(encoding); } /** * Get the signing secret for JWTs * @param keyid - Optional key id * @returns */ export function getJwtSecret(keyid?: string, secrets?: JWTSecret[]) { secrets = secrets ?? getJWtSecrets(); let found: JWTSecret | undefined = secrets[0]; if (keyid == null) { return found.secret; } found = secrets.find(s => s.keyid === keyid); if (found != null) return found.secret; return createHmacHash(secrets[0].secret, keyid); } /** * Load JWT secrets from env vars * @returns */ export function getJWtSecrets() { const secrets: JWTSecret[] = []; const secret = getEnvVar('COLLABLAND_JWT_SECRET'); if (secret != null) { secrets.push({secret}); } if (getEnvVar('COLLABLAND_JWT_SECRETS') != null) { const jwtSecrets = getEnvVarAsObject('COLLABLAND_JWT_SECRETS'); if (jwtSecrets != null) { secrets.push(...jwtSecrets); } } if (secrets.length === 0) { // Generate 10 random secrets for (let i = 0; i < 10; i++) { const keyid = `s${i}`; secrets.push({keyid, secret: createHmacHash(generateIdSync(), keyid)}); } } return secrets; } /** * Get the JWT issuer * @returns */ export function getJwtIssuer() { return getApiServerURL(); } /** * Get the JWT audience * @returns */ export function getJwtAudience() { return getApiServerURL(); } import jsonwebtoken from 'jsonwebtoken'; export const {decode, sign, verify} = jsonwebtoken; export type { DecodeOptions, Jwt, JwtHeader, JwtPayload, Secret, SignOptions, VerifyOptions, } from 'jsonwebtoken';