quality / commitlint (push) Skipped
quality / changes (push) Failing after 8s
quality / openapi (push) Skipped
quality / web (push) Skipped
quality / api (push) Skipped
CD / quality (push) Failing after 9s
quality / docker-check (push) Skipped
CD / publish (push) Skipped
- genReqId (uuid) + x-request-id в каждом ответе и request_id в error envelope — корреляция ошибок между клиентом и логами - RBAC: /agents/:id/(stats|blocked-ips|blocked-ports) классифицируются как fw:stats:read (reset остаётся под fw:agents:write) - web: test-скрипт + 10 unit-тестов (filter-utils, fleet-kpis, parseClaims, nav) - typecheck-скрипты для api/shared/db; CI: тесты shared и web, typecheck всех пакетов - гигиена: .node-version (22), актуальный .dockerignore, drizzle out → ./migrations, удалены 12 лишних .gitkeep и пустой apps/api/test
74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
export type PermissionAction = 'read' | 'write' | 'admin'
|
|
|
|
/** Hierarchy: admin ⊃ write ⊃ read within the same section. */
|
|
export function hasPermission(
|
|
granted: readonly string[],
|
|
required: string,
|
|
): boolean {
|
|
if (granted.includes(required)) return true
|
|
const parts = required.split(':')
|
|
if (parts.length !== 3) return false
|
|
const [app, section, action] = parts
|
|
if (action === 'read') {
|
|
return (
|
|
granted.includes(`${app}:${section}:write`) ||
|
|
granted.includes(`${app}:${section}:admin`)
|
|
)
|
|
}
|
|
if (action === 'write') {
|
|
return granted.includes(`${app}:${section}:admin`)
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function permissionForRequest(
|
|
method: string,
|
|
url: string,
|
|
): string | null {
|
|
const path = url.split('?')[0] ?? url
|
|
const m = method.toUpperCase()
|
|
const write = m !== 'GET' && m !== 'HEAD' && m !== 'OPTIONS'
|
|
|
|
// Agent statistics live under /agents/:id/… — classify as stats, not agents.
|
|
if (
|
|
/^\/api\/v1\/agents\/[^/]+\/(stats|blocked-ips|blocked-ports)$/.test(path)
|
|
) {
|
|
return 'fw:stats:read'
|
|
}
|
|
if (path.startsWith('/api/v1/agents') || path.startsWith('/api/v1/install-links')) {
|
|
return write ? 'fw:agents:write' : 'fw:agents:read'
|
|
}
|
|
if (path.startsWith('/api/v1/lists')) {
|
|
return write ? 'fw:lists:write' : 'fw:lists:read'
|
|
}
|
|
if (
|
|
path.startsWith('/api/v1/rules') ||
|
|
path.startsWith('/api/v1/policies') ||
|
|
path.startsWith('/api/v1/policy-sets')
|
|
) {
|
|
return write ? 'fw:policies:write' : 'fw:policies:read'
|
|
}
|
|
if (path.startsWith('/api/v1/stats') || path.startsWith('/api/v1/dashboard')) {
|
|
return 'fw:stats:read'
|
|
}
|
|
if (path.startsWith('/api/v1/integrations')) {
|
|
return write ? 'fw:settings:admin' : 'fw:lists:read'
|
|
}
|
|
if (path.startsWith('/api/v1/settings') || path.startsWith('/api/v1/install-context')) {
|
|
return write ? 'fw:settings:admin' : 'fw:settings:read'
|
|
}
|
|
if (path.startsWith('/api/v1/audit')) {
|
|
return 'fw:audit:read'
|
|
}
|
|
return null
|
|
}
|
|
|
|
export type AuthUser = {
|
|
id: string
|
|
email: string
|
|
name: string
|
|
apps: string[]
|
|
permissions: string[]
|
|
isAdmin: boolean
|
|
}
|