92 lines
2.7 KiB
TypeScript
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
|
|
};
|
|
}
|
|
}
|