Nestjs call request scoped service without request - nestjs

After adding a request scoped service to an existing codebase, many of the seeder functions calling services directly return undefined for request scoped services (as no HTTP request has actually been sent).
How can I "seed" a request instance so it's available and can be resolved as a dependency in the service?

The following utility function will seed a request and return an ID for usage:
import { INestApplication } from '#nestjs/common'
import { ContextIdFactory } from '#nestjs/core'
/**
* Seeds a request and returns the context ID
* for referencing (e.g. for `NestApplication.resolve()`).
*
* Request scoped services rely on a HTTP request
* to be made to be instantiated, and
* seeders do not fire HTTP requests to
* generate context.
*/
export const seedRequest = <T extends {}>(app: INestApplication, req: T) => {
const contextId = ContextIdFactory.create()
app.registerRequestByContextId(req, contextId)
return {
contextId,
}
}
Use like so:
const app: INestApplication
// initialise app, etc...
const contextId = seedRequest(app, {
req: {
user: {
id: 'abc123',
},
},
})
const someService = await app.resolve(SomeService, contextId)
// use someService...

Related

What is the right way to pass request data to services in nestjs?

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.

Loopback 4 authentication metadata options undefined

I have created a simple jwt auth application the same way its displayed here: https://github.com/raymondfeng/loopback4-example-auth0
The authentication part is working properly but the authorization does not work as expected.
I decorated my controller with following function and added a scope.
#authenticate({strategy: 'auth0-jwt', options: {scopes: ['greet']}})
In my authentication strategy I´m checking the scope via the AuthenticationMetadata class.
import {AuthenticationBindings, AuthenticationMetadata, AuthenticationStrategy} from '#loopback/authentication';
import {inject} from '#loopback/core';
import {ExpressRequestHandler, Request, Response, RestBindings} from '#loopback/rest';
import {UserProfile} from '#loopback/security';
import {JWT_SERVICE} from './types';
const jwtAuthz = require('express-jwt-authz');
export class JWTAuthenticationStrategy implements AuthenticationStrategy {
name = 'auth0-jwt';
constructor(
#inject(RestBindings.Http.RESPONSE)
private response: Response,
#inject(AuthenticationBindings.METADATA)
private metadata: AuthenticationMetadata,
#inject(JWT_SERVICE)
private jwtCheck: ExpressRequestHandler,
) {}
async authenticate(request: Request): Promise<UserProfile | undefined> {
return new Promise<UserProfile | undefined>((resolve, reject) => {
this.jwtCheck(request, this.response, (err: unknown) => {
if (err) {
console.error(err);
reject(err);
return;
}
console.log(this.metadata.options);
// If the `#authenticate` requires `scopes` check
if (this.metadata.options?.scopes) {
jwtAuthz(this.metadata.options!.scopes, {failWithError: true})(request, this.response, (err2?: Error) => {
if (err2) {
console.error(err2);
reject(err2);
return;
}
// eslint-disable-next-line #typescript-eslint/no-explicit-any
resolve((request as any).user);
});
} else {
// eslint-disable-next-line #typescript-eslint/no-explicit-any
resolve((request as any).user);
}
});
});
}
}
When trying to access
this.metadata.options
I´m always getting an undefined back.
How can I achieve to get the options and the scope out of metadata?
Thanks
For Loopback Authorization your class needs to implement the Provider<Authorizer> interface. In that interface it defines the 2 functions you need to implement
#injectable({scope: BindingScope.TRANSIENT})
class AuthorizationService implements Provider<Authorizer>{
value (): Authorizer {
return this.authorize.bind(this);
}
async authorize (
context: AuthorizationContext,
metadata: AuthorizationMetadata,
) {
// TODO implement authorization
}
}
The authorization metadata will be injected by loopback into that function automatically after you bind it with an AuthorizationTags.Authorizer
If you are having problems implementing Authentication then read my step by step guide on how we implemented Loopback Authentication using Firebase. That should be able to help you with the core ideas to get Authentication running.

Function implementation is missing or not immediately following the declaration

hi everyone im new to typescript and node and trying to make a chat app using stream.io and typescript. Now im trying to get all messagess from a specific channell in getallmessages function. however when i call this function to test it im getting error saying Function implementation is missing or not immediately following the declaration. Can someone please tell me how i can call this function so i can display response to console ? thanks !
import { StreamChat } from 'stream-chat';
import { appConfig } from '#/config';
export class ChatService {
public static masterHealthBotUserId = 'masterHealthBot';
private static client = StreamChat.getInstance(
appConfig.get('getstream').apiKey,
appConfig.get('getstream').apiSecret,
);
/**
* Creates get stream chat token that can be used by the client to connect to getStream
* #param userId - User Id
* #param expiry - expiration time of token. If undefined then token wont expire
*/
public createToken({ userId, expiry }: { userId: number; expiry?: number }) {
return ChatService.client.createToken(userId.toString(), expiry);
}
// function that im trying to call but its giving me error
public async getallmessages(podId: string) {
const channel = ChatService.client.channel('messaging', podId);
const messageFilter = { id: { $exists: true } };
const response = await channel.search(messageFilter);
console.log(response.results);
}}
}
getallmessages('3');
}

Feathers JS MongoDB Service: Dynamic Collection Names

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!

Aurelia web application calling azure mobile services API endpoint

I am creating an Aurelia web project that will consume an API. The API is housed as an Azure Mobile Service. I know I need to add the X-ZUMO Auth header to the request. But, when I instantiate my http client, that header never makes it to the request headers according to the browser dev tools.When I run this in my application, I am presented with a login screen I presume because the X-ZUMO header is not present so the app doesn't have permissions to run. I am running this using gulp which is setting up a local instance of the web application for me. I also tried the external IP address.
Here is my class:
import {HttpClient} from 'aurelia-http-client';
export class WebApi
{
static inject = [HttpClient];
constructor(http)
{
this.http = http;
this.baseUrl = 'https://myProject.azure-mobile.net/';
this.zumoHeader = 'X-ZUMO-APPLICATION';
this.zumoKey = 'zumoKey';
this.client = new HttpClient()
.configure(x => {
x.withBaseUrl(this.baseUrl);
x.withHeader(this.zumoHeader, this.zumoKey);
});
}
// requests will go here
testGet()
{
this.client.jsonp('api/logs?application_id=sessionID&__count=20')
.then(response =>
{
alert(response);
});
}
}
Turns out, you can't use the jsonp method in this instance. The get method(if you're making a get request) is the only way the headers will get added.
After this, I had to make sure the server could handle CORS as well.
import {HttpClient} from 'aurelia-http-client';
export class WebApi
{
static inject = [HttpClient];
constructor(http)
{
this.http = http;
this.baseUrl = 'https://myProject.azure-mobile.net/';
this.zumoHeader = 'X-ZUMO-APPLICATION';
this.zumoKey = 'zumoKey';
this.client = new HttpClient()
.configure(x => {
x.withBaseUrl(this.baseUrl);
x.withHeader(this.zumoHeader, this.zumoKey);
});
}
// requests will go here
testGet()
{
this.client.get('api/logs?application_id=sessionID&__count=20')
.then(response =>
{
alert(response);
});
}
}

Resources