feat: add SessionsModule with service for session management

Implemented SessionsModule and SessionsService to manage user sessions. Includes methods for session creation, refresh token rotation, and session revocation. Integrated with database and CryptoService for secure token handling.
This commit is contained in:
Mathis HERRIOT
2026-01-08 15:27:02 +01:00
parent 9963046e41
commit a0836c8392
2 changed files with 99 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import { Module } from "@nestjs/common";
import { CryptoModule } from "../crypto/crypto.module";
import { DatabaseModule } from "../database/database.module";
import { SessionsService } from "./sessions.service";
@Module({
imports: [DatabaseModule, CryptoModule],
providers: [SessionsService],
exports: [SessionsService],
})
export class SessionsModule {}

View File

@@ -0,0 +1,88 @@
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { and, eq } from "drizzle-orm";
import { CryptoService } from "../crypto/crypto.service";
import { DatabaseService } from "../database/database.service";
import { sessions } from "../database/schemas";
@Injectable()
export class SessionsService {
constructor(
private readonly databaseService: DatabaseService,
private readonly cryptoService: CryptoService,
) {}
async createSession(userId: string, userAgent?: string, ip?: string) {
const refreshToken = await this.cryptoService.generateJwt(
{ sub: userId, type: "refresh" },
"7d",
);
const ipHash = ip ? await this.cryptoService.hashIp(ip) : null;
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
const [session] = await this.databaseService.db
.insert(sessions)
.values({
userId,
refreshToken,
userAgent,
ipHash,
expiresAt,
})
.returning();
return session;
}
async refreshSession(oldRefreshToken: string) {
const session = await this.databaseService.db
.select()
.from(sessions)
.where(
and(eq(sessions.refreshToken, oldRefreshToken), eq(sessions.isValid, true)),
)
.limit(1)
.then((res) => res[0]);
if (!session || session.expiresAt < new Date()) {
if (session) {
await this.revokeSession(session.id);
}
throw new UnauthorizedException("Invalid refresh token");
}
// Rotation du refresh token
const newRefreshToken = await this.cryptoService.generateJwt(
{ sub: session.userId, type: "refresh" },
"7d",
);
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
const [updatedSession] = await this.databaseService.db
.update(sessions)
.set({
refreshToken: newRefreshToken,
expiresAt,
updatedAt: new Date(),
})
.where(eq(sessions.id, session.id))
.returning();
return updatedSession;
}
async revokeSession(sessionId: string) {
await this.databaseService.db
.update(sessions)
.set({ isValid: false, updatedAt: new Date() })
.where(eq(sessions.id, sessionId));
}
async revokeAllUserSessions(userId: string) {
await this.databaseService.db
.update(sessions)
.set({ isValid: false, updatedAt: new Date() })
.where(eq(sessions.userId, userId));
}
}