UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

140 lines (121 loc) 3.74 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', 'graphql'].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', is_graphql: model === 'graphql', }, 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, ); req.body = obj.payload; req.query = obj.query; // @ts-ignore res.body = obj.body; // @ts-ignore res.headers = obj.headers; return next(); }; }