'use strict'; const {Permission} = require('../models/permission'); const tldExtract = require('tld-extract').parse_host; /** * Authorization middleware. * * Builds on middleware/auth.js (which sets req.user + req.groups). Effective * rights are resolved once per request via Permission.effectiveFor and cached on * req._effective. Roles: admin > manager (owner/full over a domain) > viewer. */ // The username for the request, tolerating a missing user relation by falling // back to the token's created_by (which is the username). function reqUsername(req){ return (req.user && req.user.username) || (req.token && req.token.created_by) || null; } // Normalize a host or domain string to its registrable domain. function toDomain(value){ if(!value) return value; try{ return tldExtract(value).domain; }catch(error){ return value; } } // Resolve (and cache) the effective rights for this request. async function getEffective(req){ if(req._effective) return req._effective; req._effective = await Permission.effectiveFor({ username: reqUsername(req), groups: req.groups || [], }); return req._effective; } function forbidden(message){ let error = new Error('Forbidden'); error.name = 'Forbidden'; error.message = message || 'You do not have permission to perform this action.'; error.status = 403; return error; } // Global-admin-only gate (user management, DNS providers, grant management). async function requireAdmin(req, res, next){ try{ let effective = await getEffective(req); if(effective.isAdmin) return next(); return next(forbidden('Administrator access required.')); }catch(error){ return next(error); } } /** * Require at least `minRole` on the domain resolved from the request. * * @param {string} minRole - 'viewer' | 'manager' * @param {Function} resolveDomain - (req) => host|domain string */ function requireDomainRole(minRole, resolveDomain){ return async function(req, res, next){ try{ let effective = await getEffective(req); // Match against the full hostname so subdomain wildcards resolve; // plain patterns still cover their subdomains (see roles.domainMatch). let target = resolveDomain(req); if(!target) return next(forbidden('Could not determine the target domain.')); target = String(target).toLowerCase().trim(); if(Permission.allows(effective, minRole, target)) return next(); return next(forbidden(`You need '${minRole}' rights on ${toDomain(target)}.`)); }catch(error){ return next(error); } }; } // Common domain resolvers for route wiring. const resolve = { // A host lives in req.params.item (e.g. api.example.com -> example.com). hostParam: req => req.params.item, // A host being created lives in the request body. hostBody: req => req.body && req.body.host, // A domain (or provider domain) name in req.params.item. domainParam: req => req.params.item, }; /** * Filter a list of records to those the request may at least view. * Admins and holders of a global role see everything; otherwise a record is * kept when its domain (via `getDomain`) is one the user has rights on. * * @param {Object} req * @param {Array} records * @param {Function} getDomain - (record) => host|domain string */ async function filterViewable(req, records, getDomain){ let effective = await getEffective(req); if(effective.isAdmin || Permission.rank(effective.global) >= Permission.rank('viewer')){ return records; } return records.filter(function(record){ // Full host; domainMatch handles exact, subdomain, and wildcard patterns. return Permission.allows(effective, 'viewer', getDomain(record)); }); } module.exports = { getEffective, requireAdmin, requireDomainRole, filterViewable, resolve, toDomain, reqUsername, };