Files
RenewCMS/web/src/lib/permissions.ts
T

103 lines
2.3 KiB
TypeScript

export type Scope = 'none' | 'own' | 'all'
export const SCOPE_RANK: Record<Scope, number> = { none: 0, own: 1, all: 2 }
export function isScope(value: unknown): value is Scope {
return typeof value === 'string' && Object.keys(SCOPE_RANK).includes(value)
}
export interface Grant {
key: string
scope: Scope
}
export interface RoleRef {
id: number
name: string
built_in: boolean
locked: boolean
}
export interface AssignedBy {
id: number
name: string
}
export interface Access {
role: RoleRef
permissions: Grant[]
roleAssignedBy?: AssignedBy | null
}
export function parseRoleAssignedBy(value: unknown): AssignedBy | null {
if (typeof value !== 'object' || value === null) {
return null
}
const { id, name } = value as { id?: unknown; name?: unknown }
return typeof id === 'number' && typeof name === 'string' && name !== '' ? { id, name } : null
}
export interface CanOptions {
ownerId?: number
typeKey?: string
scope?: 'own' | 'all'
}
export interface ScopeOptions {
typeKey?: string
}
const TYPE_SEPARATOR = ':'
export function formatGrantKey(key: string, typeKey?: string) {
return typeKey === undefined ? key : `${key}${TYPE_SEPARATOR}${typeKey}`
}
export function parseGrantKey(key: string): { base: string; typeKey?: string } {
const at = key.indexOf(TYPE_SEPARATOR)
if (at === -1) {
return { base: key }
}
return { base: key.slice(0, at), typeKey: key.slice(at + TYPE_SEPARATOR.length) }
}
function scopeOfGrant(grants: readonly Grant[], key: string): Scope | undefined {
return grants.find((grant) => grant.key === key)?.scope
}
export function effectiveScope(
grants: readonly Grant[],
key: string,
options: ScopeOptions = {},
): Scope {
if (options.typeKey) {
const typed = scopeOfGrant(grants, formatGrantKey(key, options.typeKey))
if (typed !== undefined) {
return typed
}
}
return scopeOfGrant(grants, key) ?? 'none'
}
export function allows(
grants: readonly Grant[],
userId: number | undefined,
key: string,
options: CanOptions = {},
): boolean {
const scope = effectiveScope(grants, key, options)
if (scope === 'none') {
return false
}
if (scope === 'all') {
return true
}
if (options.scope === 'all') {
return false
}
if (options.ownerId === undefined) {
return true
}
return userId !== undefined && options.ownerId !== 0 && options.ownerId === userId
}