mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
refactor( iam ): migrate session, logout, and guards to IAM
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, UnauthorizedException } from '@nestjs/common';
|
||||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { PrismaService } from '../prisma.service';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
@@ -9,7 +10,7 @@ export class SessionActivityInterceptor implements NestInterceptor {
|
||||
private readonly inactivityMinutes: number;
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@InjectDataSource() private readonly dataSource: DataSource,
|
||||
private readonly config: ConfigService,
|
||||
) {
|
||||
this.inactivityMinutes = parseInt(this.config.get<string>('SESSION_INACTIVITY_MINUTES') || '30', 10);
|
||||
@@ -18,29 +19,25 @@ export class SessionActivityInterceptor implements NestInterceptor {
|
||||
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const response = context.switchToHttp().getResponse();
|
||||
const user = request.user;
|
||||
const sessionId: string | undefined = request.user?.sessionId;
|
||||
|
||||
if (user?.id) {
|
||||
const session = await this.prisma.session.findFirst({
|
||||
where: { userId: user.id },
|
||||
orderBy: { lastActivityAt: 'desc' },
|
||||
});
|
||||
if (sessionId) {
|
||||
const rows = await this.dataSource.query<Array<{ expiry_time: Date }>>(
|
||||
`SELECT expiry_time FROM iam.sessions WHERE id = $1 AND status = 'ACTIVE' LIMIT 1`,
|
||||
[sessionId],
|
||||
);
|
||||
|
||||
if (session) {
|
||||
const inactiveMinutes = (Date.now() - session.lastActivityAt.getTime()) / 60000;
|
||||
|
||||
if (inactiveMinutes > this.inactivityMinutes) {
|
||||
await this.prisma.session.delete({ where: { id: session.id } });
|
||||
throw new UnauthorizedException('Session expired due to inactivity');
|
||||
if (rows.length) {
|
||||
const minutesLeft = (rows[0].expiry_time.getTime() - Date.now()) / 60000;
|
||||
if (minutesLeft < this.inactivityMinutes * 0.2) {
|
||||
response.setHeader('X-Session-Expiry-Warning', Math.floor(minutesLeft).toString());
|
||||
}
|
||||
|
||||
const expiryWarningMinutes = Math.max(0, this.inactivityMinutes - inactiveMinutes);
|
||||
response.setHeader('X-Session-Expiry-Warning', Math.floor(expiryWarningMinutes).toString());
|
||||
|
||||
await this.prisma.session.update({
|
||||
where: { id: session.id },
|
||||
data: { lastActivityAt: new Date() },
|
||||
});
|
||||
// Extend session on every authenticated request
|
||||
await this.dataSource.query(
|
||||
`UPDATE iam.sessions SET expiry_time = NOW() + ($1 * INTERVAL '1 minute') WHERE id = $2 AND status = 'ACTIVE'`,
|
||||
[this.inactivityMinutes, sessionId],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { UserRole } from '@prisma/client';
|
||||
import { ROLES_KEY } from './roles.decorator';
|
||||
|
||||
@Injectable()
|
||||
@@ -8,12 +7,15 @@ export class RolesGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(ROLES_KEY, [
|
||||
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (!requiredRoles) return true;
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
return requiredRoles.some((role) => user?.role === role);
|
||||
// Support IAM roles array [{key, id}][] and legacy role string
|
||||
return requiredRoles.some(
|
||||
(role) => user?.roles?.some((r: { key: string }) => r.key === role) || user?.role === role,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user