Files
kittyBE/src/tools/jwt.ts

92 lines
2.7 KiB
TypeScript

// Heavily based on:
// https://github.com/TomDoesTech/REST-API-Tutorial-Updated/blob/7b5f040e1acd94d267df585516b33ee7e3b75f70/src/utils/jwt.utils.ts
import jwt from 'jsonwebtoken';
import { DEFAULT_TOKEN_LIFETIME } from '../schemas/authSchema';
import * as env from './env';
export type JwtDecoded = {
sub: number;
role: number;
iat: number;
exp: number;
};
export type JwtStatus = {
valid: boolean;
expired: boolean;
decoded: JwtDecoded | null; // null if decoding failed
};
/**
* Sign a JWT containing sub (number), role (number, 0/1), iat/exp (unix timestamp) claims.
*
* @param {Object} object The object
* @param {('accessTokenPrivateKey'|'refreshTokenPrivateKey')} keyName The key name
* @param {} options?:jwt.SignOptions|undefined The sign options undefined
* @return {string} JWT string
*/
export function signJwt(
object: Object,
keyName: 'accessTokenPrivateKey' | 'refreshTokenPrivateKey',
options?: jwt.SignOptions | undefined
): string {
// refresh tokens aren't (yet) supported
// const signingKey = Buffer.from(
// process.env[keyName]!,
// 'base64'
// ).toString('utf8');
const secret: string = env.getString(keyName, true)!;
// Use the default expiration time of 24 hours.
if (options === undefined)
options = { expiresIn: DEFAULT_TOKEN_LIFETIME };
return jwt.sign(object, secret, {
...options, // (options && options)?
// algorithm: 'RS256', // requires a valid private key, not a secret
});
}
/**
* Verify a JWT against one of the keys.
* Returns JwtStatus, which contains fields for checking validity, expiry and decoded subject claim (id).
*
* @param {string} token The token
* @param {('accessTokenPublicKey'|'refreshTokenPublicKey')} keyName The key name
* @return {JwtStatus} JWT status.
*/
export function verifyJwt(
token: string,
keyName: 'accessTokenPrivateKey' | 'refreshTokenPrivateKey'
): JwtStatus {
// refresh tokens aren't (yet) supported
// const publicKey = Buffer.from(
// process.env[keyName]!,
// 'base64'
// ).toString('utf8');
const secret: string = env.getString(keyName, true)!;
try {
const decoded: jwt.JwtPayload | string = jwt.verify(token, secret);
// TODO: Can this be done better, smarter?
return {
valid: true,
expired: false,
decoded: decoded as unknown as JwtDecoded
};
} catch (e: any) {
console.error('JWT verify error:', e);
return {
valid: e.message !== 'jwt malformed',
expired: e.message === 'jwt expired',
decoded: null
};
}
}