/** * 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 }