最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to apply a decorator to all class methods using class decorator - Stack Overflow

programmeradmin4浏览0评论

I'm using experimental typescript decorators to manage access control in express.

class AccountController extends Controller {
  
  login(req: Request, res: Response) {
    const { email, password } = req.body;
    const token = await this.model.login(email, password);
    return res.json({
      token
    });
  }

  @hasRole('ADMIN')
  list(req: Request, res: Response) {
    res.json({
      data: await this.model.findAll()
    });
  }
}

hasRole method decorator is working fine and I'm happy with it.

The Controller implements REST methods:

class Controller {
  list(req: Request, res: Response) { // impl }
  get(req: Request, res: Response) { // impl } 
  create(req: Request, res: Response) { // impl }
  update(req: Request, res: Response) { // impl }
  delete(req: Request, res: Response) { // impl }
}

The problem is, I have to apply the same decorators to most of other controllers and I find it very repetitive. For example, StockController should allow access to only MERCHANT role, and I have to do something like the following:

class StockController extends Controller {
  @hasRole('MERCHANT')
  list(req: Request, res: Response) { 
    return super.list(req, res);
  }
  @hasRole('MERCHANT')
  get(req: Request, res: Response) { 
    return super.get(req, res);
  } 
  @hasRole('MERCHANT')
  create(req: Request, res: Response) { 
    return super.create(req, res);
  }
  @hasRole('MERCHANT')
  update(req: Request, res: Response) { 
    return super.update(req, res);
  }
  @hasRole('MERCHANT')
  delete(req: Request, res: Response) { 
    return super.delete(req, res);
  }
}

This approach is not only dull and repetitive but also unsafe, because if I add a method to Controller and accidentally forget to add the method to child controllers, they will allow unwanted access.

I'd like to deal this problem with class decorator and use something like the following:

@requireRole('MERCHANT')
class StockController extends Controller {}

However, from what I see in the doc:

The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition.

As far as I understand it, I cannot implement "method hook" in class decorators. Any suggestions?

For your information, hasRole decorator looks like the following:

export function hasRole(role: string) {
  return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function(req: Request, res: Response) {
      const user = res.locals.user;
      if (user && user.hasRole(role)) {
        originalMethod.apply(this, [req, res]);
      } else {
        res.status(403).json({});
      }
    }
  }
}

I'm using experimental typescript decorators to manage access control in express.

class AccountController extends Controller {
  
  login(req: Request, res: Response) {
    const { email, password } = req.body;
    const token = await this.model.login(email, password);
    return res.json({
      token
    });
  }

  @hasRole('ADMIN')
  list(req: Request, res: Response) {
    res.json({
      data: await this.model.findAll()
    });
  }
}

hasRole method decorator is working fine and I'm happy with it.

The Controller implements REST methods:

class Controller {
  list(req: Request, res: Response) { // impl }
  get(req: Request, res: Response) { // impl } 
  create(req: Request, res: Response) { // impl }
  update(req: Request, res: Response) { // impl }
  delete(req: Request, res: Response) { // impl }
}

The problem is, I have to apply the same decorators to most of other controllers and I find it very repetitive. For example, StockController should allow access to only MERCHANT role, and I have to do something like the following:

class StockController extends Controller {
  @hasRole('MERCHANT')
  list(req: Request, res: Response) { 
    return super.list(req, res);
  }
  @hasRole('MERCHANT')
  get(req: Request, res: Response) { 
    return super.get(req, res);
  } 
  @hasRole('MERCHANT')
  create(req: Request, res: Response) { 
    return super.create(req, res);
  }
  @hasRole('MERCHANT')
  update(req: Request, res: Response) { 
    return super.update(req, res);
  }
  @hasRole('MERCHANT')
  delete(req: Request, res: Response) { 
    return super.delete(req, res);
  }
}

This approach is not only dull and repetitive but also unsafe, because if I add a method to Controller and accidentally forget to add the method to child controllers, they will allow unwanted access.

I'd like to deal this problem with class decorator and use something like the following:

@requireRole('MERCHANT')
class StockController extends Controller {}

However, from what I see in the doc:

The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition.

As far as I understand it, I cannot implement "method hook" in class decorators. Any suggestions?

For your information, hasRole decorator looks like the following:

export function hasRole(role: string) {
  return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function(req: Request, res: Response) {
      const user = res.locals.user;
      if (user && user.hasRole(role)) {
        originalMethod.apply(this, [req, res]);
      } else {
        res.status(403).json({});
      }
    }
  }
}

Share Improve this question edited Aug 11, 2020 at 12:44 glinda93 asked Aug 11, 2020 at 9:26 glinda93glinda93 8,4996 gold badges50 silver badges87 bronze badges 2
  • A method decorator wraps the method into a new function that calls the original method and can also do other things before or after that. A class decorator wraps the constructor. It is possible to write a class decorator that does what you need. – axiac Commented Aug 11, 2020 at 9:43
  • @axiac Thanks for suggestion. Please guide me by an example, I'm lost.. – glinda93 Commented Aug 11, 2020 at 9:44
Add a ment  | 

1 Answer 1

Reset to default 7

This is possible by overriding class methods

function AttachToAllClassDecorator<T>(someParam: string) {
    return function(target: new (...params: any[]) => T) {
        for (const key of Object.getOwnPropertyNames(target.prototype)) {
            // maybe blacklist methods here
            let descriptor = Object.getOwnPropertyDescriptor(target.prototype, key);
            if (descriptor) {
                descriptor = someDecorator(someParam)(key, descriptor);
                Object.defineProperty(target.prototype, key, descriptor);
            }
        }
    }
}

Basically going through all methods (maybe add some logic around it for whitelisting/blacklisting some methods) and overriding with new method that has method decorator wrapped.

Here is basic example for method decorator.

function someDecorator(someParam: string): (methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor {
    return (methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor => {
        let method = descriptor.value;

        descriptor.value = function(...args: any[]) {
            console.warn(`Here for descriptor ${methodName} with param ${someParam}`);

            return method.apply(this, args);
        }

        return descriptor;
    }
}

TS Playground

发布评论

评论列表(0)

  1. 暂无评论