UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

142 lines (123 loc) 3.9 kB
import type { NextFunction, Request, Response } from 'express'; import { Access, Services, authorizations as authz } from '../../typings'; import os from 'node:os'; import { getAuthorizationToken } from './authenticate'; export function authorize(services: Services) { return async (req: Request, res: Response, next: NextFunction) => { const now = new Date(); const method = req.method.toLowerCase(); const path = `/api${req.path}`; const fragments: Array<string> = path.split('/').slice(1); const query = req.query; const headers = req.headers; const cookies = req.cookies ?? {}; const token = getAuthorizationToken(req); const access = services.config.security.tokens.find( ({ token: t }) => t === token, ) as Access | undefined; const modelConfig = services.models.MODELS.get(fragments[1]); const model = ['admin', 'stream', 'aggregate'].includes(fragments[1]) ? 'na' : (modelConfig?.getModelConfig().name ?? 'unknown'); const correlationId = model === 'na' ? 'na' : (fragments[2] ?? 'unknown'); const correlationField = modelConfig?.getCorrelationField() ?? 'unknown'; if ( services.config.authz.onlyModels.length > 0 && !services.config.authz.onlyModels.includes(model) ) { return next(); } if ( services.config.authz.skipModels.length > 0 && services.config.authz.skipModels.includes(model) ) { return next(); } const authorizationRequest: authz.AuthorizationRequest = { action: { scope: [ 'action', ...fragments, method, correlationField, correlationId, ], method, path, query, headers, is_stream: model === 'stream', is_admin: model === 'admin', is_aggregate: model === 'aggregate', }, subject: { ...access, scope: ['subject', access?.id ?? 'na'], cookies, token, access, }, object: { scope: ['object'], model, correlation_id: correlationId, correlation_field: correlationField, }, context: { scope: ['context'], date: now.toISOString(), dow: now.getDay(), dom: now.getDate(), year: now.getFullYear(), month: now.getMonth() + 1, hostname: os.hostname(), }, }; const decision = await services.authz.authorize(authorizationRequest); services.telemetry.logger.debug( '[middlewares#authorization] Authorization request', { request: authorizationRequest, decision }, ); if (decision.verb === authz.AUTHORIZATION_VERB_DENY) { return next({ status: 403, message: 'Unauthorized' }); } res.locals.authz = { decision, request: authorizationRequest }; return next(); }; } export function obligations(services: Services) { return async (req: Request, res: Response, next: NextFunction) => { const obligations = (res.locals?.authz?.decision.obligations ?? []) as Array<authz.Obligation>; if (obligations.length === 0) { return next(); } const obj = services.authz.applyObligations( { // @ts-ignore body: res.body, payload: req.body, query: req.query, headers: req.headers, request: res.locals?.authz?.request, decision: res.locals?.authz?.decision, }, obligations, ); Object.defineProperty(req, 'body', { ...Object.getOwnPropertyDescriptor(req, 'body'), value: obj.payload, writable: true, }); Object.defineProperty(req, 'query', { ...Object.getOwnPropertyDescriptor(req, 'query'), value: obj.query, writable: true, }); // @ts-ignore res.body = obj.body; // @ts-ignore res.headers = obj.headers; return next(); }; }