How to automate testing a user journey? - node.js

I use NodeJS for the web server and Mocha for testing. I tried this test to ensure that the application can create a user, log it out, and sign it in again:
const request = require('supertest');
const app = require('../app');
const req = request(app);
describe("user journey", function() {
it("tests a user journey (signup, logout, sign in)", function(done) {
let signedInStub = "something only for logged in users";
req
.post("/signup")
.send({
"email": "test#test.com",
"password": "abcd1234"
})
.expect(function(response) {
expect(response.body.includes(signedInStub))
})
.get("/logout")
.expect(function(response) { // <-- Test fails here.
!response.body.includes(signedInStub)
})
.post("/login")
.send({
"email": "test#test.com",
"password": "abcd1234"
})
.expect(function(response) {
response.body.includes(signedInStub)
})
.get("/content/2")
.expect(200, done);
});
});
I run it with mocha --exit test.js and get the error:
1) user journey
tests a user journey (signup, logout, sign in):
TypeError: Cannot read property 'expect' of undefined
How can I test on the command-line that a user can create an account, log out, and log in?

Create an agent and use that across multiple requests to persist the session.
If needed, install chai, which works with supertest, with npm install --save chai. (See here for details about testing in NodeJS with Mocha and Chai.)
If you are using an auth type other than cookies, persist the data in a variable in the same way and send it with each request.
const request = require('supertest');
const app = require('../app');
const { expect } = require('chai')
const User = require('../models/user');
const email = "test#test.com";
describe("user journey", function() {
let req
let signedInStub = "something only for logged in users"
before(function(){
req = request.agent(app)
})
it("should signup a new test#test.com user", async function() {
// Delete this email in case emails are unique in the database.
await User.deleteOne({email: email});
const response = await req.post("/signup")
.send({
"email": email,
"password": "abcd1234"
})
.redirects(1); // optional, in case your back-end code uses a redirect after signing up.
// Log the response so you can track errors, e.g. hidden parameters in the HTML form that are missing from this POST request in code.
console.log(response.text);
expect(response.text).to.include(signedInStub)
})
it("should logout the new user", async function() {
const response = await req.get("/logout")
expect(response.text).not.to.include(signedInStub)
})
it("should login the new user", async function() {
const response = await req.post("/login")
.send({
"email": email,
"password": "abcd1234"
})
expect(response.text).to.include(signedInStub)
})
it("should get the new users content", async function() {
await req.get("/content/2")
.expect(200)
});
});

Related

When I try to run a Post User Registration script in Auth0 it gives "Error! API Error. Please contact support if the problem persists" error

I am trying to use Auth0's actions for post user registration. When I try to test it via UI, it gives me an error like "Error! API Error. Please contact support if the problem persists" and in the console it only writes "Error: {}". The script I wrote for this action looks something like this:
const https = require('https');
const jsonwebtoken = require('jsonwebtoken');
/**
* #param {Event} event - Details about registration event.
*/
exports.onExecutePostUserRegistration = async (event) => {
const TOKEN_SIGNING_KEY = event.secrets.signingKey
const TOKEN_EXPIRATION_IN_SECONDS = 10
const payload = {
email: event.user.email,
name: event.user.given_name,
surname: event.user.family_name
};
const token = jsonwebtoken.sign(payload, TOKEN_SIGNING_KEY,
{ subject: "postUserRegistration",
expiresIn: TOKEN_EXPIRATION_IN_SECONDS });
console.log("Starting post user registration operations for user with email: ", payload.email);
return new Promise((resolve, reject) => {
const request = https.request(url, options,
(res) => {
if (res.statusCode === 200) {
resolve({statusCode: res.statusCode, headers: res.headers})
} else {
reject({
headers: res.headers,
statusCode: res.statusCode
})
}
res.on('error', reject);
});
request.on("error", function (e) {
console.error(e);
reject({message: {e}});
});
request.on("timeout", function () {
reject({message: "request timeout"});
})
request.end();
});
}
Can you help me about what exactly causes this problem?
In order to understand this problem I tried to assign the Promise to a variable and then see what it returned. The funny part was that it was "pending". It couldn't be "pending" in any way, because it everything was true it would be "resolved", else "rejected" and if there is a timeout/error from the request it would be "rejected".
It turns out that Auth0 has some limitations for actions and the endpoint called was stuck to our firewall which caused a timeout in Auth0 before an HTTPS timeout. As the request was broken halfway, it stayed as "pending".

How to Authenticate a Service Account for the Gmail API in Node Js?

So I'm using the Node.js Gmail library to send an email to another user. I was thinking of using a Service account to do just that. I've followed their documentation of passing the keyFile property but when I try to run the code, I get a 401 error, Login Required.
Here's what I got so far:
const { gmail } = require("#googleapis/gmail");
function createMessage(from, to, subject, message) {
// logic that returns base64 email
const encodedMail=[...];
return encodedMail;
}
export default function handler(req, res) {
const auth = gmail({
version: "v1",
keyFile: './google_service.json',
scopes: ["https://www.googleapis.com/auth/gmail.send"],
});
const raw = createMessage(
process.env.SERVICE_EMAIL,
"someone#gmail.com",
"Subject",
"This is a test",
);
const post = auth.users.messages.send({
userId: "me",
requestBody: {
raw,
},
});
post
.then((result) => {
console.log(result.data);
})
.catch((err) => {
console.log(err);
});
}
I've already got my Service Account credential json file and placed it at the root of my project. Is there something I'm doing wrong?

OAuth2 idToken is verified, but Login Required error persists

So cutting right to the chase...
I'm integrating an Angular front-end with an express back-end.
The login happens using angularx-social-login in the front-end(trying to avoid redirects) and the idToken is sent to the back-end for verification. The scopes are added during the login at the front-end.
After using google-auth-library to verify the token everything is good and correct.
But once a service.people.connections.list() for getting google contacts is called a Login Required error persist. I've tried using the access token I get at the front-end, the payload I get from the verification and all that... I'm sure I'm missing a single step, but I have no clue.
req.headers.authorization here is the idToken.
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: req.headers.authorization,
audience: CLIENT_ID
});
const payload = ticket.getPayload();
console.log(payload);
const userid = payload['sub'];
const service = google.people({ version: 'v1' });
service.people.connections.list({
resourceName: 'people/me',
pageSize: 10,
personFields: 'names,emailAddresses',
}, (err, res) => {
if (err) return console.error('The API returned an error: ' + err);
const connections = res.data.connections;
if (connections) {
console.log('Connections:');
connections.forEach((person) => {
if (person.names && person.names.length > 0) {
console.log(person.names[0].displayName);
} else {
console.log('No display name found for connection.');
}
});
} else {
console.log('No connections found.');
}
});
};

If MFA enabled in AWS cognito, do I need to create js on client side to call cognitoUser.authenticateUser() because of the promt for code?

I am using reactjs and node for server side.
As you can see in the "mfa required" part of the code below, if this is all on node, then I can't really do "prompt" the user for the code, I have to pass this back to the front end.
Tried solution: If I do pass the MFA required to front end and get the users input then send it back to node to call "respondToAuth" I am getting two MFA codes in my SMS message.
Have I tried other solutions?
I am hesitant to use amplify because everything is on the front end, I would ideally like to do my authentication on the back end (thus node).
Another option I am leaning towards is just using initiateAuth api instead of "cognitoUser.AuthenticateUser". This way I can get the challenge response and pass it on in sequence. But as per my initial question, I am wondering if I can implement the below code and be able to route users to input MFA code (without duplicating MFA sms message)
AWS.config.update({
region: process.env.Region
});
var AmazonCognitoIdentity = require('amazon-cognito-identity-js');
const poolData = { //--Moved to env variables
UserPoolId: process.env.UserPoolId, // your user pool id here
ClientId: process.env.ClientId // your app client id here
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
router.post('/api/authenticateuser', (req, res) => {
const val = req.body;
var userData = {
Username: val.value.user, // your username here
Pool: userPool
};
var authenticationData = {
Username: val.value.user, // your username here
Password: val.value.pass, // your password here
};
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(result) {
console.log('You are now logged in.');
console.log(result);
const accessToken = result.getAccessToken().getJwtToken();
const idToken = result.getIdToken().getJwtToken();
res.json({
accessToken,
idToken
});
},
onFailure: function(err) {
res.json(err);
},
mfaRequired: function(codeDeliveryDetails) {
// console.log("mfa enabled");
// var verificationCode = prompt('Please input verification code' ,'');
// cognitoUser.sendMFACode(verificationCode, this);
// res.json({ MFA:codeDeliveryDetails})
}
});
})

Getting "Error: the string "Not a valid BCrypt hash." was thrown, throw an Error :)" during Mocha ExpressJS testing

I have a MEAN stack app that is using Passport for authentication.
I'm trying to write a unit test that logs in and checks whether you are redirected to the root (/). However, whenever I run Mocha I get the following error message:
1) POST /home Login test should redirect to / after login:
Error: the string "Not a valid BCrypt hash." was thrown, throw an Error :)
Here's my unit test LoginSpec.js:
var should = require("should");
var app = require("../app");
var mongoose = require("mongoose");
var User = mongoose.model("User");
var request = require("supertest");
var agent = request.agent(app);
...
describe('POST /home', function() {
before(function(done) {
user = new User({
email: "john#email.com",
firstName: "John",
lastName: "Doe",
password: "strongPassword",
username: "johndoe"
});
user.save(done);
})
describe('Login test', function() {
it ('should redirect to / after login', function(done) {
agent.post('/login')
.send({
username: 'johndoe',
password: 'strongPassword'
})
.end(function(err, res) {
done();
})
})
after(function(done) {
User.remove().exec();
return done();
})
})
})
Do I need to BCrype my password? If so, how do I do this?
Also, how come some of the online examples I'm seeing for logging in don't do it? Such as NodeJS/Passport - Testing user login with mocha and superagent and How to authenticate Supertest requests with Passport?
It happen because your password field on database have just a string, not a hashed string.
It must be like $2a$08$LMXAGOARNn4XmnC/rQuhfujrWVwgK/RuHuGpLtXvcv/yruY1v3yXa but probably are just the original password.
I thought I'd answer this since I had the same issue and could not find anywhere with a direct answer.
Where you are defining a new user you will need to use bcrypt to encrypt that password, also when you are login in you will then need to use bcrypt to compare the password to the one saved in the user you have fetched. Otherwise you will continue to get the issue of "Not a valid BCrypt hash.".
Here is a simple encrypt and compare function that I use in my app
UserSchema.methods.encryptPassword = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(10));
}
UserSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.password);
}
More information can be found here: https://www.npmjs.com/package/bcrypt

Resources