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.
Related
I am implementing the three-legged OAuth twitter-api-v2 flow. My first call from my frontend is to the route containing twitterCallback(). This uri is then generated, and the oauth_token_secret is taken and saved to session - I am using express-session.
public twitterCallback = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const client = this.createTwitterClient();
const { url :uri, oauth_token, oauth_token_secret } = await.client.generateAuthLink(CALLBACK_URL);
req.session = req.session || {} as any;
req.session.oauth_token_secret = oauth_token_secret;
req.session.oauth_token = oauth_token;
res.status(200).json({ uri });
} catch (error) {
next(error);
}
};
However the following call which has the user return, sees the oauth_token in the query, but the oauth_token_secret isn't displaying any value. I can see that the session id is different - I presume because the session cookie for the first request is set on the frontend app, and then on the 2nd request its origin is twitter instead.
public loginWithTwitter = async (req: RequestWithTwitter, res: Response, next: NextFunction): Promise<void> => {
try {
const client = new TwitterApi({
appKey: CONSUMER_KEY,
appSecret: CONSUMER_SECRET,
accessToken: req.query.oauth_token,
accessSecret: req.session.oauth_token_secret,
});
const login = await client.login(req.query.oauth_verifier)
.then(({ client: loggedClient, accessToken, accessSecret }) => {
console.log(loggedClient, accessSecret, accessToken);
})
.catch((e) => console.error(e));
res.status(200).json({ login, message: 'Logged in with twitter with ' });
} catch (error) {
next(error);
}
};
How do I ensure that I can safely keep the session value for oauth_token_secret across different requests, despite the point of request origin being different?
Any tips please - driving me mad because I know I am missing something obvious!
I have explored the idea of trying to set the session id to the frontend on the twitter side.
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),
});
};
Im using Router classes to manage all my Routes:
const router = express.Router();
/**
* User Sign up Route at /api/auth/register
*/
router.post(
"/register",
checkBodyParameters(['username', 'email', 'password']),
verifyRegister.ensurePasswordStrength,
verifyRegister.checkUsernameAndEmail,
AuthController.register
);
export = router;
I want to check the x-www-form-urlencoded body parameters. To see if either the key is not what it should be, or the value is empty.
I wrote a middleware function to check that:
import { Request, Response } from "express";
export default function checkBodyParameters(
bodyParams: Array<string>,
req: Request,
res: Response,
next
) {
let requestBodyParams: Array<string> = [];
requestBodyParams.push(req.body.username, req.body.email, req.body.password);
requestBodyParams.forEach((requestBodyParam) => {
if (bodyParams.includes(requestBodyParam)) {
if (requestBodyParam !== "") {
next();
} else {
res.status(400).json({
message: "Paremeter cant be empty",
value: requestBodyParam,
});
}
} else {
res
.status(400)
.json({ message: "Paremeter not specified", value: requestBodyParam });
}
});
}
But it seems like it doesnt like me passing Arguments to the middleware function in
checkBodyParameters(['username', 'email', 'password'])
My Question is how do i create a middleware function which acceppts more values than req, res and next? And how to use this function correctly with the router instance.
Any Feedback is appreciated
You are calling the function instead of returning a function as a middleware.
Instead, use:
const checkBodyParameters = (
bodyParams: Array<string>
) => (
req: Request,
res: Response,
next
) => {
let requestBodyParams: Array<string> = [];
requestBodyParams.push(req.body.username, req.body.email, req.body.password);
requestBodyParams.forEach((requestBodyParam) => {
if (bodyParams.includes(requestBodyParam)) {
if (requestBodyParam !== "") {
next();
} else {
res.status(400).json({
message: "Paremeter cant be empty",
value: requestBodyParam,
});
}
} else {
res
.status(400)
.json({ message: "Paremeter not specified", value: requestBodyParam });
}
});
}
export default checkBodyParameters
im currently working on a express application that uses typescript. Im currently working on a Authentication Middleware and was wondering if you can make the Middlewares typesafe in a way:
authenticateJwt = (
req: RequestWithToken,
res: Response,
next: () => void
) => {
// Append the decoded req.token to the req header so we can use it internally
const token = req.token;
// #ts-ignore
this.verifyJwt(token)
.then((decoded: Token) => {
req.token = decoded;
next();
})
.catch(() => res.status(401).send('Unauthorized'));
};
now in my routes.ts:
router.get(
'/me',
// #ts-ignore
jwtService.authenticateJwt,
userController.getProfileFromUser
);
I have to write // #ts-ignore because it says that '(req: RequestWithToken, res: Response, next: () => void) => void is not of type RequestHandlerParams
definition of RequestWithToken:
export interface RequestWithToken extends Request {
token: Token;
}
export interface Token {
username: string;
}
create a custom.d.ts
and overwrite the Request Interface of express and express-serve-static-core
declare module 'express' {
interface Request {
token: Token;
}
}
declare module 'express-serve-static-core' {
interface Request {
token: Token;
}
}
this way both the RequestHandlerParams(useually your controller) and RequestHandler(useually your Middleware) are getting your new Request Interface.
then add it to the files section of your tsconfig.json:
"files": [
"src/custom.d.ts"
]
Have you tried :
const token = req.token as Token;
I am using Typescript in Node.js. When you use Express middleware, you often transform the Request object. With Typescript, however, we could not track how the Request object was transformed. If you know the middleware that passed before, is there a way to find out the type of the request from it? If not possible in express, I would like to find another framework where it is possible. Is it possible in Nest (https://github.com/kamilmysliwiec/nest)?
Example Code
import { Request, Response, NextFunction } from 'express';
function userMiddleware(req: Request & User, res: Response, next: NextFunction) {
req.user = {
id: 'user_id',
};
next();
}
interface User {
user: {
id: string;
}
}
interface Middleware {
<T>(req: Request & T, res: Response, next: NextFunction): void;
}
class Controller {
middleware = [userMiddleware];
get = new GetMethod(this.middleware);
post = (req: Request /* I don't know exact req type */, res: Response, next: NextFunction) => {
console.log(req.user) // Error!
}
}
class GetMethod {
constructor(middleware: Middleware[]) {
// How to deduce type of req from Middleware array?
}
}
const controller = new Controller();
express.use('/', controller.middleware, controller.post);
I want to extract type information from Middleware list in Controller class.
First I think the right interface is
interface User {
id: string;
}
Because they're callbacks they'll receive default Request that don't have user in its signature.
Therefore you have 2 options, do a type assertion, or to write a custom declaration. Both a fine if you do them properly.
Type assertion:
interface User {
id: string;
}
const isObject = (value: unknown): value is {[key: string]: unknown} => {
return value && typeof value === 'object';
};
const isReqWithUser = (req: Request): req is Request & {user: User} => {
return isObject(req) && !!req.user;
}
class Controller {
post = (req: Request, res: Response, next: NextFunction) => {
if (isReqWithUser(req)) {
console.log(req.user) // now it works
}
next();
}
}
Custom declaration:
but we need to understand that user not always exist on the request and we should mark it optional.
interface User {
id: string;
}
declare module 'express' {
export interface Request {
user?: User; // adding our custom declaration.
}
}
class Controller {
post = (req: Request, res: Response, next: NextFunction) => {
console.log(req.user) // now it works
next();
}
}