Repository navigation
feat(auth): /api/auth/me/permissions endpoint + wire frontend canAccess (lift non-admin UI lockout) #917
Description
Summary
After the group-only authorization migration (#907 via #912 backend + #914 frontend), the frontend canAccess() in frontend/src/permissions.ts is a stub that ignores its action/resource arguments and returns isAdmin() for every gate:
export function canAccess(action: string, resource: string): boolean {
void action; void resource;
// ... until /me/permissions exists, only admins pass
return isAdmin();
}The backend now computes real per-group effective permissions (union of the user's groups' permission sets), but the frontend has no way to read them, so every non-admin user is locked out of the entire gated UI — a Standard Users member who could previously view/create/execute now sees all gated buttons and sections hidden.
This is safe (the backend still enforces authorization, so it fails closed), but it is a real functional/UX regression: the group model is effectively admin-or-nothing in the UI until this endpoint lands.
Surfaced by the post-merge security review of #912/#914 (2026-06-02).
Scope
Backend: add a public-to-the-authenticated-user endpoint, e.g. GET /api/auth/me/permissions, returning the current user's effective permission set (the {action, resource} pairs from the union of their groups), derived from the same GetUserPermissions/ComputeEffectivePermissions path the server uses for enforcement. No role anything.
Frontend: fetch that set on login/bootstrap, cache it on the current-user state, and make canAccess(action, resource) consult it (admins keep the {admin, *} wildcard). Remove the interim return isAdmin() stub and the void action; void resource;.
Acceptance
- A
Standard Usersmember sees exactly the actions their groups grant (not empty, not admin). - Admins unchanged (wildcard).
- The endpoint and the FE gate derive from the identical permission source the backend enforces with (no drift).
- Tests: BE endpoint returns the union for a multi-group user; FE
canAccesstests updated off the "non-admins always false" pin.
Context
Backend permission source: internal/auth/service_group.go (GetUserPermissions), ComputeEffectivePermissions. Frontend gate: frontend/src/permissions.ts (canAccess, isAdmin).
Activity
feat(auth): GET /api/auth/me/permissions + wire frontend canAccess (#922
fix(test): use groups not legacy role in recommendations-lookback moc…