UNPKG

@katalysttech/auth

Version:

A flexible authentication module for NestJS applications with JWT and refresh token support

188 lines 7.39 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RefreshTokenService = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("typeorm"); let TypeORM; let Redis; try { const { DataSource, Repository } = require('typeorm'); TypeORM = { DataSource, Repository }; } catch { } try { const { Redis: RedisClient } = require('ioredis'); Redis = RedisClient; } catch { } let RefreshTokenService = class RefreshTokenService { constructor(options) { this.options = options; this.repository = null; this.redisClient = null; this.initializeStorage(); } async initializeStorage() { const { type, entity, dataSource, redis } = this.options.refreshToken.storage; if (type === 'typeorm') { if (!TypeORM) { throw new Error('TypeORM is not installed. Please install typeorm package to use TypeORM storage.'); } if (!entity) { throw new Error('Entity is required for TypeORM storage.'); } if (!dataSource) { throw new Error('DataSource is required for TypeORM storage. Please provide it in the refreshToken.storage.dataSource config.'); } this.repository = dataSource.getRepository(entity); } else if (type === 'redis') { if (!Redis) { throw new Error('Redis is not installed. Please install ioredis package to use Redis storage.'); } if (!redis) { throw new Error('Redis configuration is required for Redis storage.'); } this.redisClient = new Redis(redis); } } async storeToken(userId, tokenId, expiresAt, deviceInfo) { if (this.options.refreshToken.maxActiveSessions) { const activeSessions = await this.getActiveSessions(userId); if (activeSessions.length >= this.options.refreshToken.maxActiveSessions) { const oldestSession = activeSessions[activeSessions.length - 1]; await this.revokeToken(userId, oldestSession.tokenId); } } if (this.repository) { await this.repository.save({ userId, tokenId, expiresAt, deviceInfo, isRevoked: false, }); } else if (this.redisClient) { const key = `refresh_token:${tokenId}`; const expiresIn = Math.floor((expiresAt.getTime() - Date.now()) / 1000); await this.redisClient.set(key, JSON.stringify({ userId, deviceInfo, expiresAt }), 'EX', expiresIn); } } async validateToken(userId, tokenId) { if (this.repository) { const token = await this.repository.findOne({ where: { userId, tokenId, isRevoked: false, }, }); if (!token) return false; const isValid = new Date(token.expiresAt) > new Date(); if (!isValid) { await this.repository.remove(token); } return isValid; } else if (this.redisClient) { const key = `refresh_token:${tokenId}`; const data = await this.redisClient.get(key); if (!data) return false; const { userId: storedUserId, expiresAt } = JSON.parse(data); return storedUserId === userId && new Date(expiresAt) > new Date(); } return false; } async revokeToken(userId, tokenId) { if (this.repository) { await this.repository.update({ userId, tokenId }, { isRevoked: true }); } else if (this.redisClient) { const key = `refresh_token:${tokenId}`; await this.redisClient.del(key); } } async revokeAllUserTokens(userId) { if (this.repository) { await this.repository.update({ userId }, { isRevoked: true }); } else if (this.redisClient) { const pattern = `refresh_token:*`; const keys = await this.redisClient.keys(pattern); for (const key of keys) { const data = await this.redisClient.get(key); if (data) { const { userId: storedUserId } = JSON.parse(data); if (storedUserId === userId) { await this.redisClient.del(key); } } } } } async cleanupExpiredTokens() { if (this.repository) { await this.repository.createQueryBuilder() .delete() .where('expiresAt <= :now', { now: new Date() }) .execute(); } } async getActiveSessions(userId) { if (this.repository) { return await this.repository.find({ where: { userId, isRevoked: false, expiresAt: (0, typeorm_1.MoreThan)(new Date()), }, order: { createdAt: 'DESC', }, }); } else if (this.redisClient) { const pattern = `refresh_token:*`; const keys = await this.redisClient.keys(pattern); const activeSessions = []; for (const key of keys) { const data = await this.redisClient.get(key); if (data) { const session = JSON.parse(data); if (session.userId === userId && new Date(session.expiresAt) > new Date()) { activeSessions.push({ ...session, tokenId: key.split(':')[1], }); } } } return activeSessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); } return []; } }; exports.RefreshTokenService = RefreshTokenService; exports.RefreshTokenService = RefreshTokenService = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_1.Inject)('AUTH_OPTIONS')), __metadata("design:paramtypes", [Object]) ], RefreshTokenService); //# sourceMappingURL=refresh-token.service.js.map