UNPKG

@nmshd/typescript-rest

Version:

A Library to create RESTFul APIs with Typescript

64 lines (50 loc) 2.28 kB
import * as debug from 'debug'; import { NextFunction, Request, Response } from 'express'; import * as Errors from '../server/model/errors'; const debuggerInstance = debug('typescript-rest:middlewares:routeRequiresAuthorization'); export function routeRequiresAuthorization( authenticator: { getRoles: (req: Request, res: Response) => Array<string> }, ...permittedRoles: [string, ...Array<string>] ) { if (permittedRoles.length === 0) throw new Error('At least one permitted role must be specified.'); const roleRegex = /^[a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+){0,}$/; const nonMatchingRoles = permittedRoles.filter((role) => !roleRegex.test(role)); if (nonMatchingRoles.length > 0) { throw new Error( `Invalid permitted role(s) specified: ${nonMatchingRoles.join(', ')}. Roles must match the pattern: ${roleRegex}.` ); } return (req: Request, res: Response, next: NextFunction) => { const rawUserRoles = authenticator.getRoles(req, res); if (debuggerInstance.enabled) debuggerInstance('Validating authentication roles: <%j>.', rawUserRoles); const userRoles = rawUserRoles.map(Role.from); const isAuthorized = permittedRoles.some((r) => isRoleMatched(r, userRoles)); if (!isAuthorized) { next(new Errors.ForbiddenError('You are not allowed to access this endpoint.')); return; } next(); }; } function isRoleMatched(permittedRole: string, userRoles: Array<Role>): boolean { return userRoles.some((userRole) => userRole.matches(permittedRole)); } class Role { private roleRegex: RegExp; private constructor(role: string) { this.roleRegex = this.transformUserRole(role); } public static from(role: string): Role { return new Role(role); } private transformUserRole(userRole: string): RegExp { if (!userRole.includes('*')) return new RegExp(`^${userRole}$`); const regexString = userRole .replaceAll('**', '[a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+){0,}') .replaceAll('*', '[a-zA-Z0-9_-]+'); return new RegExp(`^${regexString}$`); } public matches(permittedRole: string): unknown { return this.roleRegex.test(permittedRole); } }