Files
peardock/server/core/acl.js
T
Raven Scott f4a97aad2a
Release rolling / release (push) Successful in 8m18s
Move away from Autopass for AlmaLInux Compatibility
2026-07-14 22:19:33 -04:00

88 lines
2.4 KiB
JavaScript

/**
* Capability / role ACL for peardock RPC.
*
* Secure default: every peer is viewer (read-only).
* Elevate via:
* - admin seed HMAC proof (handshake)
* - HMAC capability grant (pd1. invite)
* - PEARDOCK_ADMIN_KEYS peer allowlist
* - peer policy registered role
* - PEARDOCK_INSECURE_OPEN_ADMIN=1 (dev escape hatch → admin for all)
*
* Set PEARDOCK_DEFAULT_ROLE=viewer|operator|admin to change baseline.
*/
import { Roles, roleAllows, MethodRoles } from '../../shared/protocol.js'
import { resolvePeerRole } from './peer-policy.js'
import { isInsecureOpenAdmin } from '../../shared/crypto-auth.js'
const DEFAULT_ROLE = (process.env.PEARDOCK_DEFAULT_ROLE || Roles.viewer).toLowerCase()
const ADMIN_KEYS = new Set(
(process.env.PEARDOCK_ADMIN_KEYS || '')
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean)
)
/**
* Resolve baseline role for a peer public key hex (before handshake elevation).
* @param {string} peerIdHex
* @returns {string}
*/
export function resolveRole(peerIdHex) {
const id = (peerIdHex || '').toLowerCase()
if (isInsecureOpenAdmin()) {
try {
return resolvePeerRole(id, Roles.admin)
} catch {
return Roles.admin
}
}
let envRole = Roles.viewer
if (ADMIN_KEYS.size > 0) {
envRole = ADMIN_KEYS.has(id) ? Roles.admin : DEFAULT_ROLE
} else if ([Roles.viewer, Roles.operator, Roles.admin].includes(DEFAULT_ROLE)) {
envRole = DEFAULT_ROLE
}
// Never default to admin unless explicitly configured
if (envRole === Roles.admin && ADMIN_KEYS.size === 0 && DEFAULT_ROLE !== Roles.admin) {
envRole = Roles.viewer
}
try {
return resolvePeerRole(id, envRole)
} catch {
return envRole
}
}
/**
* @param {string} role
* @param {string} method
*/
export function assertAllowed(role, method) {
if (!roleAllows(role, method)) {
const need = MethodRoles[method] || Roles.admin
const err = new Error(`Permission denied: ${method} requires role "${need}" (have "${role}")`)
err.code = 'PERMISSION_DENIED'
throw err
}
}
/**
* Rank helper for elevating (never demote via capability below policy max without care).
* Handshake elevation: max(baseline, elevated).
* @param {string} a
* @param {string} b
*/
export function maxRole(a, b) {
const rank = { [Roles.viewer]: 1, [Roles.operator]: 2, [Roles.admin]: 3 }
const ra = rank[a] || 0
const rb = rank[b] || 0
return ra >= rb ? a : b
}
export { Roles, ADMIN_KEYS, DEFAULT_ROLE }