How can I make an "isAdmin" verification middleware? - node.js

I have an authentication middleware, where I check through my route if the user is authenticated in the header with the security token
middlewareAuth
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface TokenPayload {
id: string;
iat: number;
exp: number;
}
export default async function authMiddleware(
req: Request, res: Response, next: NextFunction
) {
const { authorization } = req.headers;
if (!authorization) {
return res.status(401).json('Invalid Authorization');
};
const token = authorization.replace('Bearer', ' ').trim();
try {
const secret = process.env.JWT_SECRET as string;
const data = jwt.verify(token, secret);
const { id } = data as TokenPayload;
req.userId = id;
return next();
} catch (err) {
return res.status(401).json(err);
}
}
I import this middleware in my route, thus verifying as I describe below:
import { Router } from "express";
import CreatePost from "../controllers/postControllers/CreatePost";
import authMiddleware from "../middlewares/AuthMiddlewares";
const router = Router();
router.post('/', authMiddleware, CreatePost.store);
export default router;
Within my user entity, I have a boolean called "isAdmin" which by default returns false. I would like a middleware that checks if also within my profile isAdmin is true and thus authenticate and put in the route along with AuthMiddleware. For that I tried to import my AuthMiddleware and verify, but without success.
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import authMiddleware from './AuthMiddlewares';
export default async function authAdminMiddleware(
req: Request, res: Response, next: NextFunction,
) {
authMiddleware(req, res, () => {
if (req.userId.isAdmin) {
next();
} else {
res.status(403).json("You are not alowed to do that!");
}
})
}
Property 'isAdmin' does not exist on type 'string'.ts(2339)
What would be the most effective way to create this middleware? Can anyone give me some help? Thanks

Related

Error creating verifyTokenAdmin middleware

I'm trying to create two middlewares, an authentication middleware and a middleware that checks if the user in question has "isAdmin" true inside its database.
The authentication middleware works perfectly, but to make the admin middleware work, I tried to put req.users = data; inside, but it returns me the error:
Property 'users' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.ts(2339)
auth middleware:
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface TokenPayload {
id: string;
iat: number;
exp: number;
}
export default async function authMiddleware(
req: Request, res: Response, next: NextFunction
) {
const { authorization } = req.headers;
if (!authorization) {
return res.status(401).json('Invalid Authorization');
};
const token = authorization.replace('Bearer', ' ').trim();
try {
const secret = process.env.JWT_SECRET as string;
const data = jwt.verify(token, secret);
req.users = data;
const { id } = data as TokenPayload;
req.userId = id;
return next();
} catch (err) {
return res.status(401).json(err);
}
}
In auth middleware admin, in users it returns the same error
Property 'users' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.ts(2339)
auth middleware admin
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import authMiddleware from './AuthMiddlewares';
export default async function authAdminMiddleware(
req: Request, res: Response, next: NextFunction,
) {
authMiddleware(req, res, () => {
if (req.users.isAdmin) {
next();
} else {
res.status(403).json("You are not alowed to do that!");
}
})
}
You can use declaration merging to declare the additional property users in your req object:
declare global {
namespace Express {
interface Request {
users: any
}
}
}
(See also here.)

having trouble getting user authorization variables working

In am having trouble passing the token information to my controller for authorization. In the below code, I have console logged the relevant information and I am able to get the token correctly, and the decoded information, but not the req.user information. When I console.log for that information I receive null, and when I console.log for decoded.id, I get undefined. I believe this is what is hanging up my authorization, however I'm not sure what to look at to fix it? Any thoughts very helpful!
Here's a github link:https://github.com/roxanneweber/projectmanager
const jwt = require('jsonwebtoken');
const asyncHandler = require('express-async-handler');
const User = require('../models/userModel');
const protect = asyncHandler(async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
try {
// Get token from header
token = req.headers.authorization.split(' ')[1];
console.log(token);
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log(decoded);
// Get user from token
req.user = await User.findById(decoded.id).select('-password');
console.log(req.user);
console.log(decoded.id);
next();
} catch (error) {
console.log(error);
res.status(401);
throw new Error('Not authorized');
}
}
if (!token) {
res.status(401);
throw new Error('Not authorized');
}
});
module.exports = { protect };
hi i am handling the token like this in my auth.middleware.ts
import jwt from 'jsonwebtoken';
export default function (req, res, next) {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
// add userData object to request
req.userData = {
email: decodedToken.email,
userId: decodedToken.userId,
username: decodedToken.username,
role: decodedToken.role,
};
next();
} catch (error) {
return res.status(401).json({
message: 'not authenticated',
});
}
}
then my frontend handles setting the token like this:
import { HttpInterceptor, HttpRequest, HttpHandler, HttpHeaders } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { AuthenticationService } from '../services/authentication.service';
export interface HttpConfig {
body?: any;
headers?: HttpHeaders;
observe?: any;
}
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthenticationService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const authToken = this.authService.getToken();
const authRequest = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + authToken),
});
return next.handle(authRequest);
}
}
then i am using the auth.middleware.ts in my backend route files like this:
import express from 'express';
import authMiddleware from '../middleware/auth.middleware';
import FooController from './foo.controller';
class FooRoutes {
router = express.Router();
fooController = FooController;
constructor() {
this.configureRoutes();
}
configureRoutes() {
this.router.post('/foo/start', authMiddleware, this.fooController.start);
this.router.put('/foo/stop/:id', authMiddleware, this.fooController.stop);
this.router.get('/foo/:userId', authMiddleware, this.fooController.getAll);
this.router.delete('/foo/delete/:id', authMiddleware, this.fooController.delete);
}
}
export default new FooRoutes().router;

Chai test doesn't pass the cookie authentification middleware

I have a middleware that checks if a user is loggued-in before letting him access a route. It looks like this:
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
const authenticate = (req: Authenticate, res: Response, next: NextFunction) => {
const token = req.cookies;
if (token) {
jwt.verify(token, process.env.JWT_TOKEN_KEY, (error, res) => {
if (error) return res.sendStatus(403);
req.cookie = { _id: res._id, locale: res.locale };
return next();
});
}
return res.sendStatus(401);
};
export default authenticate;
The test is:
import chai from "chai";
import chaiHttp from "chai-http";
import { server } from "../index";
chai.use(chaiHttp);
const api = chai.request(server).keepOpen();
describe("GET /user/:id", () => {
it("return user information", () => {
api
.get("/user/123")
.set("Cookie", "_id=567;locale=en")
.end(function (err, res) {
chai.expect(res).to.have.status(200);
});
});
});
The test works fine if I remove the middleware from the route. So:
// OK
router.post("/user/:id", searchUser);
// NOT OK
router.post("/user/:id", authenticate, searchUser);
The error is:
TypeError: Cannot read property 'sendStatus' of undefined
at /Users/myname/Desktop/Code/myapp/server/src/middleware/authenticate.ts:21:29 at Object.module.exports [as verify] (/Users/myname/Desktop/Code/myapp/server/node_modules/jsonwebtoken/verify.js:57:12)
The res variable of the jwt.verify() callback function overwrites the res variable of the authenticate middleware, which is why you get this error. You should give it a different name to avoid conflicts.
I think you got the req.cookies from chai-http. If you didn't, you probably forgot to use cookie-parser middleware.
E.g.
index.ts:
import express from 'express';
import cookieParser from 'cookie-parser';
import authenticate from './mws/authenticate';
const server = express();
server.use(cookieParser());
server.get('/user/:id', authenticate, (req, res) => {
const { id } = req.params;
res.json({ id, name: 'teresa teng' });
});
export { server };
authenticate.ts:
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
const authenticate = (req: Request, res: Response, next: NextFunction) => {
const token = req.cookies;
console.log('cookies: ', req.cookies);
if (token) {
jwt.verify(token, process.env.JWT_TOKEN_KEY, (error, verifyResponse) => {
if (error) return res.sendStatus(403);
req.cookies = { _id: verifyResponse._id, locale: verifyResponse.locale };
return next();
});
}
return res.sendStatus(401);
};
export default authenticate;
index.test.ts:
import chai from 'chai';
import chaiHttp from 'chai-http';
import { server } from './';
chai.use(chaiHttp);
const api = chai.request(server).keepOpen();
describe('GET /user/:id', () => {
it('return user information', (done) => {
api
.get('/user/123')
.set('Cookie', '_id=567;locale=en')
.end(function (err, res) {
chai.expect(res).to.have.status(200);
done();
});
});
});
logs:
GET /user/:id
cookies: { _id: '567', locale: 'en' }
1) return user information

How to set a jwt cookie with mocha/chai?

I need to test protected routes with mocha and chai. The authentication middleware crashes all the time with an error 500 because jwt must be a string.
When the user logs-in, he receives an http-only cookie whose data is a jwt token. The cookie is automatically sent to the server on each request thanks to axios withCredentials: true property.
So, I've tried to set a cookie or an auth bearer to my test, but without any success so far. How to fix this?
Here is what I've written:
import chai from "chai";
import chaiHttp from "chai-http";
import { app } from "../index";
import jwt from "jsonwebtoken";
chai.use(chaiHttp);
const api = chai.request(app).keepOpen();
const token = jwt.sign(
{ _id: "123", locale: "en" },
process.env.JWT_TOKEN_KEY,
{
expiresIn: "14d",
}
);
describe("GET /user/:id", () => {
it("return user information", (done) => {
api
.get("/user/123")
.set("Cookie", token)
.end((err, res) => {
chai.expect(res).to.have.status(200);
done();
});
});
});
The middleware is:
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { Cookie } from "../models/Cookie";
interface Authenticate extends Request {
cookie: Cookie;
cookies: any;
}
const authenticate = (req: Authenticate, res: Response, next: NextFunction) => {
const token = req.cookies;
if (token) {
jwt.verify(token, process.env.JWT_TOKEN_KEY, (error, res) => {
if (error) {
return res.sendStatus(403);
}
req.cookie = { _id: res._id, locale: res.locale };
return next();
});
}
return res.sendStatus(401);
};
export default authenticate;
I've been stuck on testing my api routes since 48 hours. Any help would be highly appreciated.
req.cookies is an object, from the express docs:
When using cookie-parser middleware, this property is an object that contains cookies sent by the request. If the request contains no cookies, it defaults to {}.
So you need to set the jwt inside the cookie in this way:
.set('Cookie', `jwtToken=${token}`)
And get it in your middleware:
const { jwtToken } = req.cookies;
jwt.verify(jwtToken, ...

Correct way of checking if a jwt token has expired in passport jwt

I have a created a middleware to check if the jwt token has expired.
For some reason the TokenHasExpiredError is not intercepted as an error in passport.authenticate.
Here's my middleware
import { Request, Response, NextFunction } from 'express';
import passport from 'passport';
import { ApiError } from '../classes/error';
const authJwt = (req: Request, res: Response, next: NextFunction) => {
passport.authenticate('jwt', function (err, user, info) {
// TokenExpiredError is not considered as an error?
if (err) return next(err);
if (!user) {
if (info.name === 'TokenExpiredError') {
return next(ApiError.jwtTokenExpired('Jwt Token Expired'));
}
return next(ApiError.unauthenticated('User is not authenticated'));
}
req.user = user;
next();
})(req, res, next);
};
export default authJwt;
I have a couple of questions:
From the code above, why is TokenExpiredError or any other error not intercepted as an error.
What is the info parameter in the callback? I can't find it in the documentation.

Resources