I have multiple authentication strategies, example for one of them:
#Injectable()
export class EmployeeStrategy extends PassportStrategy(Strategy, 'employee') {
constructor(
private authService: AuthService,
#Inject(appConfig.KEY)
configService: ConfigType<typeof appConfig>,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.EMPLOYEE_KEY,
});
}
async validate({ phone }: JwtPayload) {
const employee = await this.authService.authByRole(phone, Role.Employee);
if (!employee) {
throw new UnauthorizedException('insufficient scope');
}
return employee;
}
And some others mostly like this one. But because i throw unauthorized exception inside it, i cannot use multiple of them at the same route/controller. E.g.
#UseGuards(AuthGuard(['employee', 'admin']))
The first one that crashes leading to error. How to solve that problem?
#xxx_coder_noscope Your vision of the strategy is a bit wrong. The strategy here is a way how to get a special token, secret key, etc from a defined place(HTTP header, query, param, cookies, etc). The entity returned from the validate() method will be injected to request object as user property.
Later via creating an EmployeeGuard as EmployeeGuard implements CanActivete and overriding canActivate() method check user role by type and return boolean for allowing or decline access to endpoint
Related
I have many services that all need to know the tenant ID from the request (kept in JWT auth token). The request is either GRPC (jwt stored in MetaData) or Graphql (jwt stored in context.headers.authorization).
I would like to be able to force myself not to forget to pass this tenant id when using the services. Ideally I dont want to even have to constantly write the same code to get the info from the request and pass it through. However the only ways I've managed to do it was using:
#Inject(REQUEST) for grpc in the service constructor. This doesn't work for the graphql requests. The only other way I saw was to only return service methods AFTER providing the data, which looks ugly as hell:
class MyService {
private _actions: {
myMethod1() { ... }
}
withTenantDetails(details) {
this._details = details;
return this._actions;
}
}
If I can somehow get the execution context within MyService that would be a good option, and make this easy using:
const getTenantId = (context: ExecutionContext) => {
if (context.getType() === 'rpc') {
logger.debug('received rpc request');
const request = context.switchToRpc().getContext();
const token = request.context.get("x-authorization");
return {
token,
id: parseTokenTenantInfo(token)
};
}
else if (context.getType<GqlContextType>() === 'graphql') {
logger.debug('received graphql request');
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().request;
const token = request.get('Authorization');
return {
token,
id: parseTokenTenantInfo(token)
};
}
else {
throw new Error(`Unknown context type receiving in tenant param decorator`)
}
}
But I can't find any way to get the executioncontext across to the service without also having to remember to pass it every time.
It's possible to inject Request into injectable service.
For that, the Service will be Scope.Request, and no more Singleton, so a new instance will be created for each request. It's an important consideration, to avoid creating too many resources for performance reason.
It's possible to explicit this scope with :
#Injectable({ scope: Scope.REQUEST })
app.service.ts :
#Injectable({ scope: Scope.REQUEST })
export class AppService {
tenantId: string;
constructor(#Inject(REQUEST) private request: Request) {
// because of #Inject(REQUEST),
// this service becomes REQUEST SCOPED
// and no more SINGLETON
// so this will be executed for each request
this.tenantId = getTenantIdFromRequest(this.request);
}
getData(): Data {
// some logic here
return {
tenantId: this.tenantId,
//...
};
}
}
// this is for example...
const getTenantIdFromRequest = (request: Request): string => {
return request?.header('tenant_id');
};
Note that, instead of decode a JWT token in order to retrieve TENANT_ID for each request, and maybe for other service (one per service), an other approach could be to decode JWT one single time, and then add it in Request object.
It could be done with a global Guard, same as authorization guard examples of official docs.
Here just a simple example : (could be merged with a Auth Guard)
#Injectable()
export class TenantIdGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
request['tenantId'] = getTenantIdFromRequest(request);
return true; // or any other validation
}
}
For GraphQL applications, we should inject CONTEXT in place of REQUEST :
constructor(#Inject(CONTEXT) private context) {}
You have to set either request inside context, or directly TENANT_ID inside context in order to retrieve it after inside service.
I have a repository:
export class MyRepository
extends
Repository<MyEntity>
{
constructor(
protected readonly _clientId: string
) {
super()
}
// ... methods
}
I need to pass the client id through which isnt known until request time. As such, the only way I know how to do it would be to create a factory which can create the repo at run time (it's in GRPC metadata).
#Injectable()
export class MyRepositoryFactory {
create(clientId: string) {
return new MyRepository(
clientId,
);
}
}
I register this as a provider and then in my controller I call:
const { clientId } = context;
const repository = this.myRepositoryFactory.create(clientId);
However I get the error
"Cannot read property 'findOne' of undefined"
when trying to do a basic typeorm call. I can see this is because instead the repository should be registered in the module imports like:
imports: [
TypeOrmModule.forFeature([ MyRepository, MyEntity ])
],
However this only works when injecting the repository directly, not in a factory. I have no idea how to either overcome this problem, or use a different way of creating the repository at run time with GRPC meta data passed through. Any help would be appreciated, thanks.
you cant call new and have the repository connected to TypeORM.
for that you need to make call to getCustomRepository(MyCustomRepository (or connectio.getCustomRepository) so it would be connected to the connectiong pool and everything TypeORM hides under the hood.
IMO creating a custom repository per request is not such a great idea, maybe you can create a Provider that has scope REQUEST (so the provider would be created per-request) allowing NestJS to inject the repository the 'standard' way, modify it's client attribute and then use it's methods that uses the internal repository methods.
something like
#Injectable({ scope: Scope.REQUEST })
export class RequestRepositoryHandler {
client_id: string
constructor(#InjectRepository(MyEntity) private repo: Repository<MyEntity>){}
// other methods
}
// usage
#Injectable()
export class SomeService {
constructor(private providerRepo: RequestRepositoryHandler){}
async method(client_id: string){
this.providerRepo.client_id = client_id; // the provider is per-request
// now do some work
}
}
Goal
I'm attempting to build a multi-tenant application with Feathers JS. On login, the tenant ID will be included in the request. From then on, the tenant ID will be gotten from the user field with the request parameters. Every user has this tenantId field.
In MongoDB, I have one unique collection of every data type per tenant. The collection names look like tenantId.documents and tenantId.users
Problem
The service generated via the feathers generate service CLI command looks like so:
export class Documents extends Service {
//eslint-disable-next-line #typescript-eslint/no-unused-vars
constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
super(options);
const client: Promise<Db> = app.get('mongoClient');
client.then(db => {
this.Model = db.collection('documents');
});
}
}
As you can see, the generated Services seem to need their collection name ("documents" in this case) during instantiation. Normally, this makes sense since it saves time awaiting a call to app.get("mongoClient")
However, since I need to dynamically change which collection I read from based on the User's tenantId, this won't work for me.
I implemented something like the following:
export class Documents extends Service {
client: Promise<Db>
//eslint-disable-next-line #typescript-eslint/no-unused-vars
constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
super(options);
this.client = app.get("mongoClient");
}
async create(data: IDocumentData, params: Params) {
const db: Db = await this.client;
this.Model = db.collection(`${params.user!!.organizationId}.documents`);
return super.create(data, params);
}
}
The problems are these:
I need to await this.client every request, even when the promise will probably already be fulfilled by the time a user actually makes a request to this service
I have to implement every method of the parent Service even though I barely need to add any real functionality.
Question
What is the most feathers-y way to solve this problem?
I don't want to override every method that I need in every service
I don't see a way to handle this with middleware or hooks.
I also don't think it's necessary to create one service instance per tenant in my application. It seems wasteful since I don't need to make any additional external requests based on the tenant ID, I just need to change the collection
Is there a good, pretty way to do this in Feathers?
Thanks to the helpful Feather community Slack channel, I think I came across a halfway-decent solution to this specific issue. It doesn't address all of my concerns, but at least it de-clutters my code.
First, I should create a new class that extends the built in Service class that implements the feature that I want. It could look something like this:
class DynamicMongoService extends Service {
client: Promise<Db>;
collectionName: string;
constructor(
options: Partial<MongoDBServiceOptions>,
app: Application,
collectionName: string
) {
super(options);
this.client = app.get("mongoClient");
this.collectionName = collectionName;
}
async getCollection(params: Params) {
const db: Db = await this.client;
this.Model = db.collection(
`${params!!.user!!.organizationId}.${this.collectionName}`
);
}
async find(params?: Params) {
await this.getCollection(params!!);
return super.create(params!!);
}
async get(id: Id, params?: Params) {
await this.getCollection(params!!);
return super.get(id, params);
}
async create(data: Partial<any> | Array<Partial<any>>, params?: Params) {
await this.getCollection(params!!);
return super.create(data, params);
}
async update(id: NullableId, data: any, params?: Params) {
await this.getCollection(params!!);
return super.update(id!!, data, params);
}
async patch(id: NullableId, data: Partial<any>, params?: Params) {
await this.getCollection(params!!);
return super.patch(id!!, data, params);
}
async remove(id: NullableId, params?: Params) {
await this.getCollection(params!!);
return super.patch(id!!, params!!);
}
}
The key elements are thus:
Pass collection name in the constructor
Get the collection name before each method
An implementation of this service would look like this:
export class Documents extends DynamicMongoService {
constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
super(options, app, "documents");
}
}
Not the best, but easy enough!
I am trying to log the session user but its not working as i suspected. I was thinking i could add it in the middle ware but clearly this wont work. Is there a strategy or pattern i should be using to accomplish this.
The PassportAuthInterceptor log should have distinct users but its only using the last one set by the Middleware
I see them using it in the log4js documentation but clearly this doesnt seem to make sense the way i demonstrated below.
I also see that the have a AuthLibrary.currentUser() call, but im unsure how to accomplish this.
https://github.com/log4js-node/log4js-node/blob/master/docs/layouts.md#tokens
seeing as how this is NestJS, can i inject the user into a service somehow as that could solve my problem.
export class LoggerMiddleware implements NestMiddleware {
private readonly logger = new AppLoggerService(LoggerMiddleware.name);
public use(req, res, next: () => void) {
log4js.getLogger('default').addContext('user', user.authName)
next();
}
}
So this is kinda gross but it works, i created 2 loggers one global (normal) and one session (scope request). The thing is that any class that is used by passport login cannot reference the SessionLoggerService .. This will have me split classes as global and Session so the classes they call can use the correct logger.
https://github.com/nestjs/nest/issues/1870
#Injectable({scope: Scope.REQUEST})
export class SessionLoggerService implements LoggerService {
public static logger = undefined;
constructor(#Inject(REQUEST) private readonly req) {
SessionLoggerService.logger = log4js.getLogger('user');
const user = this.req && this.req.user ? this.req.user : null;
if (user) {
SessionLoggerService.logger.addContext('user', user.authName);
} else {
SessionLoggerService.logger.addContext('user', '');
}
}
....
}
I am working on a multi-tenant application using NestJS and offering my API through their GraphQL module. I would like to know how I could tell NestJS to instantiate my providers per web request. According to their documentation providers are singleton by default but I could not find a way to register transient or per request providers.
Let me explain a specific use case for this. In my multi-tenant implementation, I have a database per customer and every time I receive a request in the backend I need to find out for which customer it is so I need to instantiate services with a connection to the right database.
Is this even possible using NestJS?
With the release of nest.js 6.0, injection scopes were added. With this, you can choose one of the following three scopes for your providers:
SINGLETON: Default behavior. One instance of your provider is used for the whole application
TRANSIENT: A dedicated instance of your provider is created for every provider that injects it.
REQUEST: For each request, a new provider is created. Caution: This behavior will bubble up in your dependency chain. Example: If UsersController (Singleton) injects UsersService (Singleton) that injects OtherService (Request), then both UsersController and UsersService will automatically become request-scoped.
Usage
Either add it to the #Injectable() decorator:
#Injectable({ scope: Scope.REQUEST })
export class UsersService {}
Or set it for custom providers in your module definition:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
Outdated Answer
As you can see in this issue, nestjs does not yet offer a built-in solution for request-scoped providers. But it might do so in the near future:
Once async-hooks feature (it is still experimental in node 10) is
stable, we'll think about providing a built-in solution for
request-scoped instances.
I was struggling with similar issue, and one way to achieve this is to use node-request-context module as a global request register, that will give you the request context. So you will not have separate service instances, but you can ask this static register to give you request specific instance/connection.
https://github.com/guyguyon/node-request-context
Create simple context helper:
import { createNamespace, getNamespace } from 'node-request-context';
import * as uuid from 'uuid';
export class RequestContext {
public static readonly NAMESPACE = 'some-namespace';
public readonly id = uuid.v4();
constructor(public readonly conn: Connection) { }
static create(conn: Connection, next: Function) {
const context = new RequestContext(conn);
const namespace = getNamespace(RequestContext.NAMESPACE) || createNamespace(RequestContext.NAMESPACE);
namespace.run(() => {
namespace.set(RequestContext.name, context);
next();
});
}
static currentRequestContext(): RequestContext {
const namespace = getNamespace(RequestContext.NAMESPACE);
return namespace ? namespace.get(RequestContext.name) : null;
}
static getConnection(): Connection {
const context = RequestContext.currentRequestContext();
return context ? context.conn : null;
}
}
The conn instance parameter is your connection, feel free to put there other request specific dependencies. Also the id there is just for debugging, no real need to use uuid module as I did.
Create middleware wrapper (this allows you to use DI here):
#Injectable()
export class ContextMiddleware implements NestMiddleware {
constructor(private readonly connectionManager: ...) { }
resolve(...args: any[]): MiddlewareFunction {
return (req, res, next) => {
// create the request specific connection here, probably based on some auth header...
RequestContext.create(this.connectionManager.createConnection(), next);
};
}
}
Then register new middleware in your nest application:
const app = await NestFactory.create(AppModule, {});
app.use(app.get(RequestLoggerMiddleware).resolve());
And finally the profit part - get the request specific connection anywhere in your application:
const conn = RequestContext.getConnection();
NestJS has a build-in request-scope dependency injection mechanism
https://docs.nestjs.com/fundamentals/injection-scopes
but it has serious drawbacks according to the documentation:
Scope bubbles up the injection chain. A controller that depends on a request-scoped provider will, itself, be request-scoped.
Using request-scoped providers will have an impact on application performance. While Nest tries to cache as much metadata as possible, it will still have to create an instance of your class on each request. Hence, it will slow down your average response time and overall benchmarking result. Unless a provider must be request-scoped, it is strongly recommended that you use the default singleton scope.
Recently I have created request-scope implementation for NestJS free from bubbling up the injection chain and performance impact.
https://github.com/kugacz/nj-request-scope
To use it first you have to add import of RequestScopeModule in the module class decorator:
import { RequestScopeModule } from 'nj-request-scope';
#Module({
imports: [RequestScopeModule],
})
Next, there are two ways of request-scope injection:
Inject express request object into class constructor with NJRS_REQUEST token:
import { NJRS_REQUEST } from 'nj-request-scope';
[...]
constructor(#Inject(NJRS_REQUEST) private readonly request: Request) {}
Change class inject scope to request-scope with #RequestScope() decorator:
import { RequestScope } from 'nj-request-scope';
#Injectable()
#RequestScope()
export class RequestScopeService {
You can find example implementations in this repository: https://github.com/kugacz/nj-request-scope-example