After a few hours of research for a good logical and semantic prototype, i found a good solution.
router.put('/:articleId', AuthGuard([{ role: 'user', when: isOwner }, { role: 'admin' }]), async (req, res) => {
});
A predicate function can be added to a specific role for more flexibility. In this example, the user can only update an item "when" it owns it.
The little module :
import passport from 'passport';
import { Request, Response, NextFunction } from 'express';
export const AuthenticateGuard = passport.authenticate('jwt', { session: false });
export interface IAuthorizeItem {
role: string;
when?: (req: Request) => boolean;
};
export default function AuthGuard(items?: IAuthorizeItem[]) {
return [AuthenticateGuard, AuthorizeGuard(items)];
};
export function AuthorizeGuard(items?: IAuthorizeItem[]) {
return async function (req: Request, res: Response, next: NextFunction) {
let user = req.user;
if (!user) {
return res.status(500).send('Access not allowed !');
}
if (typeof req.user.hasRole !== 'function') {
throw new Error('User object must have hasRole method');
}
if (!items || items.length == 0) {
return next();
}
for (let item of items) {
if (req.user.hasRole(item.role)) {
if (!item.when) {
return next();
}
else {
if (item.when(req)) {
return next();
}
}
}
}
res.status(500).send('Access not allowed !');
}
};
Note: I avoid any dynamic types checking because i used typescript and this module must be used only with static object.