135 lines
3.7 KiB
TypeScript
135 lines
3.7 KiB
TypeScript
import jwt from 'jsonwebtoken';
|
|
import environment from '../config/environment';
|
|
import logger from '../utils/logger';
|
|
import { JwtPayload, RefreshTokenPayload } from '../types/auth.types';
|
|
|
|
class TokenService {
|
|
/**
|
|
* Generate JWT access token
|
|
*/
|
|
generateToken(payload: JwtPayload): string {
|
|
try {
|
|
const token = jwt.sign(
|
|
{
|
|
userId: payload.userId,
|
|
email: payload.email,
|
|
authentikSub: payload.authentikSub,
|
|
groups: payload.groups ?? [],
|
|
role: payload.role,
|
|
type: 'access',
|
|
},
|
|
environment.jwt.secret,
|
|
{
|
|
expiresIn: environment.jwt.expiresIn as any,
|
|
}
|
|
);
|
|
|
|
logger.info('Generated JWT token', { userId: payload.userId });
|
|
return token;
|
|
} catch (error) {
|
|
logger.error('Failed to generate JWT token', { error });
|
|
throw new Error('Token generation failed');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify and decode JWT token
|
|
*/
|
|
verifyToken(token: string): JwtPayload {
|
|
try {
|
|
const decoded = jwt.verify(
|
|
token,
|
|
environment.jwt.secret
|
|
) as JwtPayload & { type?: string };
|
|
|
|
if (decoded.type && decoded.type !== 'access') {
|
|
throw new Error('Invalid token type');
|
|
}
|
|
|
|
logger.debug('JWT token verified', { userId: decoded.userId });
|
|
return decoded;
|
|
} catch (error) {
|
|
if (error instanceof jwt.TokenExpiredError) {
|
|
logger.warn('JWT token expired');
|
|
throw new Error('Token expired');
|
|
} else if (error instanceof jwt.JsonWebTokenError) {
|
|
logger.warn('Invalid JWT token', { error: error.message });
|
|
throw new Error('Invalid token');
|
|
} else {
|
|
logger.error('Failed to verify JWT token', { error });
|
|
throw new Error('Token verification failed');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate refresh token (longer lived)
|
|
*/
|
|
generateRefreshToken(payload: RefreshTokenPayload): string {
|
|
try {
|
|
const token = jwt.sign(
|
|
{
|
|
userId: payload.userId,
|
|
email: payload.email,
|
|
type: 'refresh',
|
|
},
|
|
environment.jwt.secret,
|
|
{
|
|
expiresIn: '7d', // Refresh tokens valid for 7 days
|
|
}
|
|
);
|
|
|
|
logger.info('Generated refresh token', { userId: payload.userId });
|
|
return token;
|
|
} catch (error) {
|
|
logger.error('Failed to generate refresh token', { error });
|
|
throw new Error('Refresh token generation failed');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify refresh token
|
|
*/
|
|
verifyRefreshToken(token: string): RefreshTokenPayload {
|
|
try {
|
|
const decoded = jwt.verify(
|
|
token,
|
|
environment.jwt.secret
|
|
) as RefreshTokenPayload & { type?: string };
|
|
|
|
if (decoded.type && decoded.type !== 'refresh') {
|
|
throw new Error('Invalid token type');
|
|
}
|
|
|
|
logger.debug('Refresh token verified', { userId: decoded.userId });
|
|
return decoded;
|
|
} catch (error) {
|
|
if (error instanceof jwt.TokenExpiredError) {
|
|
logger.warn('Refresh token expired');
|
|
throw new Error('Refresh token expired');
|
|
} else if (error instanceof jwt.JsonWebTokenError) {
|
|
logger.warn('Invalid refresh token', { error: error.message });
|
|
throw new Error('Invalid refresh token');
|
|
} else {
|
|
logger.error('Failed to verify refresh token', { error });
|
|
throw new Error('Refresh token verification failed');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decode token without verification (for debugging)
|
|
*/
|
|
decodeToken(token: string): JwtPayload | null {
|
|
try {
|
|
const decoded = jwt.decode(token) as JwtPayload;
|
|
return decoded;
|
|
} catch (error) {
|
|
logger.error('Failed to decode token', { error });
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new TokenService();
|