Require at least one capability to see/access apps that define them
An app-level grant (user_app_perms/role_app_perms) alone used to be enough to show an app's portal tile and pass AuthGate, even with zero capabilities held. Most apps only gate specific routes/buttons with capabilities, not their dashboard/shell, so a role with the app toggled on but no caps granted (e.g. wages, utilities, hvac, plant, tablet-checkin — none of which auto-grant Staff caps) still saw the full app. Apps with no capabilities defined at all keep prior behaviour.
This commit is contained in:
parent
f756a600b1
commit
97a0b50c92
1 changed files with 41 additions and 8 deletions
|
|
@ -79,6 +79,31 @@ export function cookieOpts(request, clear = false) {
|
|||
}
|
||||
}
|
||||
|
||||
// An app-level grant (user_app_perms / role_app_perms) alone used to be enough
|
||||
// to show an app's tile and let AuthGate in — but most apps only enforce their
|
||||
// granular capabilities on specific routes/buttons, not on the dashboard/shell
|
||||
// itself. That meant a role with the app toggled on and zero capabilities still
|
||||
// saw the full app. For any app that defines capabilities at all, require the
|
||||
// user to hold at least one of them; apps with no capabilities defined yet keep
|
||||
// the old app-perm-only behaviour unchanged.
|
||||
function CAP_GATE_CLAUSE(appIdExpr, userIdParam) {
|
||||
return `(
|
||||
NOT EXISTS (SELECT 1 FROM app_capabilities ac WHERE ac.app_id = ${appIdExpr})
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM app_capabilities ac
|
||||
WHERE ac.app_id = ${appIdExpr}
|
||||
AND (
|
||||
EXISTS (SELECT 1 FROM user_capabilities uc WHERE uc.user_id = ${userIdParam} AND uc.capability_id = ac.id)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM user_roles ur2
|
||||
JOIN role_capabilities rc ON rc.role_id = ur2.role_id
|
||||
WHERE ur2.user_id = ${userIdParam} AND rc.capability_id = ac.id
|
||||
)
|
||||
)
|
||||
)
|
||||
)`
|
||||
}
|
||||
|
||||
// Resolve a user's granular capabilities as "<app_slug>:<cap_slug>" strings.
|
||||
// Admins implicitly hold every capability of every active app; everyone else
|
||||
// gets the union of capabilities granted via their roles and direct grants.
|
||||
|
|
@ -136,6 +161,7 @@ export async function getUserWithApps(userId) {
|
|||
WHERE ur.user_id = $1 AND rap.app_id = a.id
|
||||
)
|
||||
)
|
||||
AND ${CAP_GATE_CLAUSE('a.id', '$1')}
|
||||
ORDER BY a.category NULLS FIRST, a.name`,
|
||||
[userId]
|
||||
)
|
||||
|
|
@ -218,6 +244,7 @@ export async function authRoutes(app) {
|
|||
const { app: appSlug } = request.query
|
||||
|
||||
// Live DB check: user must be active and (if app requested) have permission
|
||||
// AND (for apps that define capabilities) hold at least one of them.
|
||||
const { rows: [user] } = await pool.query(
|
||||
`SELECT u.id, u.email, u.name, u.is_admin, u.offsite_allowed
|
||||
FROM users u
|
||||
|
|
@ -225,13 +252,18 @@ export async function authRoutes(app) {
|
|||
AND (
|
||||
$2::text IS NULL
|
||||
OR u.is_admin = true
|
||||
OR EXISTS (SELECT 1 FROM user_app_perms p
|
||||
JOIN apps a ON a.id = p.app_id
|
||||
WHERE p.user_id = u.id AND a.slug = $2 AND a.active = true)
|
||||
OR EXISTS (SELECT 1 FROM user_roles ur
|
||||
JOIN role_app_perms rap ON rap.role_id = ur.role_id
|
||||
JOIN apps a ON a.id = rap.app_id
|
||||
WHERE ur.user_id = u.id AND a.slug = $2 AND a.active = true)
|
||||
OR (
|
||||
(
|
||||
EXISTS (SELECT 1 FROM user_app_perms p
|
||||
JOIN apps a ON a.id = p.app_id
|
||||
WHERE p.user_id = u.id AND a.slug = $2 AND a.active = true)
|
||||
OR EXISTS (SELECT 1 FROM user_roles ur
|
||||
JOIN role_app_perms rap ON rap.role_id = ur.role_id
|
||||
JOIN apps a ON a.id = rap.app_id
|
||||
WHERE ur.user_id = u.id AND a.slug = $2 AND a.active = true)
|
||||
)
|
||||
AND ${CAP_GATE_CLAUSE('(SELECT id FROM apps WHERE slug = $2)', 'u.id')}
|
||||
)
|
||||
)`,
|
||||
[payload.user_id, appSlug || null]
|
||||
)
|
||||
|
|
@ -282,7 +314,8 @@ export async function authRoutes(app) {
|
|||
JOIN role_app_perms rap ON rap.role_id = ur.role_id
|
||||
WHERE ur.user_id = $1 AND rap.app_id = a.id
|
||||
)
|
||||
)`,
|
||||
)
|
||||
AND ${CAP_GATE_CLAUSE('a.id', '$1')}`,
|
||||
[user.id]
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue