Request user email from Twitter in node.js - node.js

I'm trying to enable oauth2 login through Twitter in a node app. I have my Twitter account, I am whitelisted for email, have changed the settings on the Twitter development page (so that on login, the user is informed that my app can access their email).
But the user object still does not contain an email entry. Is email a different request, or am I missing something? I'll put the server code below, and can fire up the web server on demand, in case anybody wants to see the console output.
Relevant code in my server.js:
var express = require('express'),
request = require('request'),
path = require('path'),
fs = require('fs'),
bodyParser = require("body-parser");
var Twitter = require("node-twitter-api");
var app = express();
...
var twitter = new Twitter({
consumerKey: 'XXXXXXX',
consumerSecret: 'XXXXXX',
callback: 'http://thelionstestingjungle.com'});
var _requestSecret;
app.get("/tokenRequest", function(req, res) {
console.log('Token request');
twitter.getRequestToken(function(err, requestToken, requestSecret) {
if (err) {
console.log('Token request error');
res.status(500).send(err);
} else {
console.log('Request secret: ' + requestSecret);
_requestSecret = requestSecret;
res.redirect("https://api.twitter.com/oauth/authenticate?oauth_token=" + requestToken);
}
});});
app.get("/accessToken", function(req, res) {
console.log('Access token');
var requestToken = req.query.oauth_token,
verifier = req.query.oauth_verifier;
twitter.getAccessToken(requestToken, _requestSecret, verifier, function(err, accessToken, accessSecret) {
if (err) {
console.log('Get info error');
res.status(500).send(err);
} else {
twitter.verifyCredentials(accessToken, accessSecret, function(err, user) {
if (err) {
console.log('Verification in get info error');
res.status(500).send(err);
} else {
console.log('User: ' + JSON.stringify(user));
res.send(user);
}
});
}
});});
...

SO! The call is indeed different. The verify_credentials call to the API takes a handful of optional params. See the doc page:
https://dev.twitter.com/rest/reference/get/account/verify_credentials
To retrieve user email, add a include_email=true param to your URL. To do this with the node-twitter-api module:
var params = {
'include_email' : true
};
twitter.verifyCredentials(accessToken, accessSecret, params, function(err, user)
The module takes a dictionary, and parses the key-value pairs into URL params, and adds them for you.
EASY!

Related

Issue with setting Authorization header using Express JS implemenitng Using Cognito User Pool Authentication

I am implementing authentication using Cognito User Pool. I am only using NodeJS + Express JS, so whole coding is on server side and client side I just render html file using templates.
I am using below code to autheticate the user passing username and password.
const cognitoProvider = new AWS.CognitoIdentityServiceProvider();
cognitoProvider.adminInitiateAuth(params, function (err, result) {})
If authentication is sucessfull, result object has the accesstoken, tokentype and refresh token. Now I am not clear on how to set Authorzation header with this result which has the token. After this code I redirect user to the home page where I welcome the user. In subsequent request I want to retrieve token from header to check if user has logged in. But issue is I am not able to set the authorization header.
Below is the code:
cognitoProvider.adminInitiateAuth(params, function (err, result) {
If (err == null)
{
res.set('Authorization', result.AuthenticationResult); // Is this correct ? Even If i use this in next request I cannot retrieve Authorization header using req.header['Authorization']. I am not sure if I am coding this correctly.
//Below is my next line of code
res.redirect('/home'); // I redirect user to home page. I am not using render here as I was to avoid the post back message in case user tries to refresh the page, so I use redirect.
}
})
I have spend hours but not able to make this work. May be I am missing some basic concept here. Code Below of /users where I pass username & Password:
var express = require('express');
var router = express.Router();
const AWS = require('aws-sdk');
AWS.config.region = "REGION COMES HERE";
const cognitoProvider = new AWS.CognitoIdentityServiceProvider();
router.post('/', function (req, res, next) {
const username = req.body['InputEmail'];
const password = req.body['InputPassword'];
const params = {
"AuthFlow": "ADMIN_USER_PASSWORD_AUTH",
"AuthParameters": {
"USERNAME": username,
"PASSWORD": password
},
"UserPoolId": "USERPOOLID",
"ClientId": "CLIENTID"
}
cognitoProvider.adminInitiateAuth(params, function (err, result) {
if (err) {
var errormessage;
switch (err.code) {
case 'NotAuthorizedException':
errormessage = err.message
break;
default:
errormessage = "Issues encountered. Try Again";
}
res.render('index', { title: 'Express', message: errormessage });
return;
}
if (result.ChallengeName) {
switch (result.ChallengeName) {
case 'NEW_PASSWORD_REQUIRED':
res.render('firsttimelogin', { title: 'Express', username:username, message: '' });
break;
default:
res.render('index', { title: 'Express', message: '' });
}
} else {
res.set('Authorization', result.AuthenticationResult);
res.redirect('/home');
}
});
});
module.exports = router;
Code of /Home
var express = require('express');
var router = express.Router();
router.use('/', function(req, res, next) {
var token = req.header['Authorization'];
if (token) {
res.render('home', { title: 'Express'});
}
else{
res.redirect('/');
}
});
module.exports = router;

How to test authentication with jwt inside a cookie with supertest, passport, and JEST

Hey guys I am currently am trying to do something similar to what is posted here:
How to authenticate Supertest requests with Passport?
as I would like to test other endpoints that require authentication but in addition need to pass in a jwt. Right now, I tested it on POSTMAN and on the browser and it seems like it's working fine, but my test cases keep on breaking. I have a login POST route that is setup like so:
AccountService.js
// Login POST route
router.post('/account_service/login', (req, res, next) => {
passport.authenticate('local-login', (err, user, info) => {
try {
if (err) {
const error = new Error('An Error occurred: Cannot find user');
return next(error);
} else if (!user) {
return res.redirect('/account_service/login');
}
req.login(user, { session: false }, (error) => {
if (error) {
return next(error);
}
const email = req.body.email;
const role = req.user[0].role;
const id = req.user[0].id;
const user = {
email: email,
role: role,
id: id
};
const accessToken = jwt.sign(user, config.ACCESS_TOKEN_SECRET, {
expiresIn: 28800 // expires in 8 hours
});
const cookie = req.cookies.cookieName;
if (cookie === undefined) {
// set a new cookie
console.log('setting new cookie');
res.cookie('jwt', accessToken, { maxAge: 900000, httpOnly: true });
res.send({ token: accessToken });
} else {
// cookie was already present
console.log('cookie exists', cookie);
}
res.redirect('/account_service/profile');
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
After the user is authenticated, I assign a JSON web token to the user and place it in the cookie so it gets stored within the headers for authorized requests. Here is an example:
AccountService.js
// Get all users
router.get('/account_service/all_users', passport.authenticate('jwt', { session: false }), (req, res, next) => {
const sql = 'select * from user';
const params = [];
db.all(sql, params, (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({
message: 'success',
data: rows
});
});
});
I use passport.authenticate to ensure that the jwt is valid. This GET request only works after I login with admin user account.
Within my passport file I have it setup like so:
passport.js
const LocalStrategy = require('passport-local').Strategy;
const db = require('../database.js');
const bcrypt = require('bcrypt');
const config = require('../config/config.js');
const JwtStrategy = require('passport-jwt').Strategy;
const cookieExtractor = function (req) {
var token = null;
if (req && req.cookies) token = req.cookies.jwt;
return token;
};
module.exports = function (passport) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, (req, email, password, done) => {
try {
const sql = `select * from user WHERE email = "${email}"`;
const params = [];
db.all(sql, params, (err, row) => {
if (err) {
return done(err);
}
if (!row.length || !bcrypt.compareSync(password, row[0].password)) {
return done(null, false, req.flash('loginMessage', 'Inavalid username/password combination. Please try again.'));
}
return done(null, row);
});
} catch (error) {
return done(error);
}
}));
const opts = {};
opts.jwtFromRequest = cookieExtractor; // check token in cookie
opts.secretOrKey = config.ACCESS_TOKEN_SECRET;
// eslint-disable-next-line camelcase
passport.use(new JwtStrategy(opts, function (jwtPayload, done) {
try {
const sql = `select * from user WHERE email = "${jwtPayload.email}"`;
const params = [];
db.all(sql, params, (err, row) => {
if (err) {
return done(err);
}
if (!row.length || !bcrypt.compareSync('admin', jwtPayload.role)) {
return done(null, false, { message: '403 Forbidden' });
}
return done(null, row);
});
} catch (error) {
return done(error);
}
}));
};
Here's where I get confused as my test cases break. I am trying to login before my test cases to allow my other test cases to run but I end up getting a 401 error. Here are my test cases:
accountservice.test.js
const app = require('../../app');
const supertest = require('supertest');
const http = require('http');
describe('Account Service', () => {
let server;
let request;
beforeAll((done) => {
server = http.createServer(app);
server.listen(done);
request = supertest.agent(server);
request.post('/account_service/login')
.send({ email: 'admin#example.com', password: 'admin' })
.end(function (err, res) {
if (err) {
return done(err);
}
console.log(res);
done();
});
});
afterAll((done) => {
server.close(done);
});
it('Test request all users endpoint | GET request', async done => {
const response = await request.get('/account_service/all_users');
expect(response.status).toBe(200);
expect(response.body.message).toBe('success');
expect(response.body.data.length).toBe(3);
done();
});
});
But my test cases fail as I get a 401 error when it expects a 200 success code.
I tried thinking of a way to extract the jwt from a cookie after the login call so that I can set up the headers for the /account_service/all_users GET request code but was unable to find a way using Supertest. I saw this post: Testing authenticated routes with JWT fails using Mocha + supertest + passport but saw that it gets the token from the body.
After messing around with my code, I ended up having issues with in-memory storage and running asynchronous db.run functions that would call every time I ran my server. So I used a file to store my data and ran my tests again and it ended up working!
Here was the faulty code:
const sqlite3 = require('sqlite3').verbose();
const md5 = require('md5');
const DBSOURCE = ':memory:';
const db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
} else {
db.run(`CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name text,
email text UNIQUE,
password text,
status text,
comments text,
photos text,
CONSTRAINT email_unique UNIQUE (email)
)`,
(err) => {
if (err) {
// Table already created
console.log('Table already created');
} else {
// Table just created, creating some rows
const insert = 'INSERT INTO user (name, email, password, status, comments, photos) VALUES (?,?,?,?,?,?)';
db.run(insert, ['user_delete', 'user_delete#example.com', md5('admin123456'), 'pending_deleted', 'comment1,comment2', 'https://giphy.com/gifs/9jumpin-wow-nice-well-done-xT77XWum9yH7zNkFW0']);
db.run(insert, ['user_no_delete', 'user#example.com', md5('user123456'), 'active', 'comment1', 'https://giphy.com/gifs/cartoon-we-bare-bears-wbb-NeijdlusjcduU']);
db.run(insert, ['mikey', 'mikey#example.com', md5('mikey123'), 'pending_deleted', 'comment1', 'https://giphy.com/gifs/wwe-shocked-vince-mcmahon-gdKAVlnm3bmKI']);
}
});
}
});
module.exports = db;
I simply stored this data within a file and used this code instead:
const sqlite3 = require('sqlite3').verbose();
const DBSOURCE = 'mockdb.sqlite';
// Data inserted inside file
/*
db.run(insert, ['user_delete', 'user_delete#example.com', bcrypt.hashSync('admin123456', saltRounds), 'pending_deleted', 'comment1,comment2', 'https://giphy.com/gifs/9jumpin-wow-nice-well-done-xT77XWum9yH7zNkFW0', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['user_no_delete', 'user#example.com', bcrypt.hashSync('user123456', saltRounds), 'active', 'comment1', 'https://giphy.com/gifs/cartoon-we-bare-bears-wbb-NeijdlusjcduU', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['mikey', 'mikey#example.com', bcrypt.hashSync('mikey123', saltRounds), 'pending_deleted', 'comment1', 'https://giphy.com/gifs/wwe-shocked-vince-mcmahon-gdKAVlnm3bmKI', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['admin', 'admin#example.com', bcrypt.hashSync('admin', saltRounds), 'active', 'admincomments', 'adminphoto', bcrypt.hashSync('admin', saltRounds)]);
console.log('last hit in database');
});
*/
const db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
}
console.log('Connection successful!');
});
module.exports = db;
I also ended up using supertest.agent.
const app = require('../../app');
const supertest = require('supertest');
const http = require('http');
const db = require('../../database/database.js');
describe('Account Service', () => {
let server;
let request;
// Find cookie management option.
beforeAll(async (done) => {
server = http.createServer(app);
server.listen(done);
request = supertest.agent(server);
done();
});
And it ended up working and successfully solving my issue!

Cognito authenticating entire EC2 instance... or so it it seems?

It appears as though when I sign into my application, it is storing the credentials locally on the EC2 server. I've quadruple checked my code and I can't figure out what the deal is. If KEVIN signs in from any device, the next user to sign in, or if someone refreshes their page they end up signed in as KEVIN. I am including all code that could potentially be involved in the issue. Outside of this problem, all of my interaction with cognito works great; no errors & no problems. Any help would be greatly appreciated. I am using Node.js, Express, AWS, and Websockets on my EC2 Instance.
// Accessed from server for route authentication before page render or redirection
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
module.exports = {
ensureAuthenticated: function(req, res, next) {
let data = { UserPoolId : 'us-east-1_7xUGRJPKq',
ClientId : '6glign6b34c806osfhnik18cb3'
};
let userPool = new AmazonCognitoIdentity.CognitoUserPool(data);
let cognitoUser = userPool.getCurrentUser();
console.log(`ensuring authentication....`);
console.log(cognitoUser);
if (cognitoUser === null) {
req.flash('error_msg', 'Please log in');
res.redirect('/login');
} else {
cognitoUser.getSession((err, session) => {
if (err) {
console.log(err);
} else {
next();
}
});
}
},
};
// Routes where I am seeing the problem
const express = require('express');
const router = express.Router();
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
const { ensureAuthenticated } = require('../config/auth.js');
router.get('/', (req, res) => {
res.redirect('/dashboard');
});
router.get('/dashboard', ensureAuthenticated, (req, res) => {
res.render('dashboard', {
layout: './layouts/dashboard-layout.ejs'
});
});
// Login authentication
router.post('/login', (req, res) => {
const loginDetails = {
Username: req.body.email,
Password: req.body.password
}
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(loginDetails);
const userDetails = {
Username: req.body.email,
Pool: userPool
}
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userDetails);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: data => {
cognitoUser.getUserAttributes(function(err, result) {
if (err) {
console.log(err);
return;
} else {
console.log('LOGIN RESULTS')
console.log(result[0].Value.toString());
let userId = result[0].Value.toString();
let userCity = result[2].Value.toString();
console.log(userId);
console.log(userCity);
res.redirect(`/dashboard/${userCity}/${userId}/`)
};
});
},
onFailure: err => {
req.flash('error_msg', 'Invalid Credentials');
console.error(err);
res.redirect('/login');
}
})
});
Thank You!
UPDATE 10/21/19: Removed function that doesn't apply to the issue.
Also do not seem to have any JWT in local storage:
Click Here for image

How to get the sessionToken for currentUser?

I work on a web app hosted on heroku. As database I try to use back4app. With help of the back4app documention I have realized the log in. Since two days I try to get the session to work with it.
Therefore I have read a lot of blogs, articels and of course the parse documentation. But I'm not able to get it to work. I hope, you will find my problem. Following code is my lastest attempt:
const express = require('express');
const server = express();
const path = require('path');
const bodyParser = require('body-parser');
const port = process.env.PORT || 8080;
const back4app = require('parse/node');
back4app.initialize("xxx","yyy");
back4app.serverURL = 'https://parseapi.back4app.com/';
server.use(express.static('web'));
server.use(bodyParser.json());
server.get('/lgn', (req, resp) => {
console.log("server.get('/lgn',...");
resp.sendFile(path.join(__dirname + '/web/login.html'));
});
server.post('/lgn', (req, resp) => {
const data = req.body;
console.log("server.post('/lgn',...");
if(data.email != undefined){
console.log(data.email);
resetPassword(data);
} else{
logIn(data, function(err, user){
console.log(user.get("sessionToken"));
//How to get the user object in other routes?
console.log('session');
back4app.User.enableUnsafeCurrentUser(); //is this a secure solution?
back4app.User.currentAsync().then(function(userObj) {
console.dir(userObj.get("sessionToken"));
});
if(user){
resp.send( JSON.stringify( {url: '/'}) );
} else{
console.log(err);
}
});
}
});
function logIn(data, cb) {
// Create a new instance of the user class
var user = back4app.User
.logIn(data.username, data.password)
.then(function(user) {
console.log('User successful logged in with name: ' + user.get("username"));
cb(null, user);
})
.catch(function(error){
console.log("Error: " + error.code + " " + error.message);
cb(error);
});
}
server.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
});
The userObj is null. But why? What I have to do, that I get the currentUser and his session in other routes?
(I have also tryed to work with back4app.Session, but didn't get, what I want.)
It is unsafe to use the currentUser methods in a Node.js app.
Instead of:
logIn(data, function(err, user){
console.log(user.get("sessionToken"));
//How to get the user object in other routes?
console.log('session');
back4app.User.enableUnsafeCurrentUser(); //is this a secure solution?
back4app.User.currentAsync().then(function(userObj) {
console.dir(userObj.get("sessionToken"));
});
if(user){
resp.send( JSON.stringify( {url: '/'}) );
} else{
console.log(err);
}
});
Use:
logIn(data, function(err, user){
// This is your session token. You will have to send it back to your client. The client should store it and send it to the server in the other routes.
const sessionToken = user.get("sessionToken");
console.log(sessionToken);
//How to get the user object in other routes?
//In order to get the user in other routes you will have to use the sessionToken like this:
back4app.User.me({ sessionToken }).then(function(userObj) {
console.dir(userObj.get("sessionToken"));
});
if(user){
resp.send( JSON.stringify( {url: '/'}) );
} else{
console.log(err);
}
});

How to reset / change password in Node.js with Passport.js?

I use Passport.js in Node.js to create a login system. Everything is ok, but I do not know how to reset user password when they forget their password or they want to change it.
User model in MongoDB
var UserSchema = new Schema({
email: String,
username: String,
provider: String,
hashed_password: String,
salt: String,
});
Didn't really like the idea of hitting my database to store tokens, especially when you want to be creating and verifying tokens for many actions.
Instead I decided to copy how Django does it:
convert timestamp_today to base36 as today
convert user.id to base36 as ident
create hash containing:
timestamp_today
user.id
user.last_login
user.password
user.email
salt the hash with a hidden secret
create a route like : /change-password/:ident/:today-:hash
We test the req.params.timestamp in order to simply test if it's valid for today, cheapest test first. fail first.
Then we find the user, fail if it doesn't exist.
Then we generate the hash again from above, but with the timestamp from req.params
The reset link becomes invalid if :
they remember their password and login (last_login changes)
they're actually still logged in and:
just change their password (password changes)
just change their email (email changes)
tomorrow arrives (timestamp changes too much)
This way:
you're not storing these ephemeral things in your database
when the purpose of the token is to change the state of a thing, and that things state changed, then the purpose of the token is no longer securely relevant.
I tried to use node-password-reset as Matt617 suggested but didn't really care for it. It's about the only thing that's coming up in searches currently.
So some hours digging around, I found it easier to implement this on my own. In the end it took me about a day to get all the routes, UI, emails and everything working. I still need to enhance security a bit (reset counters to prevent abuse, etc.) but got the basics working:
Created two new routes, /forgot and /reset, which don't require the user to be logged in to access.
A GET on /forgot displays a UI with one input for email.
A POST on /forgot checks that there is a user with that address and generates a random token.
Update the user's record with the token and expiry date
Send an email with a link to /reset/{token}
A GET on /reset/{token} checks that there is a user with that token which hasn't expired then shows the UI with new password entry.
A POST on /reset (sends new pwd and token) checks that there is a user with that token which hasn't expired.
Update the user's password.
Set the user's token and expiry date to null
Here's my code for generating a token (taken from node-password-reset):
function generateToken() {
var buf = new Buffer(16);
for (var i = 0; i < buf.length; i++) {
buf[i] = Math.floor(Math.random() * 256);
}
var id = buf.toString('base64');
return id;
}
Hope this helps.
EDIT:
Here's the app.js. Note I'm keeping the entire user object in the session. I plan on moving to couchbase or similar in the future.
var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var flash = require('connect-flash');
var morgan = require('morgan');
var cookieParser = require('cookie-parser');
var cookieSession = require('cookie-session');
var bodyParser = require('body-parser');
var http = require('http');
var https = require('https');
var fs = require('fs');
var path = require('path');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var app = express();
app.set('port', 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
var cookies = cookieSession({
name: 'abc123',
secret: 'mysecret',
maxage: 10 * 60 * 1000
});
app.use(cookies);
app.use(favicon());
app.use(flash());
app.use(morgan());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(path.join(__dirname, 'public')));
module.exports = app;
passport.use(new LocalStrategy(function (username, password, done) {
return users.validateUser(username, password, done);
}));
//KEEP ENTIRE USER OBJECT IN THE SESSION
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
//Error handling after everything else
app.use(logErrors); //log all errors
app.use(clientErrorHandler); //special handler for xhr
app.use(errorHandler); //basic handler
http.createServer(app).listen(app.get('port'), function () {
console.log('Express server listening on HTTP port ' + app.get('port'));
});
EDIT:
Here are the routes.
app.get('/forgot', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
//UI with one input for email
res.render('forgot');
});
app.post('/forgot', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
users.forgot(req, res, function (err) {
if (err) {
req.flash('error', err);
}
else {
req.flash('success', 'Please check your email for further instructions.');
}
res.redirect('/');
});
});
app.get('/reset/:token', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
var token = req.params.token;
users.checkReset(token, req, res, function (err, data) {
if (err)
req.flash('error', err);
//show the UI with new password entry
res.render('reset');
});
});
app.post('/reset', function (req, res) {
if (req.isAuthenticated()) {
//user is alreay logged in
return res.redirect('/');
}
users.reset(req, res, function (err) {
if (err) {
req.flash('error', err);
return res.redirect('/reset');
}
else {
req.flash('success', 'Password successfully reset. Please login using new password.');
return res.redirect('/login');
}
});
});
Here the implementation of airtonix
const base64Encode = (data) => {
let buff = new Buffer.from(data);
return buff.toString('base64');
}
const base64Decode = (data) => {
let buff = new Buffer.from(data, 'base64');
return buff.toString('ascii');
}
const sha256 = (salt, password) => {
var hash = crypto.createHash('sha512', password);
hash.update(salt);
var value = hash.digest('hex');
return value;
}
api.post('/password-reset', (req, res) => {
try {
const email = req.body.email;
// Getting the user, only if active
let query = AccountModel.where( {username: email, active: true} );
query.select("_id salt username lastLoginDate");
query.findOne((err, account) => {
if(err) {
writeLog("ERROR", req.url + " - Error: -1 " + err.message);
res.status(500).send( { error: err.message, errnum: -1 } );
return;
}
if(!account){
writeLog("TRACE",req.url + " - Account not found!");
res.status(404).send( { error: "Account not found!", errnum: -2 } );
return;
}
// Generate the necessary data for the link
const today = base64Encode(new Date().toISOString());
const ident = base64Encode(account._id.toString());
const data = {
today: today,
userId: account._id,
lastLogin: account.lastLoginDate.toISOString(),
password: account.salt,
email: account.username
};
const hash = sha256(JSON.stringify(data), process.env.TOKENSECRET);
//HERE SEND AN EMAIL TO THE ACCOUNT
return;
});
} catch (err) {
writeLog("ERROR",req.url + " - Unexpected error during the password reset process. " + err.message);
res.status(500).send( { error: "Unexpected error during the password reset process :| " + err.message, errnum: -99 } );
return;
}
});
api.get('/password-change/:ident/:today-:hash', (req, res) => {
try {
// Check if the link in not out of date
const today = base64Decode(req.params.today);
const then = moment(today);
const now = moment().utc();
const timeSince = now.diff(then, 'hours');
if(timeSince > 2) {
writeLog("ERROR", req.url + " - The link is invalid. Err -1");
res.status(500).send( { error: "The link is invalid.", errnum: -1 } );
return;
}
const userId = base64Decode(req.params.ident);
// Getting the user, only if active
let query = AccountModel.where( {_id: userId, active: true} );
query.select("_id salt username lastLoginDate");
query.findOne((err, account) => {
if(err) {
writeLog("ERROR", req.url + " - Error: -2 " + err.message);
res.status(500).send( { error: err.message, errnum: -2 } );
return;
}
if(!account){
writeLog("TRACE", req.url + " - Account not found! Err -3");
res.status(404).send( { error: "Account not found!", errnum: -3 } );
return;
}
// Hash again all the data to compare it with the link
// THe link in invalid when:
// 1. If the lastLoginDate is changed, user has already do a login
// 2. If the salt is changed, the user has already changed the password
const data = {
today: req.params.today,
userId: account._id,
lastLogin: account.lastLoginDate.toISOString(),
password: account.salt,
email: account.username
};
const hash = sha256(JSON.stringify(data), process.env.TOKENSECRET);
if(hash !== req.params.hash) {
writeLog("ERROR", req.url + " - The link is invalid. Err -4");
res.status(500).send( { error: "The link is invalid.", errnum: -4 } );
return;
}
//HERE REDIRECT TO THE CHANGE PASSWORD FORM
});
} catch (err) {
writeLog("ERROR",req.url + " - Unexpected error during the password reset process. " + err.message);
res.status(500).send( { error: "Unexpected error during the password reset process :| " + err.message, errnum: -99 } );
return;
}
});
In my application I'm using passport-local with this Account model
import mongoose from 'mongoose';
import passportLocalMongoose from 'passport-local-mongoose';
const Schema = mongoose.Schema;
let accountSchema = new Schema ({
active: { type: Boolean, default: false },
activationDate: { type: Date },
signupDate: { type: Date },
lastLoginDate: { type: Date },
lastLogoutDate: { type: Date },
salt: {type: String},
hash: {type: String}
});
accountSchema.plugin(passportLocalMongoose); // attach the passport-local-mongoose plugin
module.exports = mongoose.model('Account', accountSchema);
create a random reset key in your DB, persist it with a timestamp. then create a new route that accepts the reset key. verify the timestamp before changing the password to the new password from the route.
never tried this but i ran across this some time ago which is similar to what you need:
https://github.com/substack/node-password-reset
Maybe this article could help:
Password Reset Emails In Your React App Made Easy with Nodemailer
i am not a professional or expert but this worked for me ,well if you are sending password reset email u, while using passport authentication then use
// here User is my model enter code here`
User.findOne({ username: req.body.email }, (err, user) => {
user.setPassword( req.body.password, function(err, users) => {
User.updateOne({ _id: users._id },{ hash: users.hash, salt: users.salt },
(err,result) => {
if (err) {
} else {
}
})
})
})
now here use id to update if you email instead of id in updateOne() it wont work

Resources