Fastify pass custom parameter to preHandler - node.js

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));

Related

fastifyGuard not working (role based authentication)

I have a fastify project, where I use fastify-jwt to create tokens for the users. Now I want to have a role based authentication system. I found the fastifyGuard plugin but I register it correctly, nevertheless it is not working in the routes file.
What I currently have
async function routes(fastify, options, next) {
const Collection = require("$/App/Controllers/Collection/Controller");
fastify.get(
"/test/admin",
{
preValidation: [fastify.authenticate],
},
Collection.testFunction
);
}
I provide a bearer token and it works perfectly.
Than I add the fastifyGuard preHandler:
async function routes(fastify, options, next) {
const Collection = require("$/App/Controllers/Collection/Controller");
fastify.get(
"/test/admin",
{
preValidation: [fastify.authenticate],
preHandler: [fastify.guard.role('admin')]
},
Collection.testFunction
);
}
and the app crashes. So I tried to debug it, and in the routes file fastify.guard is undefined.
Thanks for any kind of help.

How can I access route params in Nestjs middleware

I'm creating a middleware in nestjs and here is how the structure looks like
export class TestModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(TestMiddleware)
.forRoutes('/test/:id')
}
}
I'm using fastify Adaptor and I've a question that how can I access the route param in my middleware.
I've tried accessing req.params but it comes empty and can't see any other property having the route param in it.
UPDATE
Apparently I've found a way to write middleware in fastify which allowed me to access proper fastify request body and response body with params resolved.
For that I've used the package called fastify-plugin
In the middleware file you can use it like this
export default fastifyPlugin(
function (fastify, options, next: VoidFunction) {
fastify.addHook('onRequest', (req, res, done) => {
done();
});
next();
},
{
name: 'auth',
},
);

Apollo Server executeOperation with authorization headers

How do we pass headers to Apollo server executeOperation in tests?
There is mention about passing a headers object here
I'm trying to pass an auth header with or without a JWT token to test access control.
const result = await server.executeOperation({ query: query, http: { headers: { authorization: "" } } })
// Type '{ authorization: string; }' is not assignable to type 'Headers'.
// Object literal may only specify known properties, and 'authorization' does not exist in type 'Headers'.ts(2322)
This results in a type error. There is a Headers class defined in the Apollo server types in fetch.d.ts but I'm un able to import to instantiate it.
Using "apollo-server": "^2.25.2". Any hints or links to get this going?
Update: as a work around I'm decrypting and decoding the JWT in the server context and passing an authenticated user around in there. Then I'm able to mock the whole context and create a new test server with the mocked context. It'd be nice to be able to user headers for more production like experience but this works for now.
import { mockDeep, mockReset } from 'jest-mock-extended'
interface Context {
prisma: PrismaClient
user: () => User|null
}
export const context = mockDeep<Context>()
export const testServer = new ApolloServer({
typeDefs,
resolvers,
context
});
// ...
context.user.mockReturnValue({
id: 1,
name: "Foo",
slug: "foo",
})
const res = await testServer.executeOperation({ query: query })
Apollo Server now supports a second parameter for executeOperation. It is possible to provide the parameters used in the context function.
const result = await server.executeOperation(
{ query: query },
{ req: { headers: { authorization: '...' } } }
);
Note: With Typescript, it will complain but you can cast it in any and avoid building the whole express.Request type.
Tested with apollo-server-core#3.5.0
The other solution works but I find this one convenient. If you mock the token parsing, you can easily simulate calls from different users.
To solve this, I have created a 'fake' request object with the already decrypted JWT token that I used to initialize my third party auth object (keycloak) and pass to the apollo context. I need to initialize the keycloak object because I have authentication schema directives that require the keycloak object to be initialized.
const req = {
kauth: {
grant: {
access_token: {
isExpired: () => {
return false;
},
token: "abc",
content: {
email: "me#me.com",
resource_access: {
"my-api": {
roles: ["admin"],
},
},
},
},
},
},
};
server = new ApolloServer({
schema,
resolvers,
dataSources: () => ({
users,
}),
context: () => {
return { kauth: new KeycloakContext({ req }) };
},
});
});
Still, I would like a solution that is more native to apollo and not a work around.

How to add custom middleware to express-openapi-validator using Swagger 3

I've got a Node app using express-openapi-validator that takes a an api spec file (which is a .yml file), with request and response validation. The express-openapi-validator package routes the request to a handler file (defined in the spec). This is what one of the handlers might look like:
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
I've got an API key feature, where users can get a new API key, and the other endpoints that need the caller to have the API key in the request headers to validate the request.
I know it should be possible to use middleware to validate the request, but I can't figure out how to use custom middleware with the express-openapi-validator package on select endpoints.
For eg:
GET /apikey = does not require api key
GET /resource = requires api key
How do I configure this?
Here's what the openapi validator code in my app.js looks like:
new OpenApiValidator({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
})
.install(app)
.then(() => {
app.use((err, _, res) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
});
I actually ended up finding a solution for this myself.
First of all, I'm using version 4.10.5 of express-openapi-validator, so the code above is slightly different.
Here's what it looks like now:
// index.js
app.use(
OpenApiValidator.middleware({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
}),
);
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
The way I ended up using middleware in my routes is below:
I've added a securitySchemes section in my swagger.yml file, like so:
components:
securitySchemes:
verifyApiKey:
type: apiKey
in: header
name: x-api-key
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
There's a bit more information about it here: https://swagger.io/docs/specification/authentication/
On each route that needs the middleware, I'm adding a security section, like so:
/team:
post:
security:
- bearerAuth: []
description: Create a new team
operationId: createTeam
x-eov-operation-id: createTeam
x-eov-operation-handler: team
As you can see in my code above (in the index.js file), I've got a validateSecurity key, with a handlers key that then has the correlating keys that are in my swagger.yml (verifyApiKey and bearerAuth). These functions get the request and scope to check if they're valid. These functions return a boolean value, so true means that the middleware lets the request through, and false means a 403 response will be returned.
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
Please respond if I've got anything above wrong, or if the explanation can be clearer. If you have questions, please post them below.
You can simply pass array of handlers instead of just 1 function, like in express.
So in you code, the getUsers function that probably is what the x-eov-operation-id refers to, would be an array of 2 functions:
const getUsers = [
apiKeyMiddleware,
(req, res) => {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
];
I was in a similar situation as you, using OpenAPI/Swagger packages like that limited my ability to add specific middleware per endpoint, so my solution was I created an npm module called #zishone/chaindler.
You can use it like this:
const { Chain } = require('#zishone/chaindler');
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
function postUsers(req, res) {
// ...
}
function mw1(req, res, next) {
next()
}
function mw2(req, res, next) {
next()
}
module.exports = {
getUsers: new Chain(mw1, mw2).handle(getUsers),
postUsers: new Chain(mw1).handle(postUsers)
}
Basically it just chains the middlewares then calls them one by one then call the handler/controller last.

Inject HttpContext into InversifyJS middleware

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.

Resources