66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { HttpClient } from '@angular/common/http';
|
|
import { Observable, of } from 'rxjs';
|
|
import { catchError, map, tap } from 'rxjs/operators';
|
|
import { AuthService, UserInfo } from '../../shared/services/auth.service';
|
|
import { ApiConfigService } from './api-config.service';
|
|
import { PermissionAction } from '../models/permission.model';
|
|
|
|
const SUPER_ADMIN = 'super_admin';
|
|
|
|
/**
|
|
* Reads the current user's effective permissions (delivered on the UserInfo payload)
|
|
* and answers can(module, action). super_admin always passes. This is a UX mirror —
|
|
* the backend remains the authoritative permission boundary.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class PermissionService {
|
|
constructor(
|
|
private auth: AuthService,
|
|
private http: HttpClient,
|
|
private apiConfig: ApiConfigService
|
|
) { }
|
|
|
|
/** True if the current user may perform <action> on <module>. */
|
|
can(module: string, action: PermissionAction): boolean {
|
|
const user = this.auth.getCurrentUser();
|
|
if (!user) {
|
|
return false;
|
|
}
|
|
if (user.roles?.includes(SUPER_ADMIN)) {
|
|
return true;
|
|
}
|
|
const moduleActions = user.permissions?.[this.normalizeKey(module)];
|
|
return !!moduleActions && !!moduleActions[action];
|
|
}
|
|
|
|
canRead(module: string): boolean { return this.can(module, 'read'); }
|
|
canWrite(module: string): boolean { return this.can(module, 'write'); }
|
|
canDelete(module: string): boolean { return this.can(module, 'delete'); }
|
|
canApprove(module: string): boolean { return this.can(module, 'approve'); }
|
|
|
|
/**
|
|
* Re-fetches the current user (with fresh permissions) from GET /api/auth/me.
|
|
* Call after an admin edits the matrix so the UI reflects the change without
|
|
* a re-login. Returns the updated user, or null on failure.
|
|
*/
|
|
refresh(): Observable<UserInfo | null> {
|
|
return this.http.get<UserInfo>(`${this.apiConfig.authUrl}/me`).pipe(
|
|
tap(user => this.auth.setCurrentUser(user)),
|
|
map(user => user),
|
|
catchError(() => of(null))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Module names are stored PascalCase in code but arrive as camelCase dictionary
|
|
* keys (server's DictionaryKeyPolicy). Lowercase the first character to match.
|
|
*/
|
|
private normalizeKey(module: string): string {
|
|
if (!module) {
|
|
return module;
|
|
}
|
|
return module.charAt(0).toLowerCase() + module.slice(1);
|
|
}
|
|
}
|