Files
gridpilot.gg/apps/api/src/domain/auth/AuthorizationService.ts

76 lines
1.7 KiB
TypeScript

import { Injectable } from '@nestjs/common';
export type SystemRole = string;
@Injectable()
export class AuthorizationService {
private cache:
| {
rolesByUserId: Readonly<Record<string, readonly SystemRole[]>>;
expiresAtMs: number;
}
| null = null;
getRolesForUser(userId: string): readonly SystemRole[] {
const now = Date.now();
if (this.cache && now < this.cache.expiresAtMs) {
return this.cache.rolesByUserId[userId] ?? [];
}
const cacheMs = parseCacheMs(process.env.GRIDPILOT_AUTHZ_CACHE_MS);
const rolesByUserId = parseUserRolesJson(process.env.GRIDPILOT_USER_ROLES_JSON);
this.cache = {
rolesByUserId,
expiresAtMs: now + cacheMs,
};
return rolesByUserId[userId] ?? [];
}
}
function parseCacheMs(raw: string | undefined): number {
if (!raw) {
return 5_000;
}
const parsed = Number(raw);
if (!Number.isFinite(parsed) || parsed < 0) {
return 5_000;
}
return parsed;
}
function parseUserRolesJson(raw: string | undefined): Readonly<Record<string, readonly SystemRole[]>> {
if (!raw) {
return {};
}
try {
const parsed = JSON.parse(raw) as unknown;
if (!parsed || typeof parsed !== 'object') {
return {};
}
const record = parsed as Record<string, unknown>;
const normalized: Record<string, readonly SystemRole[]> = {};
for (const [userId, roles] of Object.entries(record)) {
if (!userId || !Array.isArray(roles)) {
continue;
}
const cleaned = roles
.filter((v) => typeof v === 'string')
.map((v) => v.trim())
.filter(Boolean);
if (cleaned.length > 0) {
normalized[userId] = cleaned;
}
}
return normalized;
} catch {
return {};
}
}