I am getting error Property 'admin' does not exist on type 'Request<{}, any, any, ParsedQs, Record<string, any>>'. Also nodejs app will not run as it fails with the same error. I am using typescript
app.get('/users', auth, (req, res) => {
console.log(` User is admin = ${req.admin}`);
console.log('Users Page');
res.send('Users Page');
});
function auth(req, res, next) {
if (req.query.admin === 'true') {
req.admin = true;
next();
} else {
res.send('No auth');
}
}
Make also sure that you properly structure the URL something like this http://localhost:3000/users?admin=true
import { NextFunction, Request, Response } from 'express';
app.get('/users', auth, (req: Request, res: Response) => {
console.log(` User is admin = ${req.admin}`);
console.log('Users Page');
res.send('Users Page');
});
declare module 'express-serve-static-core' {
export interface Request {
admin: boolean;
}
}
function auth(
req: Request<{}, {}, {}, {admin: boolean}>,
res: Response,
next: NextFunction) {
if (req.query.admin === 'true') {
req.admin = true;
next();
} else {
res.send('No auth');
}
}
Related
interface CustomRequest<T> extends Request {
user?: IUser,
body: T
}
interface CreateBody {
email:string;
}
interface deleteBody {
id:string;
}
router.post('/create', authenticate, (req:CustomRequest<CreateBody>, res) => {
Content.Create({
email:req.body.email
})
}
router.post('/delete', authenticate, (req:CustomRequest<deleteBody>, res) => {
Content.Create({
id:req.body.id
})
}
export const authenticate = async (
req: CustomRequest<null>
res: Response,
next: NextFunction,
) => {
try {
const user = await User.findToken(req.cookies.auth);
if (!user) return res.send({ success: false });
req.user = user;
next();
} catch (err) {
return res.send({ success: false });
}
};
I want to specify the request type by situation when designating middleware in nodejs.
req:CustomRequest
I've tried generics this way, but it doesn't work.
How can I define the type gracefully?
I would like to specify the type that some api have an e-mail in body and some api have a password in it.
I am creating a router with ExpressJs (using TypeScript) and thrown exceptions are not caught by my custom error handler, here is the code:
In index.ts file (which is the main):
import express, { Express } from 'express';
import cors from 'cors';
import { config } from '~/utils/config';
import { NotFoundRoute } from '~/middlewares/NotFound';
import { ExceptionHandler } from '~/middlewares/ExceptionHandler';
import { usersRouter } from '~/controllers/users';
const app: Express = express();
app.use(express.json());
app.use(cors());
app.use('/users', usersRouter);
app.all('*', NotFoundRoute);
app.use(ExceptionHandler);
app.listen(config.port, () => console.log("API is running on port " + config.port));
The custom exception handler:
import { NextFunction, Request, Response } from 'express';
export const ExceptionHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
if (res.headersSent)
return next(err);
if (err.status && err.message) {
res.status(err.status).send({ error: err.message });
} else
return res.status(500).send({ message: "Internal Server Error" });
}
The router that is problematic (where InternalServerError is not caught by the above):
import { Request, Response, Router } from 'express';
import { User } from '~/schemas/User';
import { InternalServerError } from '~/errors/InternalServerError';
const usersRouter = Router();
usersRouter.get('/', async (req: Request, res: Response) => {
try {
const users = await User.find({});
res.status(200).send(users);
} catch (err) {
res.status(500).send({ message: "Failed to fetch users"});
}
})
usersRouter.post('/register', async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
if (!email || !password)
res.status(403).send({ message: "Bad Request"});
const newUser = User.createUser({ email, password });
await newUser.save();
res.status(201).send(newUser);
} catch (err) {
throw new InternalServerError();
}
})
export { usersRouter };
All my exception are not caught which means that it is not exception-related.
In the index.ts file, the NotFoundRoute throws an exception that is caught, so I guess it works on the file's context. How it is not working ? I suppose the router has a thrown exception that would be caught but it is not.
To remind the context, I am trying to force errors to happen to see if the error handling is correct. So in this case I forced
newUser.save()
to fail.
Express cannot handle thrown exceptions or rejected Promises. To tell express that there is an error you need to call the next() function instead of using throw or rejecting a Promise:
usersRouter.post('/register', async (req: Request, res: Response, next: Function) => {
try {
const { email, password } = req.body;
if (!email || !password)
res.status(403).send({ message: "Bad Request"});
const newUser = User.createUser({ email, password });
await newUser.save();
res.status(201).send(newUser);
} catch (err) {
next(new InternalServerError()); // This is how you "throw" errors
// in Express
}
})
Note that express only expect you to call the function it passes as the third argument. The name of the argument is up to you. You can use words other than next:
usersRouter.post('/register', async (req: Request, res: Response, error: Function) => {
try {
/* some logic */
} catch (err) {
error(new InternalServerError());
}
})
Though traditionally the name of the third argument is next and most javascript developers expect it to be that.
Adding to slebetman's answer: You can throw errors that will be handled by the exception handler, but only in synchronous middleware. You can try it out:
app.use("/sync", function(req, res) {
throw "sync";
})
.use("/async", async function(req, res) {
throw "async";
})
.use(function(err, req, res, next) {
res.end("Caught " + err);
});
I am trying to integrate passport in NestJS and to get current auth info anywhere using a decorator. My NestJs version was so old, I updated the NestJS version and changed to the code below. After I changed middleware code, I can no longer get current auth info.
How to get auth info using the changed middeware?
Code before changing the middleware:
export class authMiddlware implements NestMiddleware {
async resolve(): Promise<MiddlewareFunction> {
return async (req, res, next) => {
passport.authenticate('jwt', { session: false }, (err, user, info) => {
if (err) {
return next(err);
}
if (user) {
req.user = user;
}
return next();
})(req, res, next);
};
}
After changing the middleware:
In passport.authenticate I can get the changed user from the request, but out of passport, I cannot get the user from the request.
#Injectable()
export class authMiddlware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
passport.authenticate('jwt', { session: false }, (err, user, info) => {
if (err) {
return next(err);
}
if (user) {
req.user = user;
}
console.log(req.user)
next();
})(req, res, next);
console.log(req.user)
};
}
The user.decorator:
export const passUser= createParamDecorator((data, req) => {
return plainToClass(User, req.user);
});
I had a problem with Passport's authenticate method. The callback supplied to the method was not called.
The code was as follows:
import { Router, Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import passport from 'passport';
// ... some stuff
router.post('/login',
(req: Request, res: Response, next: NextFunction) => {
passport.authenticate('local', { session: false }, (err, user, info) => {
// not reaching this part
if (err) {
return next(err);
}
if (err || !user) {
return res.status(400).json({
message: info ? info.message : 'Login failed',
user,
});
}
req.login(user, { session: false }, loginErr => {
if (loginErr) {
res.send(loginErr);
}
const token = jwt.sign(user, secrets.secret);
return res.json({ user, token });
});
return res.status(500).send('Shouldn\'t come here');
});
},
(err: any, req: Request, res: Response, next: NextFunction) => {
return res.status(err.status || 500).send(err.message)
});
I missed a crucial part of the example I based my code on. I had to use passport.authenticate as middleware. So, I used passport.authenticate to generate a function and then passed req, res and next to it like so:
router.post('/login',
(req: Request, res: Response, next: NextFunction) => {
// vvv THIS VARIABLE
const middleware = passport.authenticate('local', { session: false }, (err, user, info) => {
if (err) {
return next(err);
}
if (err || !user) {
return res.status(400).json({
message: info ? info.message : 'Login failed',
user,
});
}
req.login(user, { session: false }, loginErr => {
if (loginErr) {
res.send(loginErr);
}
const token = jwt.sign(user, secrets.secret);
return res.json({ user, token });
});
return res.status(500).send('Shouldn\'t come here');
});
// vvv AND THIS FUNCTION CALL
middleware(req, res, next);
},
(err: any, req: Request, res: Response, next: NextFunction) => {
return res.status(err.status || 500).send(err.message)
});
And everything worked.
I am following a middleware chaining example from this question.
I have a route app.put('/users/:id', isAuthenticated, (req, res) => {db.updateUser(req.params.id, req.body)}. I am trying to write a middleware function that verifies that the ID provided in the URL matches the ID retrieved from the JWT included with the request.
I already have a function isAuthenticated that verifies the JWT and sets res.locals.userId to the UID retrieved; so I would like to simply make use of that in this new function canModifyTarget but for some reason the request hangs forever:
// This function works fine
isAuthenticated: function(req, res, next) {
let token;
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
admin.auth().verifyIdToken(token).then((decodedToken) => {
res.locals.userId = decodedToken.uid;
return next();
}).catch((error) => {
return res.status(HttpStatus.UNAUTHORIZED).send();
})
}
}
// Switching out isAuthenticated for this in the route causes a permanent hang
canModifyTarget: function(req, res, next) {
console.log('This is printed');
return (req, res, next) => {
console.log('This is NOT printed');
isAuthenticated(req, res, () => {
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
})
}
}
middlewares should be callback functions that call "next()" once finished.
Your first function, when executed, is calling next() (eventually, after your promise is resolved)
Your second function isn't calling next(), it is just returning a function definition.
Define it like this
canModifyTarget: function(req, res, next) {
isAuthenticated(req, res, () => {
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
})
}
}
and if the third parameter of isAuthenticated is a callback, it should work
Also, you should define an "else" case in your isAuthenticated function, otherwise it will hang as well (maybe throw an exception or something?)
If you need to reference them, store them in variables rather than directly defining them in your module.exports:
const isAuthenticated = function(req, res, next) {
// code here
}
const canModifyTarget: function(req, res, next) {
// code here
}
module.exports = {
isAuthenticated,
canModifyTarget,
};
I think simpler is to define canModifyTarget as one more middleware. I.e:
function canModifyTarget(req, res, next) {
console.log('This is NOT printed');
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
}
and then just apply it after isAuthenticated middleware:
app.put(
'/users/:id',
isAuthenticated,
canModifyTarget,
(req, res) => {db.updateUser(req.params.id, req.body)}
);
Hope it helps.
I am just writing a solution where I needed to unify two kind of auth middlewares: password-based and apikey-based into one middleware: unifiedOrgAuth middleware.
So, basically this would enable me to just put unifiedOrgAuth middleware on those routes which need either the password-based or apikey-based auth.
The key thing was to pass the next function from the umbrella middleware to the underlying middleware by just calling the underlying middleware with the next function of the umbrella middleware:
unified auth middleware:
function unifiedOrgAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req: RequestWithOrg, _res: Response, next: NextFunction) => {
const cookies = req.cookies;
if (cookies && cookies.Authorization) {
(userAuthMiddleware(path, perm))(req, _res, next);
return;
}
const apiKey = req.header('X-API-KEY');
if (apiKey && apiKey.length > 0) {
(apiAuthMiddleware(path, perm))(req, _res, next);
return;
}
return next(new Error401Exception());
// Make linter happy.
};
}
Here are the underlying middlewares:
password-based auth middleware:
function userAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req, _res, next) => {
try {
const cookies = req.cookies;
if (!(cookies && cookies.Authorization)) {
next(new Error401Exception());
// Make linter happy.
return;
}
if (!validCookies(cookies)) {
next(new Error401Exception());
// Make linter happy.
return;
}
} catch (error) {
next(new Error401Exception());
// Make linter happy.
return;
}
next();
};
}
api-based auth middleware:
function apiAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req: RequestWithOrg, _res: Response, next: NextFunction) => {
const apiKey = req.header('X-API-KEY');
if (!apiKey) {
next(new Error401Exception());
// Make linter happy.
return;
}
if (!validApiKey(apiKey)) {
next(new Error401Exception());
// Make linter happy.
return;
}
next();
};
}