/** * Portal JWT RBAC helpers (mirrors @authportal/shared hasPermission). * Format: mm:
: */ export type AuthUser = { id: string email: string name: string apps: string[] permissions: string[] isAdmin?: boolean } 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 } type Rule = { methods: string[] match: (path: string) => boolean permission: string } const RULES: Rule[] = [ { methods: ["GET", "POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/system") || p.startsWith("/api/scheduler") || p.startsWith("/api/evobgp"), permission: "mm:settings:admin", }, { methods: ["GET"], match: (p) => p.startsWith("/api/sidebar-counts") || p.startsWith("/api/events"), permission: "mm:dashboard:read", }, { methods: ["GET"], match: (p) => p.startsWith("/api/servers"), permission: "mm:servers:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/servers"), permission: "mm:servers:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/filters"), permission: "mm:filters:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/filters"), permission: "mm:filters:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/bgp"), permission: "mm:bgp:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/bgp"), permission: "mm:bgp:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/uptime"), permission: "mm:uptime:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/uptime"), permission: "mm:uptime:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/traffic"), permission: "mm:traffic:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/traffic"), permission: "mm:traffic:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/alerts"), permission: "mm:alerts:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/alerts"), permission: "mm:alerts:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/backups"), permission: "mm:backups:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/backups"), permission: "mm:backups:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/certificates"), permission: "mm:certificates:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/certificates"), permission: "mm:certificates:write", }, { methods: ["GET"], match: (p) => p.startsWith("/api/network") || p.startsWith("/api/ospf") || p.startsWith("/api/recursive") || p.startsWith("/api/probes") || p.startsWith("/api/internet-path") || p.startsWith("/api/exec"), permission: "mm:network:read", }, { methods: ["POST", "PUT", "PATCH", "DELETE"], match: (p) => p.startsWith("/api/network") || p.startsWith("/api/ospf") || p.startsWith("/api/recursive") || p.startsWith("/api/probes") || p.startsWith("/api/internet-path") || p.startsWith("/api/exec"), permission: "mm:network:write", }, ] /** Resolve required permission for method+path, or null if public / unknown. */ export function permissionForRequest( method: string, path: string, ): string | null { const m = method.toUpperCase() const pathname = path.split("?")[0] ?? path for (const rule of RULES) { if (!rule.methods.includes(m)) continue if (rule.match(pathname)) return rule.permission } if (pathname.startsWith("/api/")) return "mm:dashboard:read" return null }