72 lines
1.7 KiB
TypeScript
72 lines
1.7 KiB
TypeScript
import { SignJWT, jwtVerify, errors, JWTPayload } from 'jose';
|
|
import { jwtSecret } from './env-exporter';
|
|
|
|
// JWT Payload Types
|
|
export interface UserJWTPayload extends JWTPayload {
|
|
userId: number;
|
|
name?: string;
|
|
email?: string;
|
|
mobile?: string;
|
|
roles?: string[];
|
|
}
|
|
|
|
export interface StaffJWTPayload extends JWTPayload {
|
|
staffId: number;
|
|
name: string;
|
|
}
|
|
|
|
// Convert string secret to Uint8Array
|
|
const getSecret = () => {
|
|
if (!jwtSecret) {
|
|
throw new Error('JWT secret not configured');
|
|
}
|
|
return new TextEncoder().encode(jwtSecret);
|
|
};
|
|
|
|
/**
|
|
* Sign a JWT token
|
|
* Compatible with tokens signed by jsonwebtoken library
|
|
*/
|
|
export const signToken = async (
|
|
payload: Record<string, unknown>,
|
|
expiresIn: string = '7d'
|
|
): Promise<string> => {
|
|
const secret = getSecret();
|
|
|
|
return new SignJWT(payload)
|
|
.setProtectedHeader({ alg: 'HS256' })
|
|
.setIssuedAt()
|
|
.setExpirationTime(expiresIn)
|
|
.sign(secret);
|
|
};
|
|
|
|
/**
|
|
* Verify a JWT token
|
|
* Compatible with tokens signed by jsonwebtoken library
|
|
*/
|
|
export const verifyToken = async (token: string): Promise<JWTPayload> => {
|
|
try {
|
|
const secret = getSecret();
|
|
const { payload } = await jwtVerify(token, secret);
|
|
return payload;
|
|
} catch (error) {
|
|
if (error instanceof errors.JWTExpired) {
|
|
throw new Error('Token expired');
|
|
}
|
|
if (error instanceof errors.JWTInvalid) {
|
|
throw new Error('Invalid token');
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if an error is a JWT-related error
|
|
*/
|
|
export const isJWTError = (error: unknown): boolean => {
|
|
return error instanceof errors.JOSEError ||
|
|
(error instanceof Error && (
|
|
error.message.includes('token') ||
|
|
error.message.includes('JWT')
|
|
));
|
|
};
|