forked from snxraven/peardock
88 lines
2.4 KiB
JavaScript
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 }
|