UNPKG

@theoptimalpartner/jwt-auth-validator

Version:

JWT token validation package with offline JWKS validation and Redis-based token revocation support

137 lines 4.58 kB
import jwt from 'jsonwebtoken'; export class TokenBlacklistService { redisService = null; initialized = false; constructor(redisService) { if (redisService) { this.redisService = redisService; } } initialize(redisService) { if (redisService) { this.redisService = redisService; } if (!this.redisService) { throw new Error('Redis service is required for token blacklist'); } this.initialized = true; } async addToBlacklist(token, expiresAt) { if (!this.initialized || !this.redisService) { throw new Error('TokenBlacklistService not initialized'); } try { const currentTime = Math.floor(Date.now() / 1000); const ttl = Math.max(0, expiresAt - currentTime); if (ttl > 0) { const tokenHash = this.getTokenHash(token); await this.redisService.saveRevokedToken(tokenHash, ttl); } } catch (error) { console.error('Error adding token to blacklist:', error); throw error; } } async isBlacklisted(token) { if (!this.initialized || !this.redisService) { return false; } try { const tokenHash = this.getTokenHash(token); return await this.redisService.isTokenRevoked(tokenHash); } catch (error) { console.error('Error checking blacklist:', error); return false; } } getTokenId(token) { try { const decoded = jwt.decode(token); if (decoded?.jti) { return decoded.jti; } if (decoded?.sub && decoded.iat) { return `${decoded.sub}:${decoded.iat}`; } return this.getTokenHash(token); } catch (error) { console.error('Error getting token ID:', error); return this.getTokenHash(token); } } getTokenHash(token) { const tokenSuffix = token.slice(-32); return Buffer.from(tokenSuffix).toString('base64'); } async invalidateUserTokens(userId, tokens) { if (!this.initialized || !this.redisService) { throw new Error('TokenBlacklistService not initialized'); } try { const promises = tokens.map(async (token) => { try { const decoded = jwt.decode(token); if (decoded?.exp) { await this.addToBlacklist(token, decoded.exp); } } catch (error) { console.error(`Error invalidating token for user ${userId}:`, error); } }); await Promise.all(promises); } catch (error) { console.error('Error invalidating user tokens:', error); throw error; } } async addMultipleToBlacklist(tokens) { if (!this.initialized || !this.redisService) { throw new Error('TokenBlacklistService not initialized'); } try { const promises = tokens.map(({ token, expiresAt }) => this.addToBlacklist(token, expiresAt)); await Promise.all(promises); } catch (error) { console.error('Error adding multiple tokens to blacklist:', error); throw error; } } async checkMultipleTokens(tokens) { if (!this.initialized || !this.redisService) { return tokens.map(() => false); } try { const promises = tokens.map((token) => this.isBlacklisted(token)); return await Promise.all(promises); } catch (error) { console.error('Error checking multiple tokens:', error); return tokens.map(() => false); } } async getStats() { try { const connectionStatus = this.redisService?.isConnected ? 'connected' : 'disconnected'; return { service: 'Redis', connectionStatus, initialized: this.initialized, }; } catch (error) { return { service: 'Redis', connectionStatus: 'error', error: error instanceof Error ? error.message : String(error), initialized: this.initialized, }; } } } //# sourceMappingURL=token-blacklist-service.js.map