HR-ATS-Portal/frontend/src/auth/permissions.js

77 lines
3.2 KiB
JavaScript

/* ============================================================
permissions.js — the 104-tag vocabulary, mirrored from the backend.
IMPORTANT — WHAT THIS DOES AND DOES NOT DO
------------------------------------------
Everything here is COSMETIC: it hides nav items and blocks routes in the UI.
Real enforcement is `require_permission` on the server, and today that only
guards /users/*, /roles/* and /permissions/*. The other 20 screens are seed
data with no server behind them, and /inbox/fetch has no auth dependency at
all. A ticked box in the RBAC matrix is not an access control.
Source of truth: backend/users/permissions.py PermissionTag, which asserts at
startup that the enum equals the full modules x actions cross-product.
============================================================ */
export const MODULES = [
'dashboard', 'inbox', 'jobs', 'candidates', 'pipeline', 'interviews', 'assessments',
'offers', 'reports', 'analytics', 'job_board', 'settings', 'rbac_users', 'tasks',
'talent', 'requisitions', 'department',
]
export const ACTIONS = [
'view', 'create', 'edit', 'delete', 'approve', 'export', 'manage', 'configure',
]
/** All `module.action` tags (modules x actions cross-product). */
export const ALL_TAGS = MODULES.flatMap((m) => ACTIONS.map((a) => `${m}.${a}`))
/**
* Build a permission predicate. A null/undefined tag is always allowed — routes
* like Tasks and Help have no backend module and are open to any signed-in user.
*/
export function makeCan(permissions) {
const set = new Set(permissions ?? [])
return (tag) => !tag || set.has(tag)
}
export const HIRING_MANAGER_ROLE = 'hiring_manager'
/** Sidebar paths a manager-type role may see. CV Bank / Matching / Import
also sit on candidates.view/create, so they are excluded here. */
export const HIRING_MANAGER_NAV = new Set([
'candidates', 'requisitions', 'interviews', 'calendar',
'help', 'aiassistant', 'aistudio', 'notifications',
])
export function isHiringManager(user) {
const name = (user?.role_name || '').trim().toLowerCase()
return name === HIRING_MANAGER_ROLE || name === 'manager'
}
const ADMIN_ROLES = new Set(['system_administrator', 'hr_administrator', 'admin'])
export function isAdmin(user) {
const name = (user?.role_name || '').trim().toLowerCase()
if (ADMIN_ROLES.has(name)) return true
return (user?.permissions || []).includes('requisitions.manage')
}
/** Unscoped Candidates list — matches backend sees_all_candidates. */
export function seesAllCandidates(user) {
if (isAdmin(user)) return true
return (user?.permissions || []).includes('candidates.manage')
}
/** Jobs/candidates limited to requisitions this user created (or is assigned).
Matches backend `scopes_to_own_requisitions`. Custom roles opt in from
Access Control by ticking Requisitions → Configure (`requisitions.configure`),
not Create. `requisitions.manage` already means admin. Do not expand
`isHiringManager` for this; that helper still locks the sidebar. */
export function scopesToOwnRequisitions(user) {
if (isHiringManager(user)) return true
if (isAdmin(user) || seesAllCandidates(user)) return false
return (user?.permissions || []).includes('requisitions.configure')
}