Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | 10x 10x 10x 10x 10x 6x 6x 2x 2x 10x 2x 8x 8x 1x 5x 2x 2x 2x 2x | /**
* JWT Token Service
*
* Handles token issuance, verification, and blacklisting.
* Uses short-lived access tokens + longer-lived refresh tokens.
*/
import jwt, { type SignOptions } from 'jsonwebtoken';
interface TokenPayload {
userId: string;
email: string;
type: 'access' | 'refresh';
}
export class TokenService {
private secret: string;
private accessTokenExpiry: string;
private refreshTokenExpiry: string;
private blacklist: Map<string, number> = new Map(); // token -> expiry timestamp
constructor(secret: string, accessTokenExpiry: string = '15m', refreshTokenExpiry: string = '7d') {
this.secret = secret;
this.accessTokenExpiry = accessTokenExpiry;
this.refreshTokenExpiry = refreshTokenExpiry;
// Clean up expired blacklist entries every 5 minutes
setInterval(() => this.cleanupBlacklist(), 5 * 60 * 1000).unref();
}
issueAccessToken(userId: string, email: string): string {
const payload: TokenPayload = { userId, email, type: 'access' };
return jwt.sign(payload, this.secret, { expiresIn: this.accessTokenExpiry } as SignOptions);
}
issueRefreshToken(userId: string, email: string): string {
const payload: TokenPayload = { userId, email, type: 'refresh' };
return jwt.sign(payload, this.secret, { expiresIn: this.refreshTokenExpiry } as SignOptions);
}
verifyToken(token: string, expectedType: 'access' | 'refresh' = 'access'): TokenPayload {
if (this.blacklist.has(token)) {
throw new Error('Token has been revoked');
}
const decoded = jwt.verify(token, this.secret) as TokenPayload & { exp: number };
if (decoded.type !== expectedType) {
throw new Error(`Expected ${expectedType} token, got ${decoded.type}`);
}
return decoded;
}
blacklistToken(token: string): void {
try {
const decoded = jwt.decode(token) as { exp?: number } | null;
Eif (decoded?.exp) {
this.blacklist.set(token, decoded.exp * 1000); // Convert to ms
}
} catch {
// If we can't decode it, no need to blacklist
}
}
private cleanupBlacklist(): void {
const now = Date.now();
for (const [token, expiry] of this.blacklist) {
if (expiry < now) {
this.blacklist.delete(token);
}
}
}
}
|