import { Injectable } from '@nestjs/common'; export type SystemRole = string; @Injectable() export class AuthorizationService { private cache: | { rolesByUserId: Readonly>; 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> { if (!raw) { return {}; } try { const parsed = JSON.parse(raw) as unknown; if (!parsed || typeof parsed !== 'object') { return {}; } const record = parsed as Record; const normalized: Record = {}; 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 {}; } }