import {AccessControl, AccessControlError, IQueryInfo, Permission} from 'role-acl'; import {Query} from 'role-acl/lib/src/core/Query'; import {Request, RequestHandler} from 'express'; // see https://www.npmjs.com/package/role-acl export class PermissionManager extends AccessControl { public can(roleOrRequest: Request|string|string[]|IQueryInfo): PermQuery { return new PermQuery(this.getGrants(), roleOrRequest); } public getRouter(resource: string, opts?: Partial): RequestHandler { return async (req: Request, res, next) => { let query = this.can(req); if (opts?.context) query = query.context(opts?.context); if (opts?.action) query = query.execute(opts?.action); if (opts?.skipConditions) query = query.skipConditions(opts?.skipConditions); const permission = await query.on(resource); if (permission.granted) { req.permissionDetails = permission; next(); } else { res.sendStatus(403); } }; } } export type RermRouterOpts = { context: unknown, action: string, skipConditions: boolean } export class PermQuery extends Query { protected resolveRequest: Request|undefined; constructor(grants: unknown, roleOrRequest: Request|string|string[]|IQueryInfo) { function isRequest(obj: unknown): obj is Request { // eslint-disable-next-line no-prototype-builtins return typeof obj === 'object' && obj && obj.hasOwnProperty('res') || false; } if (isRequest(roleOrRequest)) { super(grants, []); this.resolveRequest = roleOrRequest; } else { super(grants, roleOrRequest); } } public async on(resource: string, skipConditions?: boolean): Promise { if (this.resolveRequest) { const userInfo = await this.resolveRequest.getUserInfo(); const availableRoles = Object.keys(this._grants); const roles = (userInfo?.groups ?? []).filter(x => availableRoles.includes(x)); this.role(roles); } if ( typeof this._.role === 'object' && this._.role.includes('noaccess') || typeof this._.role === 'string' && this._.role === 'noaccess' ) { this.role([]); } return super.on(resource, skipConditions); } public context(context: unknown): PermQuery { super.context(context); return this; } public skipConditions(value: boolean): PermQuery { super.skipConditions(value); return this; } public with(context: unknown): PermQuery { super.with(context); return this; } public execute(action: string): PermQuery { super.execute(action); return this; } public sync(): PermQuery { throw new AccessControlError('Sync method is not allowed on PermissionManager!'); } } export const PermManager = new PermissionManager();