Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions layers/auth/server/services/casl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,15 @@ async function evaluateAbilityString(
return { allowed: session.abilities.includes(abilityStr) }
}

if (!caslAbility.can(action, subjectName)) {
return { allowed: false }
if (scope !== 'self') {
// Check with an empty instance so conditional (`:self`) rules don't accidentally
// satisfy an unscoped gate — conditions like { user_id } won't match {}.
return { allowed: caslAbility.can(action, subject(subjectName, {})) }
}

if (scope !== 'self') {
return { allowed: true }
// :self — quick type-level bail before the DB fetch
if (!caslAbility.can(action, subjectName)) {
return { allowed: false }
}

// :self — fetch the resource and let CASL verify the user_id condition
Expand All @@ -85,7 +88,13 @@ async function evaluateAbilityString(
return { allowed: false }
}

if (!caslAbility.can(action, subject(subjectName, resource as Record<string, unknown>))) {
// Ownership check: user owns the resource OR has unconditional manage.
// We cannot rely on caslAbility.can(action, instance) here because an unscoped
// ability (e.g. `project:write` for CREATE) would satisfy the conditional check,
// letting any member bypass the ownership gate.
const owns = resource.user_id === session.id
const hasManage = caslAbility.can('manage', subject(subjectName, {}))
if (!owns && !hasManage) {
return { allowed: false }
}

Expand Down
3 changes: 3 additions & 0 deletions test/__mocks__/hub-db-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export const organizationCreditTable = {} as any
export const transactionTable = {} as any
export const roleTable = {} as any
export const organizationMemberRoleTable = {} as any
export const TICKET_KINDS = ['feedback', 'support'] as const
export const TICKET_CATEGORIES = ['account', 'billing', 'technical', 'other'] as const
export const TICKET_STATUSES = ['open', 'active', 'closed'] as const
export const supportTicketTable = {} as any
export const supportTicketMessageTable = {} as any
export const projectTable = {} as any
Expand Down
Loading