Add role control

This commit is contained in:
Chris Chen
2026-06-23 07:19:08 -07:00
parent deff2264a6
commit 870eeec82a
45 changed files with 1923 additions and 165 deletions
@@ -0,0 +1,65 @@
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);
}
}