Create an auth.js middleware - fastify

I have created an auth.js middleware with fastify and prisma but I don't know how to insert it in my route. Here are some examples
const jwt = require("jsonwebtoken");
require("dotenv").config();
module.exports = (request, reply) => {
try {
const token = request.headers.authorization.split(" ")[1];
const decodedToken = jwt.verify(token, process.env.SECRET_TOKEN);
request.token = decodedToken;
} catch (error) {
reply.status(401).send({
message: "Vous êtes pas authentifié",
});
}
};
const profilCtrl = require("../../controller/user");
const auth = require("../../middleware/auth");
async function routes(fastify) {
fastify.get("/profil/:id", profilCtrl.profile);
}
module.exports = routes;

You can add your auth function as a preHandler hook like this:
fastify.addHook('preHandler', (request, reply, done) => {
// some code
done()
})
or like this:
fastify.route({
method: 'GET',
url: '/profil/:id',
preHandler: fastify.auth([fastify.yourMiddleware]),
handler: (req, reply) => { ... }
})
Looking at your code I'm not totally clear on if it represents multiple files or what exactly is going on. You might want to break it up into separate blocks of code with file names to clarify your question.

Related

How can I test authentication middleware with Jest

I'm learning nodejs and for the most part its going well. Im trying to learn how to do mocking in tests with jest. I've watched numerous tutorials but I cant seem to get my head around it.
I have this middleware that is used on protected routes...
import jwt from 'jsonwebtoken';
export default function (req, res, next) {
const token = req.header('x_auth-token');
if (!token) return res.status(401).json({ message: 'Access denied' });
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
} catch (err) {
return res.status(400).send('Invalid Token');
}
}
From what I've read, I think the approach Im supposed to to take is something like this...
import verifyToken from '../middleware/verifyToken';
test('verifyToken', () => {
expect.assertions(1);
const res = {};
const req = {};
const next = (err) => expect(err).toBeFalsy();
verifyToken(req, res, next);
});
However this clearly doesnt work.
So how do I mock the request header with a token?
So if we comletely forget about what req, res are in the real world, obviously they are request and response, but lets just forget about that for now.
in your real code you have token = req.header("x_auth-token")
So in our test code, we need to have something in the req object that when called with those parameters returns what you want.
So I would say.
const req = {
header: jest.fn(() => 'myAuthToken')
}
the jest.fn() has mad a mock function and when it is invoked it will always return the string myAuthToken.
We can then check that the header function has been called with the correct params by adding
expect(req.header).toHaveBeenCalledWith("x_auth-token")
You are also going to need to mock jwt.verify as you are not testing that jwt.verify works as that will be covered it its own tests. You will want to make sure that you are using the response of that correctly
To do that take a look at this stack overflow question
And finally I would set next as a mock function
mockNext = jest.fn()
Then we can say in the test
expect(mockNext).toHaveBeenCalled()
So I was having trouble understandin how to mock functions. I did a bunch of googling based on on Ollie Pugh's answer and this is what I came up with.
import jwt from 'jsonwebtoken';
import verifyToken from '../middleware/verifyToken';
import { uIds } from './testdata/userTestData';
describe('verifyToken tests', () => {
const { uid1 } = uIds;
it('Should pass the userId to the request object if token is verified', () => {
const res = {};
const req = {
header: jest.fn(() => 'myAuthToken'),
};
const next = jest.fn();
const verify = jest
.spyOn(jwt, 'verify')
.mockReturnValueOnce({ userId: String(uid1) });
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).toEqual({ userId: String(uid1) });
expect(next).toHaveBeenCalled();
});
it('Should deny access if token is not present in header', () => {
const res = {
json(msg) {
expect(msg).toEqual({ message: 'Access denied' });
},
status(responseStatus) {
expect(responseStatus).toEqual(401);
return this;
},
};
const req = {
header: jest.fn(),
};
const next = jest.fn();
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).not.toEqual({ userId: String(uid1) });
});
it('Should deny access if token is invalid', () => {
const res = {
send(text) {
expect(text).toEqual('Invalid Token');
},
status(responseStatus) {
expect(responseStatus).toEqual(400);
return this;
},
};
const req = {
header: jest.fn(() => 123),
};
const next = jest.fn();
const verify = jest.fn();
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).not.toEqual({ userId: String(uid1) });
});
});
This passes the tests. However I'm not sure about the validity of the test.

Node.js : How to use token with multer?

I want to run two same servers. And I want to upload a image from one to the other. These sources worked well but I felt that verifying a token is needed for security.
I will use the function createUploadToOther of settings.js to send image to the other server. I usually write a router like
router.post('/fromOther', verifyToken, function (req, res) {
But in this case, I don't know where to put that verifyToken. Could u let me know how to use a token with the multer header(?) in this case?
file.js
let express = require("express");
let router = express.Router();
let path = require('path');
let uploadDir = 'static'
let fs = require('fs');
const verifyToken = require('../libs/verifyToken')
const axios = require('axios')
const FormData = require('form-data')
let multer = require('multer');
let storageForSentFile = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, uploadDir);
},
filename: function (req, file, callback) {
callback(null, file.originalname);
}
})
let uploadForSentFile = multer({ storage: storageForSentFile })
router.post('/fromOther', uploadForSentFile.single('logo'), function (req, res) {
console.log("------file upload -------")
console.log(req.file)
res.json(req.file)
});
router.post('/toOther', uploadForSentFile.single('logo') , async function (req, res) {
let formData = new FormData()
formData.append('logo', fs.createReadStream(req.file.path), { knownLength: req.file.size })
const headers = {
...formData.getHeaders(),
"Content-Length": formData.getLengthSync()
};
try {
let result = await axios.post(
new URL('/file/fromOther', req.body.serverURL).href,
formData,
{headers }
)
res.status(200).json(result.data)
} catch (err) {
console.log('file/toOther err', err)
}
})
../libs/verifyToken.js
let jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
function verifyToken(req, res, next) {
console.log("req.headers1 : ", req.headers);
let token = req.headers["authorization"];
if (!token)
return res.json({ status: 409, message: 'No authorization' })
jwt.verify(token, process.env.aSecretKey, function (err, decoded) {
if (err)
return res
.status(500)
.send({ auth: false, message: "Check your ID and password" });
console.log("decoded", decoded);
req.account = decoded.account;
req.idx = decoded.idx;
next();
});
}
module.exports = verifyToken;
settings.js
import axios from 'axios'
import { headers } from '../config/env'
export function createUploadToOther(data) {
return axios.post('/file/toOther', data, { headers })
}
Function verifyToken in router('/fromOther') checks just req.headers["authorization"], so I just added the function verifyToken to both router as argument(in fact don't need to add verifyToken to router('/toOther') but wanted to make it safer) and added the key "authorization" and the value as req.headers.authorization to router('/toOther'). Then it worked well, but I don't know this will be safe enough.
//(...)
router.post('/fromOther', verifyToken, uploadForSentFile.single('logo'), function (req, res) {
console.log("------file upload -------")
console.log(req.file)
res.json(req.file)
});
router.post('/toOther', verifyToken, uploadForSentFile.single('logo') , async function (req, res) {
let formData = new FormData()
formData.append('logo', fs.createReadStream(req.file.path), { knownLength: req.file.size })
const headers = {
...formData.getHeaders(),
"Content-Length": formData.getLengthSync(),
"authorization": req.headers.authorization
};
try {
let result = await axios.post(
new URL('/file/fromOther', req.body.serverURL).href,
formData,
{headers }
)
res.status(200).json(result.data)
} catch (err) {
console.log('file/toOther err', err)
}
})
//(...)

Express Cookie Session 'undefined'

After I define a cookie in an express cookie session, I can log it to the console with ease. However, when I attempt to access this cookie in another route of my application, it returns 'undefined'.
Setting the cookie:
router.get('/callback', catchAsync(async (req, res) => {
console.log("in /callback");
if (!req.query.code) throw new Error('NoCodeProvided');
const code = req.query.code;
const creds = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
var response = await fetch(`https://discordapp.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirect}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${creds}`,
},
});
var json = await response.json();
req.session.token = json.access_token
console.log(req.session.token)
>>> RETURNS THE TOKEN CORRECTLY <<<
Attempting to access the cookie in another route:
router.get('/loggedin', catchAsync(async (req, res) => {
console.log("/loggedin");
console.log("token: " + req.session.token);
>>> RETURNS 'token: undefined' <<<
In the first router.get('/callback'..) the catchAsync() function is not declared globally. It just handle this specific route, and doesn't really require a name.
You should wrap this function inside a middleware or create a function which is available globally, I don't know what is the goal but here is the 2 option.
Option 1 initiate the functionality as a middleware. The behaviour is depends on where you place it!!!! Maybe in that case doesn't fully makes sense, but you can play around, but I think you will get it.
// if you put before your router initiation it is going to have effect on all of the routes
app.use(async(req, res, next) => {
if (!req.query.code) throw new Error('NoCodeProvided');
const code = req.query.code;
const creds = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
var response = await fetch(`https://discordapp.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirect}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${creds}`,
},
});
var json = await response.json();
req.session.token = json.access_token
console.log(req.session.token)
//
// and you can do whatever want to do
// but have to call next
//
next()
})
// and your router looks like
router.get('/callback', (req, res) => {
// do what have to do
})
Option 2 - declare the middleware and use where you want
// defining this middleware somewhere in the code
const catchAsync = async(req, res, next) => {
if (!req.query.code) throw new Error('NoCodeProvided');
const code = req.query.code;
const creds = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
var response = await fetch(`https://discordapp.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirect}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${creds}`,
},
});
var json = await response.json();
req.session.token = json.access_token
console.log(req.session.token)
//
// and you can do whatever want to do
// but have to call next
//
next()
}
router.get('/callback', catchAsync, (req, res) => {
// do what have to do
})
router.get('/loggedin', catchAsync, (req, res) => {
// do what have to do
})

cannot able to verify the twitter details in nodejs?

I used this link https://dev.twitter.com/apps I followed the procedure and I got the consumer key,consumer secret key,access token key,access token secret details.I put it in my code.I ran in localhost It shows **twitter.verifyCredentials is not a function.**I enclosed the code can anyone tell me what is the problem in the code.Thanks in advance...
var twitter=require("twitter");
var tweeter = new twitter(
{
consumer_key:'xyz',
consumer_secret:'abx',
access_token_key:'asd',
access_token_secret:'www'
});
app.get('/twitterCheck', function (req, res) {
twitter.verifyCredentials(function (error, data)
{
res.send("Hello, " + data.name + ". I am in your twitters.");
});
});
app.listen(8086,function()
{
console.log("port is listen on 8086");
});
This is from node-twitter-api package. Try using node-twitter-api, This will work:
const express = require('express')
const app = express()
const twitterAPI = require('node-twitter-api');
const twitter = new twitterAPI({
consumerKey: '*****',
consumerSecret: '******',
});
const accessToken = "***********";
const accessTokenSecret = "***********";
const params = {};
app.get('/twitterCheck', function (req, res) {
twitter.verifyCredentials(accessToken, accessTokenSecret, params, function (error, data, response) {
if (error) {
console.log(error);
} else {
console.log(data["screen_name"]);
}
});
});
app.listen(8086, function () {
console.log("port is listen on 8086");
});

How to test express middleware that depends on other vendor middleware?

I wanna test a middleware function that inside calls a vendor middleware function. The middleware is:
const expressJwt = require('express-jwt');
const validateJwt = expressJwt({ secret: 'whatever' });
exports.isAuthenticated = (req, res, next) => {
if (req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = `Bearer ${req.query.access_token}`;
}
validateJwt(req, res, next);
};
I've tried to create a sinon.spy() object and pass it as next parameter, but is not called apparently.
Another approach I've tried is to check if exists req.user, since the purpose of the express-jwt middleware is to validate and attach user to the req object. No luck with this neither.
I've seen the existence of chai-connect, but not sure how to use it.
Any ideas? Highly appreciate it!
I finally managed to do it with proxyquire and chai-connect:
In your mocha config:
chai.use(require('chai-connect-middleware'));
global.connect = chai.connect;
In your test:
describe('isAuthenticated', () => {
// Wrap call to chai.connect into a function to use params and return a Promise
const mockedMiddleware = (changeSecret) => {
let oldToken;
if (changeSecret) {
oldToken = acessToken;
acessToken = 'blabalblalba';
}
return new Promise((resolve, reject) => {
connect.use(middleware.isAuthenticated)
.req(req => {
req.query = { access_token: acessToken };
})
.next((res) => {
acessToken = oldToken;
if (res && res.status === 401) {
reject(res.message);
} else {
resolve();
}
})
.dispatch();
});
};
it('should validate correctly', () =>
mockedMiddleware().should.be.fulfilled
);
it('should not validate', () =>
mockedMiddleware(true).should.be.rejected
);
});

Resources