I'm setting up a custom provider that dependes on some request properties. And I wonder if it's possible to achieve this without changing scope to Scope.REQUEST given that the request is undefined inside the factory if the scope is not REQUEST.
What is the best approach to access request from a factory under Scope.DEFAULT?
app.module.ts
...
#Module({
...
providers: [
...
{
provide: 'MY_TOKEN',
useFactory: (req: Request) => {
...
const myFunc = (arg1, arg1) => {
...
return result;
}
return myFunc;
}
}
]
})
EDIT: This is not possible without making the factory (and therefore the provider) REQUEST scoped. You basically want to make a new configuration per request, and to do that it must be REQUEST scoped
Add inject: [REQUEST], just like you would #Inject(REQUEST) for a constructor. The inject is essentially the tokens to be injected into the factory function, just like the constructor is what normally would define what is injected in a regular provider
...
#Module({
...
providers: [
...
{
provide: 'MY_TOKEN',
inject: [REQUEST],
useFactory: (req: Request) => {
...
const myFunc = (arg1, arg1) => {
...
return result;
}
return myFunc;
}
}
]
})
This will make whatever injects MY_TOKEN REQUEST scoped as well, so make sure you are aware of that
Related
I'm just learning fastify and I'm not sure how to achieve what I want:
I have this route:
this.fastify.get('/ping', {
preHandler: [
this.fastify.jwtVerify,
],
}, this.configHandler.getConfiguration.bind(this.configHandler));
The pre handler does get executed and contains the known parameters like the request and the reply.
I want to pass a custom parameter to my preHandler function. Currently the preHandler is verifying the jwt token passed in the auth header. What I want to achieve is to pass scopes to the handler which may also be checked.
My preHandler currently is a plugin registered like this:
const jwtVerifyPlugin: FastifyPluginAsync = async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
fastify.decorate('jwtVerify', async function (request: FastifyRequest, reply: FastifyReply) {
//jwtVerficiation happens here
//scope verification should follow
})
}
So overall: I have to add scopes somewhere at the route and I have to get those scopes somwhere inside my preHandler.
Any idea how I can do that?
Thanks!
You can define your decorate function like this:
const jwtVerifyPlugin: FastifyPluginAsync = async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
fastify.decorate('jwtVerify', function (options?: { scopes?: string[] }) {
return async function (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) {
if (options?.scopes) {
// access scopes here
}
done();
};
})
}
and then use it like this:
this.fastify.get('/ping', {
preHandler: [
this.fastify.jwtVerify({ scopes: ['admin'] }),
],
}, this.configHandler.getConfiguration.bind(this.configHandler));
I have Nest.js app, where certain provider is injected into another provider:
export class AppService {
public constructor(private readonly appInnerService: AppInnerService) {}
}
AppService has a method publish which calls appInnerService send method. I created unit tests for AppService, where I want to mock AppInnerService provider:
describe('App service', () => {
let appService: AppService;
const appInnerService = {
send: jest.fn(),
};
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [AppService, AppInnerService],
})
.overrideProvider(AppInnerService)
.useValue(appInnerService)
.compile();
appService = moduleRef.get<AppService>(AppService);
});
it('should work', () => {
appService.publish({});
expect(appInnerService.send).toHaveBeenCalled();
});
}
Above code doesn't work, AppInnerService isn't injected into AppService, instead undefined is passed to constructor. Why above test isn't working and how can I fix it (without manually creating AppService class with mocked service, I want to use testing module created by #nestjs/testing package)?
For closure on the problem: AppService needed to be decorated with #Injectable() to get typescript to reflect the constructor parameter metadata. With just Injectable() as was there, it's just a function call and not a decorator, so the metadata was not reflected and Nest could not act upon it.
I'm implementing an application to generate pdf using angular server side rendering. In there I want to use request body inside the angular components, this is the code sample I trying
server.ts file
server.post('/api/v1/pdf', (req, res) => {
// res.status(404).send('data requests are not yet supported');
res.render(indexHtml, { req, providers: [
{ provide: APP_BASE_HREF, useValue: req.baseUrl },
{ provide: PDFRequestService, useValue: req}
]
});
});
service to access request data
pdf-request.service.ts
#Injectable()
export class PDFRequestService {
constructor(#Inject(REQUEST) private request: Request) {}
get requestBody(): {workflowId: string} {
return this.request.body;
}
}
and import it to the app.server.module.ts
#NgModule({
imports: [
AppModule,
ServerModule,
],
providers: [
// Add server-only providers here.
PDFRequestService
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
But I don't know how to access this PDFRequestService inside the angular application, when I import the service to app.module.ts file throwing an error saying null Injecttor for REQUEST, but it's in the express engine.
Does anyone knows how to access the request data inside angular application before rendering in server side?
Finally found a way to parse the request body to angular application.
In the route controller put the a provider like below(I was using body-parser to take in request body as json)
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [
{ provide: APP_BASE_HREF, useValue: req.baseUrl },
{ provide: 'body', useValue: req.body}]
});
});
and inside AppModule we cam access it as below
export class AppModule {
constructor(
#Optional() #Inject('body') private body: any
) {
console.log(`body`, body);
}
That all. But there is another problem, when we use angular ssr rendering process happening two times, one is the server and other one in the client, so we will loose the data in client side. To solve that we can use below modules
ServerTransferStateModule
BrowserTransferStateModule
TransferState
You can simply pass the request from the server using providers.
server.get('*', async (req, res) => {
var data = 'any data';
res.render(indexHtml, {
req,
providers: [
{provide: APP_BASE_HREF, useValue: req.baseUrl},
{provide: 'REQUEST', useValue: req},
{provide: 'RESPONSE', useValue: res},
{provide: 'data', useValue: data},
],
});
});
Not only the request and response but you can also send custom data to angular components using the same process.
And in the component you can access these datas by using Inject()
constructor(
#Optional() #Inject(REQUEST) private request: Request<any>,
#Optional() #Inject(RESPONSE) private response: Response,
#Optional() #Inject('data') private data: any,
) {
}
This mechanism typically passes the data to angular components from the express server.
I have the following controller.
#controller('/users')
class UsersController {
#httpGet('/', authMiddleware({ role: 'ADMIN' }))
public get() { ... }
}
I have implemented a custom AuthenticationProvider, which returns a principal containing details about the currently authenticated user, including the user's roles.
....
return new Principal({
firstName: "John",
lastName: "Smit",
roles: ["ADMIN"]
});
...
This all works fine, but I am wondering how I can retrieve the principal from the authMiddleware which is used by the above GET route.
For now I have an ugly hack which uses internals of InversifyJS.
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext: interfaces.HttpContext =
Reflect.getMetadata(
"inversify-express-utils:httpcontext",
req
);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
The custom authentication provider uses the authorization header to authenticate the user and returns a principal. I don't want to do this work again in the middleware, I just want to retrieve the principal.
This hack works, but I was wondering if someone knows a cleaner way of obtaining the HttpContext in this middleware.
I know you can access the HttpContext and thus the principal (user) if you extend from the BaseMiddleware, but then it's not clear to me how you pass configuration (parameters) to it, such as the desired role. Related to the following issue on InversifyJS.
https://github.com/inversify/InversifyJS/issues/673
This is not supported, but I can see why it is needed. We cannot pass the httpContext to the middleware as an argument because we want to keep the standard Express middleware compatible. This means that the only option is doing something like what you have done but ideally we should encapsulate it using some helper.
We need to implement something like the following getHttpContext function:
import * as express from "express";
import { getHttpContext } from "inversify-express-utils";
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext = getHttpContext(req);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
Until this is implemented I don't see any problems with your implementation other than the information leakage of the inversify internals.
I'm trying to make secure my GraphQL endpoint with passportJS in order that every call to this endpoint uses the AuthGuard for validating the token and setting the user on request.user, just as it does in a controller with this code:
#Get('findAll')
#UseGuards(AuthGuard('jwt'))
findAll(#Req() request): Promise<Array<Thing>> {
return this.thingService.findByUser(request.user.email);
}
The thing is I want to use it in the graphQL endpoint, which is created like this:
consumer
.apply(graphiqlExpress({ endpointURL: '/graphql' }))
.forRoutes('/graphiql')
.apply(
graphqlExpress(req => ({ schema, rootValue: req })),
¿?,
)
.forRoutes('/graphql');
I suppose I can just set it like a middleware function after the graphqlExpress function, but I have not been successful. Any thoughts?
Thank you in advance!
Edit
As a workaround I have implemented the solution proposed on Nest Docs where it uses the #UseGuard in every query/mutation that must be protected.
However, I want to protect the entire endpoint so that the guard is not called for every protected resolver, but only once on the main request. Is this even possible?
This technically is possible, but it's a pretty sloppy thing to write, and there's absolutely no guarantees it will work with Fastify so heads up. The meat of the functionality comes from the module where you implement the middleware. I ended up doing this all with the AppModule which I do not suggest (at least not all of the code there), but it works nonetheless.
You need to make the guard a custom provider so it can be injected into any context.
Then you need to mock up the ExecutionContext using req, res, next. This is easier said than done if you want type safety, but if you don't care about that (which I didn't for this) then slap up an as any and call it a day.
After that, in the middleware consumer you run the apply and make use of this.guard.canActivate with that mock ExecutionContext you created. Make this middleware async and await the canActivate call. Check that it comes back as true and if not then throw new <ErrorOfYourChoice>() and boom. It's set up. The code would look (vaguely) like this:
import {
BadRequestException,
CanActivate,
Inject,
MiddlewareConsumer,
Module,
NestModule,
} from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppResolver } from './app.resolver';
import { GraphQLModule } from '#nestjs/graphql';
import { JwtModule } from '#nestjs/jwt';
import { AuthGuard, PassportModule } from '#nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
#Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: true,
}),
JwtModule.register({ secret: 'secret' }),
PassportModule.register({ defaultStrategy: 'jwt' }),
],
controllers: [AppController],
providers: [
AppService,
AppResolver,
JwtStrategy,
{ provide: 'CustomGuard', useClass: AuthGuard() },
],
})
export class AppModule implements NestModule {
constructor(#Inject('CustomGuard') private readonly guard: CanActivate) {}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(async (req, res, next) => {
const canActivate = await this.guard.canActivate({
switchToHttp: () => ({
getRequest: () => req,
getResponse: () => res,
getNext: () => next,
}),
} as any);
if (canActivate) {
next();
} else {
throw new BadRequestException();
}
})
.forRoutes('graphql');
}
}
You can check this repository for everything wired up and working. Login with POST /login -d 'username=test1&password=changeme', get the JWT and play around with it as you like.
However, I want to protect the entire endpoint so that the guard is not called for every protected resolver, but only once on the main request. Is this even possible?
I was able to get a middleware function to resolve on every query/mutation by using the reference global approach from NestJS here: https://docs.nestjs.com/graphql/field-middleware#global-field-middleware.