76 lines
1.7 KiB
TypeScript
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 {};
|
|
}
|
|
} |