-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaccess.js
More file actions
74 lines (66 loc) · 3.53 KB
/
Copy pathaccess.js
File metadata and controls
74 lines (66 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Role-based access control tied to the KSP rank hierarchy.
//
// Every signed-in user carries an app role (assigned by the admin on the
// Access & Audit page and stored server-side); the admin role itself comes
// from Catalyst's own "App Administrator" project role, so it can never be
// self-assigned. Each feature below declares which roles may open it — the
// sidebar hides what the router blocks, and blocked visits are audit-logged.
export const ROLE_LABELS = {
admin: 'Admin',
supervisor: 'Supervisor',
investigator: 'Investigator',
analyst: 'Analyst',
policymaker: 'Policymaker',
};
export const ASSIGNABLE_ROLES = ['investigator', 'analyst', 'supervisor', 'policymaker', 'admin'];
const ALL = ['admin', 'supervisor', 'investigator', 'analyst', 'policymaker'];
// Feature registry: route prefix → allowed roles. Longest prefix wins, and a
// path that matches nothing is open to every signed-in user.
export const FEATURES = [
{ key: 'reports', label: 'Home', path: '/reports', roles: ALL },
{ key: 'incidents', label: 'Incidents', path: '/incidents', roles: ['admin', 'supervisor', 'investigator'] },
{ key: 'crimeMap', label: 'Crime Map', path: '/crime-map', roles: ['admin', 'supervisor', 'investigator', 'analyst'] },
{ key: 'aiAnalytics', label: 'AI Analytics', path: '/ai-analytics', roles: ['admin', 'supervisor', 'analyst', 'policymaker'] },
{ key: 'caseFiles', label: 'Case Files', path: '/case-files', roles: ['admin', 'supervisor', 'investigator'] },
{ key: 'investigationDiary', label: 'Investigation Diary', path: '/investigation-diary', roles: ['admin', 'supervisor', 'investigator'] },
{ key: 'reportStudio', label: 'Report Studio', path: '/report-studio', roles: ['admin', 'supervisor', 'investigator'] },
{ key: 'custody', label: 'Inmate Registry', path: '/custody', roles: ['admin', 'supervisor', 'investigator', 'policymaker'] },
{ key: 'assistant', label: 'Assistant', path: '/assistant', roles: ALL },
{ key: 'personnel', label: 'Personnel', path: '/personnel', roles: ['admin', 'supervisor', 'policymaker'] },
{ key: 'dutyRoster', label: 'Duty Roster', path: '/personnel/roster', roles: ['admin', 'supervisor'] },
{ key: 'orgChart', label: 'Org Chart', path: '/personnel/org-chart', roles: ['admin', 'supervisor', 'policymaker'] },
{ key: 'profile', label: 'Profile', path: '/profile', roles: ALL },
{ key: 'help', label: 'Help Center', path: '/help', roles: ALL },
{ key: 'access', label: 'Access & Audit', path: '/access', roles: ['admin'] },
{ key: 'dashboard', label: 'Dashboard', path: '/dashboard', roles: ALL },
];
const byKey = Object.fromEntries(FEATURES.map((f) => [f.key, f]));
export function canAccess(role, featureKey) {
const f = byKey[featureKey];
if (!f) return true;
return f.roles.includes(role);
}
export function featureForPath(pathname) {
let best = null;
for (const f of FEATURES) {
if (pathname === f.path || pathname.startsWith(f.path + '/')) {
if (!best || f.path.length > best.path.length) best = f;
}
}
return best;
}
export async function fetchMyAccess(email) {
try {
const res = await fetch('/server/rag/access/me', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(8000),
});
const data = await res.json().catch(() => ({}));
if (res.ok && data.role) return { role: data.role };
} catch {}
// Fail open to the least-privileged field role so a cold function start
// never locks a user out of the whole app.
return { role: 'investigator' };
}