(Express, Node, TypeScript, REST API) How can I import a function from the service to the controllers? Property ... does not exist on type typeof - node.js

How should I import loginMember in Controller? I am developing a REST API and now I need to use code in a different file location. I am having an error in the controller. When I am calling loginMember. (Cannot find name 'loginMember'.ts(2304))
SERVICE
import MembersModel from '../models/MembersModel';
import BaseService from './BaseService';
import { createPasswordToHash } from '../scripts/utils/auth';
class MembersService extends BaseService {
constructor() {
super(MembersModel);
}
// loginMember
loginMember = async (email: any, password: any) => {
return new Promise(async (resolve, reject) => {
try {
let data = await this.BaseModel.findOne({
email: email,
password: createPasswordToHash(password),
});
return resolve(data);
} catch (error) {
return reject(error);
}
});
};
}
export default MembersService;
CONTROLLER
import BaseController from './BaseController';
import MembersService from '../services/MembersService';
import ApiError from '../errors/ApiError';
import { NextFunction, Request, Response } from 'express';
import { createPasswordToHash, generateAccessToken } from '../scripts/utils/auth';
import httpStatus from 'http-status';
class MembersController extends BaseController {
constructor(membersService: MembersService) {
super(membersService);
}
login = (req: Request, res: Response, next: NextFunction) => {
MembersService.loginMember(req.body)
.then((response: any) => {
if (response) {
const member = {
...response.toObject(),
accessToken: generateAccessToken(response.toObject()),
};
delete member.password;
delete member.createdAt;
delete member.updatedAt;
return res.status(httpStatus.OK).send(member);
}
return res.status(httpStatus.UNAUTHORIZED).send({ error: 'Invalid email or password' });
})
.catch((err: { message: string }) => {
return next(
new ApiError(err.message, httpStatus.UNAUTHORIZED, 'login', req.headers['user-agent']?.toString() || 'Unknown')
);
});
};
}
export default new MembersController(new MembersService());
Now I am gettig a new error: "Property 'loginMember' does not exist on type 'typeof MembersService'.ts(2339)"

You're trying to call loginMember as a static method, but it's not defined as one. You'll have to use an instance of MembersService to use the method. Since your MembersController is already being initialized with a MembersService instance, you may just want to have a membersService property on the MembersController. Also, the loginMember method takes an email and a password, so you'll have to pass those arguments explicitly instead of just passing the request body. (I'm not sure where the email and password are in the request body though, so I can't help you there.) So with those changes, it would look like:
class MembersController extends BaseController {
private membersService: MembersService;
constructor(membersService: MembersService) {
super(membersService);
this.membersService = membersService;
}
login = (req: Request, res: Response, next: NextFunction) => {
this.membersService.loginMember(email, password) // <- Get these from the request
.then((response: any) => {
if (response) {
const member = {
...response.toObject(),
accessToken: generateAccessToken(response.toObject()),
};
delete member.password;
delete member.createdAt;
delete member.updatedAt;
return res.status(httpStatus.OK).send(member);
}
return res.status(httpStatus.UNAUTHORIZED).send({ error: 'Invalid email or password' });
})
.catch((err: { message: string }) => {
return next(
new ApiError(err.message, httpStatus.UNAUTHORIZED, 'login', req.headers['user-agent']?.toString() || 'Unknown')
);
});
};
One other code style suggestion would be to use async await instead of .then in the login method. Also, the Promise wrapping in the loginMember method looks unnecessary, and using an async function as the argument is an antipattern. The following should get the job done while avoiding those pitfalls:
loginMember = (email: any, password: any): Promise<Response> => {
return this.BaseModel.findOne({
email: email,
password: createPasswordToHash(password),
});
};

Related

Using Fastify preHandler middleware

Passing a middleware to authenticate user before accessing this route.
When I'm passing tokenController.authUser as a middleware tokenService inside tokenController is undefined. However when I run this method as a function inside the route instead of a middleware it works fine.
server.post('/api/admin/test', { preHandler: [tokenController.authUser] }, async (request: any, reply: any) => {
return null
});
Token Controller :-
import { Users } from "#prisma/client";
import ITokenService from "../../services/tokenService/ITokenService";
import ITokenController from "./ITokenController";
export default class TokenController implements ITokenController {
private readonly tokenService: ITokenService;
constructor(_tokenService: ITokenService) {
this.tokenService = _tokenService;
}
async authUser(request: any, reply: any): Promise<Users | Error> {
const authHeader = request.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token === null)
return reply.code(401);
try {
const result = await this.tokenService.verifyToken(token);
console.log(result);
return result;
}
catch (e) {
reply.code(401);
return new Error("Error");
}
}
}
Token Service :-
import { Users } from "#prisma/client";
import ITokenService from "./ITokenService";
export default class TokenService implements ITokenService {
private readonly sign: Function;
private readonly verify: Function;
private readonly secretKey: string;
constructor(sign: Function, verify: Function, _secretKey: string) {
this.sign = sign;
this.verify = verify;
this.secretKey = _secretKey;
}
public async generateToken(user: Users): Promise<string> {
return await this.sign({ user }, this.secretKey);
}
public async verifyToken(token: string): Promise<Users | Error> {
const result = await this.verify(token, this.secretKey);
return result;
}
}
For some reason making a separate middleware function and calling tokenController.authUser inside that method works fine.
const middleware = (_req, _res, next) => {
console.log('middleware');
next()
}
server.post('/api/admin/test', { preHandler: [middleware] }, async (request: any, reply: any) => {
return null
});

How to pass jwt to prisma middleware in nestjs

I am using nestjs, graphql, & prisma. I am trying to figure out how to pass my jwt token for each database request to the prisma service iv created. Iv tried an object to the constructor but then wont compile saying I am missing a dependency injection for whatever I reference in the constructor paramter.
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleDestroy {
constructor() {
super();
//TODO how do I pass my jwt token to this for each request?
this.$use(async (params, next) => {
if (params.action === 'create') {
params.args.data['createdBy'] = 'jwt username goes here';
}
if (params.action === 'update') {
params.args.data['updatedBy'] = 'jwt username goes here';
}
const result = await next(params);
return result;
});
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Are you using a nest middleware?
JWT is normally passed to a Controller, not a service.
Example:
#Injectable()
export class MyMiddleware implements NestMiddleware {
private backend: any // This is your backend
constructor() {
this.backend = null // initialize your backend
}
use(req: Request, res: Response, next: any) {
const token = <string>req.headers.authorization
if (token != null && token != '') {
this.backend
.auth()
.verifyIdToken(<string>token.replace('Bearer ', ''))
.then(async (decodedToken) => {
const user = {
email: decodedToken.email,
uid: decodedToken.uid,
tenantId: decodedToken.tenantId,
}
req['user'] = user
next()
})
.catch((error) => {
log.info('Token validation failed', error)
this.accessDenied(req.url, res)
})
} else {
log.info('No valid token provided', token)
return this.accessDenied(req.url, res)
}
}
private accessDenied(url: string, res: Response) {
res.status(403).json({
statusCode: 403,
timestamp: new Date().toISOString(),
path: url,
message: 'Access Denied',
})
}
}
So every time I get an API call with a valid token, the token is added to the user[] in the request.
In my Controller Class I can then go ahead and use the data:
#Post()
postHello(#Req() request: Request): string {
return 'Hello ' + request['user']?.tenantId + '!'
}
I just learned about an update in Nest.js which allows you to easily inject the header also in a Service. Maybe that is exactly what you need.
So in your service.ts:
import { Global, INestApplication, Inject, Injectable, OnModuleInit, Scope } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
import { REQUEST } from '#nestjs/core'
#Global()
#Injectable({ scope: Scope.REQUEST })
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor(#Inject(REQUEST) private readonly request: any) {
super()
console.log('request:', request?.user)
}
async onModuleInit() {
// Multi Tenancy Middleware
this.$use(async (params, next) => {
// Check incoming query type
console.log('params:', params)
console.log('request:', this.request)
return next(params)
})
await this.$connect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
As you can see in the log output, you have access to the entire request object.

How to implement class-validator as a middleware?

I have been trying to use class-validator as middleware to validate some of my data.
I would love to get some advice as to
how can I also validate updates and what's a good validation
Here is the current class validator to validate the req.body sent when trying to register.
export default async (req: Request, res: Response, next: NextFunction) => {
let user = new User();
user.username = req.body.username;
user.email = req.body.email;
user.password = req.body.password;
let fieldErrors = await validate(user);
if (fieldErrors.length > 0) {
let errors = ValidatorErrToFieldErr(fieldErrors);
next(new HttpExeception({ statusCode: httpCode.BAD_REQUEST, errors }));
} else {
next();
}
};
What is a good validation pattern? I controller that handles some of the logic which in turn calls the service to mutate the database.
AuthController.ts
public static Register = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
req.body.password = await argon2.hash(req.body.password);
let modelUser = await service.addUser(req.body);
let user: IUserMe = {
//reasign user fields
};
req.session.user = {
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
};
res.json({ user });
} catch (error) {
if (error.code === "23505") {
next(
new HttpExeception({
statusCode: httpCode.BAD_REQUEST,
errors: duplicationErrToFieldError(error.detail),
})
);
} else next(new HttpExeception({ statusCode: httpCode.SERVER_ERROR }));
}
};
UserService.ts
async addUser(input: IRegisterInput): Promise<User> {
return await getRepository(User).save(input);
}
So most middleware it’s called inside the routes themselves. I’ll use an example with Express, Passport, and TypeScript since it’s what I know best.
Say i don’t want users to access my “/home” page without signing in. So I write a middleware function:
export default ( req: Request, res: Response, next: NextFunction): void => {
if(req.user != undefined){
next();
}
else{
res.status(401);
}
}
This would be analogous to your class-validator function. Now, we need to make sure that this function runs before any API calls are made to “/home”.
Thus, we write the api route as
import * as express from “express”;
import {Request, Response} from “express”;
import isAuthenticated from “isAuthenticated.ts”;
class HomeRouter{
public path = “/”;
public router = App.router();
constructor(){
this.initRoutes();
}
public initRoutes(){
this.router.get(“/home”, isAuthenticated, (req: Request, res: Response) => {
res.send(“/index.html”);
}
}
}
This will force isAuthenticated to run before any of the logic in the rest of route is executed. If you would like the middleware to apply to every call to the server, just put express.use(isAuthenticated); in your server.ts file. If you’re using a technology different from Express that I’ve failed to identify, I’m sure the premise is the same, and the how will be in the documentation.

NestJS: Receive form-data in Guards?

I'm looking to see form-data in my NestJS Guards. I've followed the tutorial, however, I'm not seeing the request body for my form-data input. I do see the body once I access a route within my controller, however.
Here's some code snippets of what I'm working with:
module.ts
...
#Module({
imports: [
MulterModule.register({
limits: { fileSize: MULTER_UPLOAD_FILESIZE_BYTES },
}),
],
controllers: [MainController],
providers: [
MainService,
AuthGuard,
],
})
...
AuthGuard.ts
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Observable } from 'rxjs';
#Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest(); // body is empty if form-data is used
return true;
}
}
MainController.ts
...
#Post("/upload")
#UseInterceptors(AnyFilesInterceptor())
#UseGuards(AuthGuard)
async upload(
#Body() body: UploadDTO,
#UploadedFiles() files: any[]
): Promise<any> {
console.log(body) // works as expected, whether form-data is used or not
...
}
...
Any feedback would be greatly appreciated!
NestJS guards are always executed before any middleware. You can use multer manually on the request object you get from the context.
import * as multer from 'multer'
...
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest();
const postMulterRequest = await new Promise((resolve, reject) => {
multer().any()(request, {}, function(err) {
if (err) reject(err);
resolve(request);
});
});
// postMulterRequest has a completed body
return true;
}
If you want to use the #UploadedFiles decorator, you need to clone the request object before modifying it in your guard.
Of course you need to have installed the multer module with:
npm install multer
Posting my solution in-case it helps other devs dealing with the same issue.
To start, I created a middleware to handle the conversion of the multipart form data request. You could also inline this in to your guard if you only have one or two. Much of this code is plagiarised from the source code, and is not fully tested:
const multerExceptions = {
LIMIT_PART_COUNT: 'Too many parts',
LIMIT_FILE_SIZE: 'File too large',
LIMIT_FILE_COUNT: 'Too many files',
LIMIT_FIELD_KEY: 'Field name too long',
LIMIT_FIELD_VALUE: 'Field value too long',
LIMIT_FIELD_COUNT: 'Too many fields',
LIMIT_UNEXPECTED_FILE: 'Unexpected field',
}
function transformException(error: Error | undefined) {
if (!error || error instanceof HttpException) {
return error
}
switch (error.message) {
case multerExceptions.LIMIT_FILE_SIZE:
return new PayloadTooLargeException(error.message)
case multerExceptions.LIMIT_FILE_COUNT:
case multerExceptions.LIMIT_FIELD_KEY:
case multerExceptions.LIMIT_FIELD_VALUE:
case multerExceptions.LIMIT_FIELD_COUNT:
case multerExceptions.LIMIT_UNEXPECTED_FILE:
case multerExceptions.LIMIT_PART_COUNT:
return new BadRequestException(error.message)
}
return error
}
#Injectable()
export class MultipartMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
// Read multipart form data request
// Multer modifies the request object
await new Promise<void>((resolve, reject) => {
multer().any()(req, res, (err: any) => {
if (err) {
const error = transformException(err)
return reject(error)
}
resolve()
})
})
next()
}
}
Then, I applied the middleware conditionally to any routes which accept multipart form data:
#Module({
controllers: [ExampleController],
imports: [...],
providers: [ExampleService],
})
export class ExampleModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(MultipartMiddleware).forRoutes({
path: 'example/upload',
method: RequestMethod.POST,
})
}
}
Finally, to get the uploaded files, you can reference req.files:
#Controller('example')
export class ExampleController {
#Post('upload')
upload(#Req() req: Request) {
const files = req.files;
}
}
I expanded this in my own codebase with some additional supporting decorators:
export const UploadedAttachment = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
return request.files?.[0]
}
)
export const UploadedAttachments = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
return request.files
}
)
Which ends up looking like:
#Controller('example')
export class ExampleController {
#Post('upload')
upload(#UploadedAttachments() files: Express.Multer.File[]) {
...
}
}

User is not available in request with NestJS passport strategy (other than documentation exemples)

I'm trying to implement a passport strategy (passport-headerapikey), I was able to make it work and I can secure my routes.
But the request is empty and cannot access the logged in user ?
import { HeaderAPIKeyStrategy } from "passport-headerapikey";
import { PassportStrategy } from "#nestjs/passport";
import { Injectable, NotFoundException } from "#nestjs/common";
import { CompanyService } from "../../companies/companies.service";
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy, "api-key") {
constructor(private readonly companyService: CompanyService) {
super(
{
header: "Authorization",
prefix: "Api-Key "
},
true,
async (apiKey, done) => {
return this.validate(apiKey, done);
}
);
}
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
throw new NotFoundException("Company not found");
}
return company;
}
}
#UseGuards(AuthGuard("api-key"))
export class CompaniesController {
constructor(private companyService: CompanyService) {}
#Get()
#ApiOperation({ title: "Get company information" })
public getCompany(#Request() req) {
// here request is empty, so i cannot access the user..
console.log("request", req);
return [];
}
}
Thanks for your help !
To access the logged user, you can inject the object in the request. To do that, in your ApiKeyStrategy constructor, change the third parameter to something like this:
async (apiKey, verified, req) => {
const user = await this.findUser(apiKey);
// inject the user in the request
req.user = user || null;
return verified(null, user || false);
}
Now, you can access the logged user:
getCompany(#Request() req) {
console.log(req.user);
}
I hope that could help you.
As show in the documentation you should do some works to get the current user : here the documetation
First of all in the app.module make sure that the context is set :
context: ({ req }) => ({ req })
Then you can add this in the controller/resolver, this example use the Gql (GraphQL):
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
if this one doesnt work for you try this one instead :
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext();
request.body = ctx.getArgs();
return request.user;
},
);
Modify your validate method like so:
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
return done(new NotFoundException("Company not found"), null);
}
return done(null, company);
}

Resources