4

I want to get the user token from the middlewares and injected into controller, is it possible?

class TaskController {
    @inject private currentUser

    @Post('/tasks')
    addTask() {
        if (!hasPermission(currentUser)) throw new Error("Unauthorized.")
        // ...
    }
}

I hope the injected currentUser above can be retrieved from some middlewares.

Ron
  • 6,037
  • 4
  • 33
  • 52

2 Answers2

2

At the moment, InversifyJS onlysupports singleton and trainsient scopes. We have a roadmap item to investigate new types of scope.

We are also working on the support for middleware at the moment but it is not fully ready. You can contact the development team on Gitter to find out more about our plans, help us or requests features.

Remo H. Jansen
  • 23,172
  • 11
  • 70
  • 93
0

InversifyJS has had the inRequestScope() for some time now, but it doesn't help much with the container-per-http-request case, because InversifyJS's request scope is actually tied to a single call to get, that is, each call to get is considered a Request, and it will only work as intended for an HTTP context if only have a single call to get per request.

I had the same problem as yours: from within a middleware, I needed to pull the current user from the request and inject that information into a CurrentUser class, so that information could later be accessed by other services down the line.

For this, I needed at least two calls to get<CurrentUser>: one in the middleware and another for instantiating the controller/handler for the request.

So, inRequestScope is not a viable solution, and inSingletonScope or inTransientScope are also out of the question.

I ended up creating the ScopedContainer class you can see down below.

First, this is how you would use it:

// register-global-dependencies.ts
ScopedContainer.globalContainer = (() => {
  const container = new Container();
  container
    .bind<SomeSingletonDep>(TOKENS.SomeSingletonDep)
    .to(SomeSingletonDep)
    .inSingletonScope();
  return container;
})();

☝️ This allows you to still have Singleton dependencies.

// register-scoped-dependencies.ts
import "register-global-dependencies";

ScopedContainer.postConfigure((container) => {
  container
    .bind<RequestSpecificDep>(TOKENS.RequestSpecificDep)
    .to(RequestSpecificDep)
    .inSingletonScope();
});

☝️ This controls which dependencies should be resolved once per HTTP request.

// lambda-handler.ts
import "register-scoped-dependencies";

handler = (event, context) => {
  const requestId = event.requestContext.requestId;
  const container = ScopedContainer.for(requestId);

  try {
    // This will be the same for every request
    const singletonDep = container.get(TOKENS.SomeSingletonDep);

    // And this will be a new instance for every request
    const requestSpecificDep = container.get(TOKENS.RequestSpecificDep);
  }
  finally {
    ScopedContainer.remove(requestId);
  }
}

This is the ScopedContainer class:

import { Container, interfaces } from "inversify";

const DEFAULT_SCOPE_ID = "__default__";

export type PostConfigureAction = (container: Container) => void;

export type ScopedContainerCache = {
  [id: string]: Container;
};

export class ScopedContainer {
  private static _postConfigureActions: PostConfigureAction[] = [];
  private static readonly _instances: ScopedContainerCache = {};

  /**
   * Options object to use when creating a new container for a
   * scope ID.
   */
  static containerOptions: interfaces.ContainerOptions;

  /**
   * A global container instance, which enables truly
   * singleton instances when using a scoped container. All scoped
   * containers reference the global container as parent.
   *
   * @example
   * ScopedContainer.globalContainer = (() => {
   *   const container = new Container();
   *   container
   *     .bind<ClassInSingletonScope>(TOKENS.ClassInSingletonScope)
   *     .to(ClassInSingletonScope)
   *     .inSingletonScope();
   *   return container;
   * })();
   */
  static globalContainer: Container;

  /**
   * Optional function to configure the global container.
   * An alternative to directly setting the @see globalContainer property.
   */
  static configureGlobalContainer: (container: Container) => void;

  /**
   * Returns a @see Container that is unique to the specified scope.
   * If this is the first time getting the container for the scope, then a
   * new container will be created using the provided factory. Any post configure
   * actions will also be applied to the new container instance.
   * @param scopeId Any string to identify the scope (e.g. current request ID).
   * @returns A @see Container that is unique to the specified scope.
   */
  static for(scopeId = DEFAULT_SCOPE_ID): Container {
    let container = this._instances[scopeId];
    if (!container) {
      container = this.makeNewContainer();
      this._instances[scopeId] = container;
    }
    return container;
  }

  /**
   * Unbinds the @see Container (i.e. container.unbindAll()) and removes
   * it from the cache.
   * @param scopeId
   */
  static remove(scopeId = DEFAULT_SCOPE_ID): void {
    let container = this._instances[scopeId];
    if (!container) return;
    container.unbindAll();
    delete this._instances[scopeId];
  }

  /**
   * Runs the @method remove method on all instances.
   */
  static removeAll(): void {
    Object.keys(this._instances).forEach((key) => this.remove(key));
  }

  /**
   * Adds a post configure action.
   * @remarks A registration with .inSingletonScope() will work as .inRequestScope() should,
   * that is, a new instance will be created for each instance of ScopedContainer.
   * @param fn A function that will be run everytime a new @see Container is created.
   * @returns The @see ScopedContainer itself, to allow chaining.
   */
  static postConfigure(fn: PostConfigureAction): ScopedContainer {
    this._postConfigureActions.push(fn);
    return this;
  }

  /**
   * Removes any post configure actions.
   */
  static resetPostConfigureActions(): void {
    this._postConfigureActions = [];
  }

  private static makeNewContainer(): Container {
    const container = this.ensureGlobalContainer().createChild(this.containerOptions);
    this._postConfigureActions.forEach((action) => action(container));
    return container;
  }

  private static ensureGlobalContainer(): Container {
    if (!this.globalContainer) {
      const container = new Container(this.containerOptions);
      this.configureGlobalContainer?.(container);
      this.globalContainer = container;
    }
    return this.globalContainer;
  }
}

https://github.com/inversify/InversifyJS/issues/1076#issuecomment-1045179988

Phillippe Santana
  • 2,906
  • 2
  • 28
  • 29