@katalysttech/auth
Version:
A flexible authentication module for NestJS applications with JWT and refresh token support
188 lines • 7.39 kB
JavaScript
"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