mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 09:00:57 +00:00
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException } from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { HttpService } from '@nestjs/axios';
|
|
import { firstValueFrom } from 'rxjs';
|
|
|
|
/**
|
|
* IAM Adapter for @tria-plc corporate identity integration
|
|
*
|
|
* This adapter wraps the corporate IAM guards and provides a bridge
|
|
* between the corporate identity system and the EDR passenger API.
|
|
*
|
|
* For back-office roles (agent, supervisor, admin, staff), this guard
|
|
* validates tokens against the corporate IAM service.
|
|
*
|
|
* For passenger-facing routes, the existing JWT guard is used.
|
|
*/
|
|
|
|
export interface IamTokenPayload {
|
|
sub: string;
|
|
email: string;
|
|
roles: string[];
|
|
permissions: string[];
|
|
organizationId?: string;
|
|
exp: number;
|
|
iat: number;
|
|
}
|
|
|
|
export interface IamValidationResponse {
|
|
valid: boolean;
|
|
payload?: IamTokenPayload;
|
|
error?: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class IamGuard implements CanActivate {
|
|
private readonly iamApiUrl: string;
|
|
private readonly iamEnabled: boolean;
|
|
|
|
constructor(
|
|
private readonly reflector: Reflector,
|
|
private readonly config: ConfigService,
|
|
private readonly http: HttpService,
|
|
) {
|
|
this.iamApiUrl = this.config.get<string>('IAM_API_URL') || 'https://iam.tria-plc.com/api';
|
|
this.iamEnabled = this.config.get<string>('IAM_ENABLED') === 'true';
|
|
}
|
|
|
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
if (!this.iamEnabled) {
|
|
// IAM disabled - allow access (for development)
|
|
return true;
|
|
}
|
|
|
|
const request = context.switchToHttp().getRequest();
|
|
const token = this.extractToken(request);
|
|
|
|
if (!token) {
|
|
throw new UnauthorizedException('No authentication token provided');
|
|
}
|
|
|
|
const validation = await this.validateToken(token);
|
|
|
|
if (!validation.valid || !validation.payload) {
|
|
throw new UnauthorizedException(validation.error || 'Invalid token');
|
|
}
|
|
|
|
// Check required roles
|
|
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
|
|
if (requiredRoles && requiredRoles.length > 0) {
|
|
const hasRole = requiredRoles.some((role) => validation.payload!.roles.includes(role));
|
|
if (!hasRole) {
|
|
throw new ForbiddenException('Insufficient permissions');
|
|
}
|
|
}
|
|
|
|
// Attach user to request
|
|
request.user = {
|
|
userId: validation.payload.sub,
|
|
email: validation.payload.email,
|
|
roles: validation.payload.roles,
|
|
permissions: validation.payload.permissions,
|
|
organizationId: validation.payload.organizationId,
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
private extractToken(request: any): string | null {
|
|
const authHeader = request.headers.authorization;
|
|
if (!authHeader) return null;
|
|
|
|
const parts = authHeader.split(' ');
|
|
if (parts.length !== 2 || parts[0] !== 'Bearer') return null;
|
|
|
|
return parts[1];
|
|
}
|
|
|
|
private async validateToken(token: string): Promise<IamValidationResponse> {
|
|
try {
|
|
const response = await firstValueFrom(
|
|
this.http.post<IamValidationResponse>(
|
|
`${this.iamApiUrl}/v1/auth/validate`,
|
|
{ token },
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-API-Key': this.config.get<string>('IAM_API_KEY') || '',
|
|
},
|
|
timeout: 5000,
|
|
},
|
|
),
|
|
);
|
|
|
|
return response.data;
|
|
} catch (err) {
|
|
return {
|
|
valid: false,
|
|
error: err instanceof Error ? err.message : 'Token validation failed',
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decorator to mark routes as requiring IAM authentication
|
|
*/
|
|
export const UseIamAuth = () => {
|
|
// This is a marker decorator that can be used with @UseGuards(IamGuard)
|
|
return (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => {
|
|
// Marker only - actual guard is applied via @UseGuards
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Decorator to specify required roles for IAM-protected routes
|
|
*/
|
|
export const IamRoles = (...roles: string[]) => {
|
|
return (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => {
|
|
if (descriptor) {
|
|
Reflect.defineMetadata('roles', roles, descriptor.value);
|
|
}
|
|
};
|
|
};
|