Assist_Design/apps/bff/src/auth/services/token-blacklist.service.ts

75 lines
2.4 KiB
TypeScript
Raw Normal View History

2025-08-21 15:24:40 +09:00
import { Injectable, Inject } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Redis } from "ioredis";
import { Logger } from "nestjs-pino";
@Injectable()
export class TokenBlacklistService {
constructor(
2025-08-21 15:24:40 +09:00
@Inject("REDIS_CLIENT") private readonly redis: Redis,
private readonly configService: ConfigService,
@Inject(Logger) private readonly logger: Logger
) {}
2025-08-22 17:02:49 +09:00
async blacklistToken(token: string, _expiresIn?: number): Promise<void> {
// Extract JWT payload to get expiry time
try {
2025-08-22 17:02:49 +09:00
const payload = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64").toString()) as {
exp?: number;
};
const expiryTime = (payload.exp ?? 0) * 1000; // Convert to milliseconds
const currentTime = Date.now();
const ttl = Math.max(0, Math.floor((expiryTime - currentTime) / 1000)); // Convert to seconds
if (ttl > 0) {
2025-08-21 15:24:40 +09:00
await this.redis.setex(`blacklist:${token}`, ttl, "1");
}
} catch {
// If we can't parse the token, blacklist it for the default JWT expiry time
try {
const defaultTtl = this.parseJwtExpiry(this.configService.get("JWT_EXPIRES_IN", "7d"));
await this.redis.setex(`blacklist:${token}`, defaultTtl, "1");
} catch (err) {
this.logger.warn(
"Failed to write token to Redis blacklist; proceeding without persistence",
{
error: err instanceof Error ? err.message : String(err),
}
);
}
}
}
async isTokenBlacklisted(token: string): Promise<boolean> {
try {
const result = await this.redis.get(`blacklist:${token}`);
return result !== null;
} catch (err) {
// If Redis is unavailable, treat as not blacklisted to avoid blocking auth
this.logger.warn("Redis unavailable during blacklist check; allowing request", {
error: err instanceof Error ? err.message : String(err),
});
return false;
}
}
private parseJwtExpiry(expiresIn: string): number {
// Convert JWT expiry string to seconds
const unit = expiresIn.slice(-1);
const value = parseInt(expiresIn.slice(0, -1), 10);
switch (unit) {
2025-08-21 15:24:40 +09:00
case "s":
return value;
case "m":
return value * 60;
case "h":
return value * 60 * 60;
case "d":
return value * 24 * 60 * 60;
default:
return 7 * 24 * 60 * 60; // Default 7 days in seconds
}
}
}