sending csrfToken and set-cookie yet still 403 invalid csrf token - jestjs

I'm am trying to get an integration test using jest (test environment : node) to pass, for a login form which uses csurf for csrf protection (using the cookie option).
I've extracted the csrfToken from the login form and the set-cookie headers yet the test still fails with a 403 - invalid csrf token.
I can't see what the issue is and would appreciate a steer in the right direction.
test file :
const request = require('supertest');
const {User} = require('../../server/models/user');
const cheerio = require('cheerio');
const app = require('../../app');
let user, csrfToken, password, cookies;
beforeEach( async () => {
user = await new User({
firstName: "Name",
lastName: "Surname",
email: "email#example.com",
password: "password",
isAdmin : true
}).save();
});
afterEach( async () => {
await User.deleteMany();
});
describe('/login', () => {
describe('GET /', () => {
const exec = async () => {
const res = await request(app).get(`/login`);
let $ = cheerio.load(res.text);
csrfToken = $('[name=_csrf]').val();
return res;
};
it('should return the login form', async () => {
const res = await exec();
expect(res.status).toBe(200);
expect(res.text).toMatch(/Sign In/);
});
});
describe('POST /', () => {
const getLoginCsrfs = async () => {
const res = await request(app).get(`/login`);
let $ = cheerio.load(res.text);
csrfToken = $('[name=_csrf]').val();
cookies = res.headers['set-cookie'];
return res;
};
const postLogin = async () => {
return request(app).post(`/login`)
.set('Cookie', cookies)
.send({ email: user.email,
password: password,
_csrf: csrfToken
});
};
it('should return 401 without incorrect user info', async () => {
await getLoginCsrfs();
password = 'wrongpassword';
const res = await postLogin();
expect(res.status).toBe(401)
});
it('should return 403 without csrf token/header credentials', async () => {
await getLoginCsrfs();
csrfToken = '';
cookies = '';
password = 'password';
const res = await postLogin();
expect(res.status).toBe(403)
});
it('should return 200 with correct credentials', async () => {
await getLoginCsrfs();
password = 'password';
const res = await postLogin();
expect(res.status).toBe(200)
});
});
});
FAIL tests/integration/login.test.js
/login
GET /
✓ should return the login form (300ms)
POST /
✕ should return 401 without incorrect user info (150ms)
✓ should return 403 without csrf token/header credentials (130ms)
✕ should return 200 with correct credentials (131ms)
● /login › POST / › should return 401 without incorrect user info
expect(received).toBe(expected) // Object.is equality
Expected: 401
Received: 403
61 | password = 'wrongpassword';
62 | const res = await postLogin();
> 63 | expect(res.status).toBe(401)
| ^
64 | });
65 |
66 | it('should return 403 without csrf token/header credentials', async () => {
at Object.toBe (tests/integration/login.test.js:63:26)
● /login › POST / › should return 200 with correct credentials
expect(received).toBe(expected) // Object.is equality
Expected: 200
Received: 403
77 | password = 'password';
78 | const res = await postLogin();
> 79 | expect(res.status).toBe(200)
| ^
80 | });
81 | });
82 | });
at Object.toBe (tests/integration/login.test.js:79:26)

initially I thought the problem related to _csrf in set-cookie but after rechecking through the solution was straightforward,
const postLogin = async () => {
return request(app).post(`/login`)
.type('form')
.set('Cookie', cookies)
.send({ email: user.email,
password: password,
_csrf: csrfToken
});
};
i had omitted the .type('form'), the 403 related to the csrfToken from the form, (all form data), not being seen.
PASS tests/integration/login.test.js (5.661s)
/login
GET /
✓ should return the login form (489ms)
POST /
✓ should return 401 without incorrect user info (443ms)
✓ should return 403 without csrf token/header credentials (131ms)
✓ should return 200 with correct credentials (255ms)
I had been googling around and considered the issues to relate to jest / supertest and multiple cookies but as ever the solution was much more straightforward and closer to home.

Related

Jest run async endpoints beforeall, afterall tests

[Junior dev!!]
->Node v18.08
->jest 29.0.1
->MySQL
Hi i m running several jest test in diferent folders of each endpoint on my API. I have several files where i m creating an account => test several endpoints => clean DB.. I have at least 14 files like these. So.. When an endpoint in those 14 file is not working the test doesn't go until the end and clean the DB so i need to go back and change the user name.. A clearly waste of time..
I'm learning about the Hook beforeAll and afterAll but they are not working.
i will show here my code without the hooks (test working)
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//Creating User
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
//Endpoints to test
test("POST/test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
//Deleting all info from DB
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
And when i want to add the hooks doesn't work
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//creating before all an user
beforeAll(() => {
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
});
//testing endpoints
test("POST/action test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
// if the endpoint doesn't work afterAll clean all DB
afterAll(() => {
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
});
what do you think. Could you help me ? Because the info on jest is only showing how to do it with a function.. Can we test something inside beforeAll & afterAll ??
I have something similar in a project that I'm working on.
I had to make sure that the beforeAll was an async function and put the await.
beforeAll(async () => {
connection = await createConnection();
await connection.runMigrations();
const id = uuidv4();
const password = await hash('admin', 8);
await connection.query(
`insert into users (id, name, email, password, is_admin, created_at, driver_license)
values ('${id}', 'Admin', 'admin#test.com.br', '${password}', true, 'now()', 'XXXXXXX')`
);
});
afterAll(async () => {
await connection.dropDatabase();
await connection.close();
});
Another thing is that I have done a test using a test function inside the beforeAll and it has returned an error, so maybe just executing the post without the test may work.
Edit: Reading the docs about the test inside the beforeAll and beforeEach, it says that you can not use a test inside it.

401 Unauthorized error when making axios request

I am using axios to make different GET and POST requests to a third-party API that requires a username and password login to access the data. I have tried several approaches to make this work but I keep getting this error in the console.
data: '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n' +
'<html><head>\n' +
'<title>401 Unauthorized</title>\n' +
'</head><body>\n' +
'<h1>Unauthorized</h1>\n' +
'<p>This server could not verify that you\n' +
'are authorized to access the document\n' +
'requested. Either you supplied the wrong\n' +
'credentials (e.g., bad password), or your\n' +
"browser doesn't understand how to supply\n" +
'the credentials required.</p>\n' +
'</body></html>\n'
}
I have been using this approach to send the username and password with node.js
export const getCameraStream = (req, res) => {
let conn = createConnection(config);
let query = `SELECT * FROM cameras WHERE id = ${req.params.id}`
conn.connect();
conn.query(query, async (error, rows, _) => {
const camera = rows[0];
const username = camera.user;
const password = camera.pass;
if (error) {
return res.json({ "status": "failure", "error": error });
}
const result = await axios({
method: 'get',
url: <placeholder for api url>,
auth: {
username, password
}
})
console.log(result);
});
conn.end();
}
and then use this code on my React front-end
const getCameraStream = async () => {
const username = camera.user
const password = camera.pass
try {
const token = Buffer.from(`${username}:${password}`).toString('base64')
const res = await publicRequest.get(`camera/getCameraStream/${camera.id}`, {
headers: {
'content-type': 'multipart/x-mixed-replace; boundary=--myboundary',
'Authorization': `Basic ${token}`
},
auth: {
username, password
}
})
console.log(res)
} catch (error) {
console.error(error)
}
}
Using this approach, I am able to pass the username and password to the server and the responseUrl property is correct. However, my app crashes and I keep getting the same unauthorized error in the console. Could this be an issue with the fact that the browser is sending a preflight OPTIONS request or am I overlooking something with my authorization?

fake response(500) with sinon in post request NodeJs

can anyone help me out creating a fake response (500) for testing my API using "sinon" , am new to nodeJs , i have tried to test where the return status is 201 and it worked however am still not able to make the fake 500 response
here is my code , thank you in advance
//knex
app.post("/api/categories", function (req, rep) {
knex('categories').insert(req.body)
.then(() => rep.sendStatus(201).json({ message: "Category inserted" }))
.catch((err) => {
console.log(err)
rep.status(500);
})
});
// in my test js
var request=require('supertest');
var KnexApp=require('../KnexFolder/app');
var sinon = require("sinon");
describe("POST/users", ()=>{
describe('when everything is fine and no errors', () => {
it('should respond with status 201',async () => {
const res = await request(KnexApp)
.post('/api/categories')
.send({
name:"from test",
img_id: 5
})
expect(res.statusCode).toEqual(201)
})
})
describe('when There is internal server error', () => {
it('should respond with status 500',async () => {
sinon.stub('/api/categories', "post").throws(
new Error({
response: { status: 500},
})
);
expect(res.statusCode).toEqual(500)
})
})
})
There are two testing strategies:
Stub knex, query interface, and the resolved/rejected value. This way is easier than the second way, you don't need to set up a real testing database and populate testing data.
As mentioned above, you need to set up a real testing database(run your migration script to create database and tables, create the seed testing data, etc...)
I will use the first way to test your code. Since sinon doesn't support stub a function export defaults by a module. We need to use proxyquire package.
app.js:
const express = require('express');
const knex = require('knex')({
client: 'mysql',
connection: {
host: '127.0.0.1',
port: 3306,
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test',
},
});
const app = express();
app.use(express.json());
app.post('/api/categories', function (req, rep) {
console.log(req.body);
knex('categories')
.insert(req.body)
.then(() => rep.sendStatus(201).json({ message: 'Category inserted' }))
.catch((err) => {
console.log(err);
rep.sendStatus(500);
});
});
module.exports = app;
app.test.js:
const request = require('supertest');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('POST/users', () => {
describe('when everything is fine and no errors', () => {
it('should respond with status 201', async () => {
const queryInterfaceStub = {
insert: sinon.stub().resolves(),
};
const knexStub = sinon.stub().returns(queryInterfaceStub);
const KnexStub = sinon.stub().returns(knexStub);
const KnexApp = proxyquire('./app', {
knex: KnexStub,
});
const res = await request(KnexApp).post('/api/categories').send({
name: 'from test',
img_id: 5,
});
sinon.assert.match(res.statusCode, 201);
});
});
describe('when There is internal server error', () => {
it('should respond with status 500', async () => {
const queryInterfaceStub = {
insert: sinon.stub().rejects(new Error('fake error')),
};
const knexStub = sinon.stub().returns(queryInterfaceStub);
const KnexStub = sinon.stub().returns(knexStub);
const KnexApp = proxyquire('./app', {
knex: KnexStub,
});
const res = await request(KnexApp).post('/api/categories').send({});
sinon.assert.match(res.statusCode, 500);
});
});
});
Test result:
POST/users
when everything is fine and no errors
{ name: 'from test', img_id: 5 }
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:558:11)
at ServerResponse.header (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:771:10)
at ServerResponse.send (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:267:15)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/71565119/app.js:20:37
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
✓ should respond with status 201 (436ms)
when There is internal server error
{}
Error: fake error
at Context.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/71565119/app.test.js:27:38)
at callFn (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:364:21)
at Test.Runnable.run (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:352:5)
at Runner.runTest (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:677:10)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:801:12
at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:594:14)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:604:7
at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:486:14)
at Immediate.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:572:5)
at processImmediate (internal/timers.js:461:21)
✓ should respond with status 500
2 passing (448ms)

Fetch API failed to Fetch during authentication, alongside CORS error

I have a button that lauches a fetch to my API that uses KOA and JWT. The javascript for the fetch initiated on click is:
<script>
function loginButton(user, pass) {
fetch('http://localhost:5454/api/login', {
method: "post",
headers: {
'Content-Type': "application/json"
},
body: JSON.stringify({
username: user,
password: pass
})
})
.then( (response) => {
console.log("Success")
})
.catch(e => console.log(e));
}
</script>
The code for my Authentication is:
router.post(`${BASE_URL}/login`, async (ctx) => {
const reqUsername = ctx.request.body.username
const reqPassword = ctx.request.body.password
const unauthorized = (ctx) => {
ctx.status = 401
ctx.body = {
error: 'Invalid username or password'
}
}
let attemptingUser
try {
attemptingUser = await Employee.findOne({ where: { username: reqUsername }})
if (attemptingUser != null && attemptingUser.password === reqPassword) {
ctx.status = 200
ctx.body = {
username: attemptingUser.username,
given_name: attemptingUser.given_name,
role: attemptingUser.role,
created_at: attemptingUser.createdAt,
updated_at: attemptingUser.updatedAt,
}
const token = jwt.sign({ username: attemptingUser.username, role: attemptingUser.role }, SECRET)
ctx.set("X-Auth", token)
} else {
unauthorized(ctx)
}
} catch(err) {
console.error(err)
console.error(`Failed to find username: ${reqUsername}`)
unauthorized(ctx)
}
})
The code for my KOA initiation is:
require('dotenv').config()
const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const baseRoutes = require('./routes')
const cors = require('#koa/cors');
const PORT = process.env.PORT || 8080
const app = new Koa()
app.use(bodyParser())
app.use(baseRoutes.routes())
app.use(cors());
app.listen(PORT, () => {
console.log(`Server listening on ${PORT}`)
})
Im using Port 8080 for my http-server and port 5454 for my npm server. I am getting a Failed to Fetch in the catch of the Fetch, as well as a CORS error related to not having a Access-Control-Allow-Origin header in the response header. I've tried a couple things and am ready to have a new set of eyes look at it, any tips?
Edit: I am successfully receiving the token in the X-Auth header, but for some reason it’s still throwing errors and I’d like to get them resolved before it spirals out of control.

Write a test in jest for adding a property to the response header

I'm setting up an authentication server when the user logged in; express should add a property ('x-auth-token') to the response header the route actually working but I don't know how to write a test for that in jest.
here is my route handler;
const bcrypt = require('bcrypt');
const { User } = require('../models/user');
const Joi = require('joi');
const express = require('express');
const router = express.Router();
router.post('/', async (req, res) => {
const { error } = validation(req.body);
if (error) return res.status(400).send(error.details[0].message);
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password)))
return res.status(400).send('Invalid email or password.');
// here express adding the token to header
res
.header('x-auth-token', user.generateAuthToken())
.send(JSON.stringify(`Welcome dear ${user.name}`));
});
function validation(value) {
const schema = {
email: Joi.string()
.min(10)
.max(255)
.email()
.required(),
password: Joi.string()
.min(8)
.max(1024)
.required()
};
return Joi.validate(value, schema);
}
module.exports = router;
here is my test:
user is available in MongoDB and email and password are valid.
const request = require('supertest');
const execude = () => {
return request(server)
.post('/api/auth')
.send({ email, password });
};
it('should place token in x-auth-token if request is valid', async () => {
const res = await execude();
expect(res.header).to.haveKey('x-auth-token');
});
jest console
● /auth › POST/ › should place token in x-auth-token if request is valid
expect(object).toHaveProperty(path)
Expected the object:
{"connection": "close", "content-length": "26", "content-type": "text/html; charset=utf-8", "date": "Mon, 29 Apr 2019 20:54:45 GMT", "etag": "W/\"1a-ARJvVK+smzAF3QQve2mDSG+3Eus\"", "strict-transport-security": "max-age=15552000; includeSubDomains", "x-content-type-options": "nosniff", "x-dns-prefetch-control": "off", "x-download-options": "noopen", "x-frame-options": "SAMEORIGIN", "x-xss-protection": "1; mode=block"}
To have a nested property:
"x-auth-token"
94 | const res = await execude();
95 |
> 96 | expect(res.header).toHaveProperty('x-auth-token');
| ^
97 | });
98 | // should send specific string if request is valid
99 | });
at Object.toHaveProperty (tests/integration/authentication.test.js:96:
Here is the function you can utilize to give post request with/without token. you have to declare your deployed database ip/address in server variable
/**
* Post request function.
* #param {String} endpoint - Endpoint to give post request.
* #param {String} token - Token to validate-optional.
* #param {Object} schema - Data to post.
* #return {} Returns body response and status code.
*/
async postRequest(endpoint, schema, token = 0) {
if (token === 0) { return await request(server).post(endpoint).send(schema) }
else { return await request(server).post(endpoint).set('x-auth-token', token).send(schema) };
}

Resources