hapijs-lab: Test a route with a valid session - node.js

In my hapijs app I have few routes which require a session, uses hapi-auth-cookie plugin for auth strategy. I want to add few tests (via Lab ) for these routes.
I couldn't find any documentation on how I can setup a test (maybe via before ?) for this scenario. Any help is appreciated. Thanks in advance.

If you only need an authenticated user, just assign the user to the credentials property in tests:
var user = { ... };
server.inject({ method: 'GET', url: '/', credentials: user }, function (res) {
console.log(res.result);
});
Here is an example that demonstrates it:
var Bcrypt = require('bcrypt');
var Hapi = require('hapi');
var HapiAuthCookie = require('hapi-auth-cookie');
var server = new Hapi.Server();
server.connection({ port: 3000 });
var users = {
john: {
username: 'john',
password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',
name: 'John Doe',
id: '2133d32a'
}
};
var validate = function (request, username, password, callback) {
var user = users[username];
if (!user) {
return callback(null, false);
}
Bcrypt.compare(password, user.password, function (err, isValid) {
callback(err, isValid, { id: user.id, name: user.name });
});
};
server.register(HapiAuthCookie, function (err) {
server.auth.strategy('session', 'cookie', {
password: 'secret',
validateFunc: validate
});
server.route({
method: 'GET',
path: '/',
config: {
auth: 'session',
handler: function (request, reply) {
reply('hello, ' + request.auth.credentials.name);
}
}
});
server.inject({ method: 'GET', url: '/', credentials: users.john }, function (res) {
console.log(res.result);
});
});
Large part of the example was taken from the Authentication Tutorial.

For my need for session during testing, I created hapi-session-inject. Usage is as follows
const server = new Hapi.Server();
const session = new Session(server);
// Callback interface
session.inject('/', (res) => {
...
});
// Promise interface
return session.inject('/').then((res) => {
...
});
Hope it helps.

Related

Hapi unknown authentication strategy jwt

I'm getting the error Hapi unknown authentication strategy jwt but I'm not sure why. I'm certain I've probably set something up wrong but here's my server index.js:
Should i be using a different auth strategy? Also stackoverflow won't let me submit my question because it's mostly code but I'm not sure what else to submit. All of the details are here and I don't know what else to add to provide any more information that i can. I'm just using using auth with config.auth.strategy and jwt in an array.
const Hapi = require('#hapi/hapi');
const objection = require('objection');
const knex = require('./knex');
const authService = require('./auth/auth-service');
const JWTAuth = require('hapi-auth-jwt2');
const init = async () => {
const server = Hapi.server({
port: 9000,
host: 'localhost',
routes: { cors: {
origin: ['*'],
headers: ['Authorization'],
exposedHeaders: ['Accept'],
additionalExposedHeaders: ['Accept'],
maxAge: 60,
credentials: true
}}
});
objection.Model.knex(require('./knex'));
await server.register([
{plugin: JWTAuth},
{
plugin: require('./movies/movie-routes'),
routes: {prefix: '/movies'}
}, {
plugin: require('./user/user-routes'),
}
])
server.auth.strategy('jwt', 'jwt',{
key: authService.jwtKey,
validate: authService.validateJWT,
verifyOptions: {algorithms: ['HS256']},
errorFunc: (err)=> {return err},
cookieKey: 'id_token'
})
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
Seems like there is a problem with authService.validateJWT I have added an example of HapiJs jwt authentication please it once for your reference
const Hapi = require('#hapi/hapi');
const JWTAuth = require('hapi-auth-jwt2');
const init = async () => {
const server = Hapi.server({
port: 9000,
host: 'localhost',
routes: {
cors: {
origin: ['*'],
headers: ['Authorization'],
exposedHeaders: ['Accept'],
additionalExposedHeaders: ['Accept'],
maxAge: 60,
credentials: true
}
}
});
await server.register([
{ plugin: JWTAuth }
])
server.auth.strategy('jwt', 'jwt', {
key: 'your-key',
validate: async function (decoded, request) {
if (!decoded) {
return { isValid: false };
} else {
request.auth.credentials = {
'user': decoded,
'token': request.headers['authorization']
};
return { isValid: true };
}
},
verifyOptions: { algorithms: ['HS256'] },
cookieKey: 'id_token'
});
server.auth.default('jwt');
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello World!';
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();

How to use LinkedIn api with Node js

All I need is to check in the backend side if the user access token is valid and get user email by its access token. It's hard to understand how to use this npm library for these purposes, so please help me.
In the documentation I've found the API address for it, but how to fetch the with Client ID and Client Secret of my app which I created on https://www.linkedin.com/developers/apps/new..
Hopefully, my question makes sense, thanks in advance <3
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
// linkedin app settings
var LINKEDIN_CLIENT_ID = "CLIENT_ID_HERE";
var LINKEDIN_CLIENT_SECRET = "CLIENT_SECRET_HERE";
var Linkedin = require('node-linkedin')(LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET);
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use(new LinkedInStrategy({
clientID: LINKEDIN_CLIENT_ID,
clientSecret: LINKEDIN_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/linkedin/callback",
scope: ['r_emailaddress', 'r_basicprofile', 'rw_company_admin'],
passReqToCallback: true
},
function (req, accessToken, refreshToken, profile, done) {
req.session.accessToken = accessToken;
process.nextTick(function () {
return done(null, profile);
});
}));
// for auth
app.get('/auth/linkedin',
passport.authenticate('linkedin', { state: 'SOME STATE' }),
function(req, res){
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
});
// for callback
app.get('/auth/linkedin/callback', passport.authenticate('linkedin', { failureRedirect: '/' }),
function (req, res) {
res.redirect('/');
});
This is a Code Snippet of how i have used it, i guess it will help you on fetching with CLIENT_ID and CLIENT_SECRET.
Note : npm passport and passport-linkedin-oauth2 should already be installed
const accessToken = req.params.accessToken;
const options = {
host: 'api.linkedin.com',
path: '/v2/me',
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'cache-control': 'no-cache',
'X-Restli-Protocol-Version': '2.0.0'
}
};
const profileRequest = https.request(options, function(result) {
let data = '';
result.on('data', (chunk) => {
data += chunk;
console.log(data)
});
result.on('end', () => {
const profileData = JSON.parse(data);
return res.status(200).json({
'status': true,
'message': "Success",
'result': profileData
});
});
});
profileRequest.end();
The existing NodeJS LinkedIn official API is not so straightforward and hardly maintained.
If relevant, you can use this NodeJS LinkedInAPI.
It allows you to easily login via username & password or with a Cookie and make various requests:
import { Client } from 'linkedin-private-api';
const username = process.env.USERNAME as string;
const password = process.env.PASSWORD as string;
(async () => {
// Login
const client = new Client();
await client.login.userPass({ username, password });
// search for companies
const companiesScroller = await client.search.searchCompanies({ keywords: 'Microsoft' });
const [{ company: microsoft }] = await companiesScroller.scrollNext();
// Search for profiles and send an invitation
const peopleScroller = await client.search.searchPeople({
keywords: 'Bill Gates',
filters: {
pastCompany: microsoft.companyId
}
});
const [{ profile: billGates }] = await peopleScroller.scrollNext();
await client.invitation.sendInvitation({
profileId: billGates.profileId,
trackingId: billGates.trackingId,
});
})

MongooseError [OverwriteModelError]: Cannot overwrite `Team` model once compiled

I'm making a call to a node.js express api from a react client.
When I make the call from the client, this is my request:
const response = await axios({
method: 'post',
url: 'http://localhost:3000/api/users/forgotPassword',
data: {email: email},
headers: {
'X-Requested-With': 'XMLHttpRequest',
}
}
);
This is the endpoint in express:
adminUserRoutes.post('/forgotPassword', (req, res) => {
console.log('it connected')
if (req.body.email === '') {
res.status(400).send('email required');
}
console.log(req.body.email)
console.log(req.body)
User.findOne({email: req.body.email}, (err, user) => {
console.log('and here')
if(user){
const token = crypto.randomBytes(20).toString('hex');
console.log('use',user)
user.resetPasswordToken = token
user.resetPasswordExpires = Date.now() + 360000
user.name = user.name
user.email = user.email
user.password = user.password
user.admin = user.admin
// console.log(user)
user.save()
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: `email`,
pass: `password`,
},
});
const mailOptions = {
from: 'devinjjordan#gmail.com',
to: `${user.email}`,
subject: 'Link To Reset Password',
text:
'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n'
+ 'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\n\n'
+ `http://localhost:3000/#/newpassword/${token}\n\n`
+ 'If you did not request this, please ignore this email and your password will remain unchanged.\n',
};
console.log('sending mail');
transporter.sendMail(mailOptions, (err, response) => {
if (err) {
console.error('there was an error: ', err);
// res.status(200).json('there was an error: ', err);
} else {
console.log('here is the res: ', response);
res.set({
"Access-Control-Allow-Origin": "*", // Required for CORS support to work
"Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS
})
res.status(200).json('recovery email sent');
}
});
} else {
console.error('email not in database');
res.status(403).send('email not in db');
}
})
});
What's odd about the situation is that when I make the request from postman to the same endpoint, I receive the expected response.
However, when I make the request from the client, I receive this error:
MongooseError [OverwriteModelError]: Cannot overwrite `Team` model once compiled.
at new OverwriteModelError (/Users/lukeschoenberger/Documents/Programming/news-arg/backend/node_modules/mongoose/lib/error/overwriteModel.js:20:11)
at Mongoose.model (/Users/lukeschoenberger/Documents/Programming/news-arg/backend/node_modules/mongoose/lib/index.js:517:13)
I'm using serverless labdma and am running sls offline start to open on port 3000.
What's very odd is that the 'Team' model isn't even mentioned in the api in question.
Edit:
This is the Team module:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
let Team = new Schema({
team_name: {
type: String
},
city_or_state: {
type: String
},
league: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'League'
},
primary_color: {
type: String
}
}, { timestamps: true })
module.exports = mongoose.model('Team', Team)
This turned out to be an issue with withe aws-serverless. Running aws-serverless with this flag solves the issuue: --skipCacheInvalidation -c. Longer post about it: https://github.com/dherault/serverless-offline/issues/258
You are compiling your model more than one time at runtime. Check if your model is already registered before registering it:
module.exports = mongoose.models.Team || mongoose.model('Team', Team)
I also ran in to same error few weeks ago. After trying out few things I came out with a simple fix:
just try to export person model this way -
module.exports.teamModel= mongoose.model('Team', Team);
instead of - module.exports = mongoose.model('Team', Team)
Hope this helps !
if you still get the error just check the paths in the modules you were exporting this model.

Export function and call it from a different file

I'm trying to export the below function and call it on a different file. It half-works. For example: If I call the function from the same file, it does save the data to the DB. However, If I call the function from a different file, it does not save it.
Token.js:
var express = require("express");
var router = express.Router();
var User = require("../models/user");
var request = require("request");
var refresh = function() {
request.post(
{
headers: {
"Content-Type": "api",
Authorization:
"auth"
},
url: "token",
form: {
grant_type: "refresh_token",
refresh_token:
"token"
}
},
function(error, response, body) {
var token = JSON.parse(body);
// res.send(token);
User.findOneAndUpdate(
{ user: 2 },
{ token: token.access_token },
{ upsert: true },
function(err, data) {
if (err) throw err;
console.log("Saved!");
}
).catch(error => {
console.log(error);
});
}
);
};
module.exports = refresh;
call.js
var refreshToken = require("./helpers/refresh-token");
refreshToken();
Edit: Fixed... I forgot to include call.js in app.js
Thank you

Hapi js API basic authentication error

I was researching about the hapi js API basic authentication and i'm using Hapi documentation about the authentication. I believe i did everything right but i'm getting following error saying about UnhandledPromiseRejectionWarning. Please help
index.js
'use strict';
const Bcrypt = require('bcrypt');
const Hapi = require('hapi');
const Basic = require('hapi-auth-basic');
const server = new Hapi.Server({
host: 'localhost',
port: 3000
})
const users = {
john: {
username: 'john',
password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',
name: 'John Doe',
id: '2133d32a'
}
};
const validate = function (request, username, password, callback) {
const user = users[username];
if (!user) {
return callback(null, false);
}
Bcrypt.compare(password, user.password, (err, isValid) => {
callback(err, isValid, { id: user.id, name: user.name });
});
};
server.register(Basic, (err) => {
if (err) {
throw err;
}
server.auth.strategy('simple', 'basic', { validateFunc: validate });
server.route({
method: 'GET',
path: '/',
config: {
auth: 'simple',
handler: function (request, reply) {
reply('hello, ' + request.auth.credentials.name);
}
}
});
server.start((err) => {
if (err) {
throw err;
}
console.log('server running at: ' + server.info.uri);
});
});
package.json
"bcrypt": "^1.0.3",
"hapi-auth-basic": "^5.0.0",
"hapi": "^17.1.0"
error
(node:1248) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError [ERR_ASSERTION]: Invalid register options "value" must be an object
(node:1248) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
If you want that code to work you will have to use a version lower than 17, i.e (16.6.2) or either look for the code updated to the hapi version you are using.
const Bcrypt = require('bcrypt');
const Hapi = require('hapi');
const users = {
john: {
username: 'john',
password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm', // 'secret'
name: 'John Doe',
id: '2133d32a'
}
};
const validate = async (request, username, password, h) => {
if (username === 'help') {
return { response: h.redirect('https://hapijs.com/help') }; // custom response
}
const user = users[username];
if (!user) {
return { credentials: null, isValid: false };
}
const isValid = await Bcrypt.compare(password, user.password);
const credentials = { id: user.id, name: user.name };
return { isValid, credentials };
};
const main = async () => {
const server = Hapi.server({ port: 4000 });
await server.register(require('hapi-auth-basic'));
server.auth.strategy('simple', 'basic', { validate });
server.auth.default('simple');
server.route({
method: 'GET',
path: '/',
handler: function (request, h) {
return 'welcome';
}
});
await server.start();
return server;
};
main()
.then((server) => console.log(`Server listening on ${server.info.uri}`))
.catch((err) => {
console.error(err);
process.exit(1);
});

Resources