Skip to content

Instantly share code, notes, and snippets.

@ufhy
Forked from nemtsov/passport-auth-with-reset.js
Created February 18, 2022 06:34
Show Gist options
  • Save ufhy/bd5f257121d649432ab77c88c4f8f9d1 to your computer and use it in GitHub Desktop.
Save ufhy/bd5f257121d649432ab77c88c4f8f9d1 to your computer and use it in GitHub Desktop.
Passport auth with password reset
const crypto = require('crypto');
const { promisify } = require('util');
const express = require('express');
const asyncify = require('express-asyncify');
const session = require('express-session');
const createFileStore = require('session-file-store');
const nodemailer = require('nodemailer');
const nodemailerSendgrid = require('nodemailer-sendgrid');
const bodyParser = require('body-parser');
const pass = require('passport');
const LocalStrategy = require('passport-local');
const { Strategy: GoogleStrategy } = require('passport-google-oauth20');
const flash = require('connect-flash');
const templates = require('./templates-passport');
const PORT = 5000;
const SESSION_COOKIE_SECRET = '';
const SESSOIN_COOKIE_MAX_AGE_IN_MS = 60 * 60 * 1000;
const SESSION_COOKIE_IS_SECURE = false;
const GOOGLE_CLIENT_ID = '';
const GOOGLE_CLIENT_SECRET = '';
const SENDGRID_API_KEY = '';
const transport = nodemailer.createTransport(nodemailerSendgrid({
apiKey: SENDGRID_API_KEY,
}));
const users = [{
id: 'local/a0234aDdfj-2f4sdfa3oEerq-2U4',
fullName: 'A Ayevich',
email: 'hello@example.com',
password: 'password'
}];
pass.serializeUser((user, cb) => cb(null, user));
pass.deserializeUser((u, cb) => cb(null, u));
pass.use(new LocalStrategy({
usernameField: 'email',
}, (email, password, cb) => {
const user = users.find(u => u.email === email);
cb(null, (user && user.password === password) ? user : false);
}));
pass.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: `http://localhost:${PORT}/auth/google/callback`
}, (accessToken, refreshToken, profile, cb) => {
const user = {
id: `google/${profile.id}`,
email: profile.email,
fullName: profile.displayName,
profile,
tokens: { accessToken, refreshToken },
};
users.push(user);
cb(null, user);
}));
const app = asyncify(express());
const FileStore = createFileStore(session);
app.disable('x-powered-by');
app.use(session({
store: new FileStore(),
name: 'sid',
resave: false,
saveUninitialized: false,
secret: SESSION_COOKIE_SECRET,
cookie: {
maxAge: SESSOIN_COOKIE_MAX_AGE_IN_MS,
secure: SESSION_COOKIE_IS_SECURE,
sameSite: 'lax',
},
}));
app.use(flash());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(pass.initialize());
app.use(pass.session());
app.get('/', (req, res) => {
res.setHeader('Content-type', 'text/html');
res.end(templates.layout(`
${req.user ?
templates.loggedInGreeting(req.user) :
templates.loggedOut()}
`));
});
app.get('/login', (req, res) => {
res.setHeader('Content-type', 'text/html');
res.end(templates.layout(`
${templates.error(req.flash())}
${templates.loginForm()}
`));
});
app.get('/signup', (req, res) => {
res.setHeader('Content-type', 'text/html');
res.end(templates.layout(`
${templates.error(req.flash())}
${templates.signupForm()}
`));
});
app.post('/login', pass.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true,
}));
app.get('/forgot', (req, res, next) => {
res.setHeader('Content-type', 'text/html');
res.end(templates.layout(`
${templates.error(req.flash())}
${templates.forgotPassword()}
`));
});
app.post('/forgot', async (req, res, next) => {
const token = (await promisify(crypto.randomBytes)(20)).toString('hex');
const user = users.find(u => u.email === req.body.email);
if (!user) {
req.flash('error', 'No account with that email address exists.');
return res.redirect('/forgot');
}
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000;
const resetEmail = {
to: user.email,
from: 'passwordreset@example.com',
subject: 'Node.js Password Reset',
text: `
You are receiving this because you (or someone else) have requested the reset of the password for your account.
Please click on the following link, or paste this into your browser to complete the process:
http://${req.headers.host}/reset/${token}
If you did not request this, please ignore this email and your password will remain unchanged.
`,
};
await transport.sendMail(resetEmail);
req.flash('info', `An e-mail has been sent to ${user.email} with further instructions.`);
res.redirect('/forgot');
});
app.get('/reset/:token', (req, res) => {
const user = users.find(u => (
(u.resetPasswordExpires > Date.now()) &&
crypto.timingSafeEqual(Buffer.from(u.resetPasswordToken), Buffer.from(req.params.token))
));
if (!user) {
req.flash('error', 'Password reset token is invalid or has expired.');
return res.redirect('/forgot');
}
res.setHeader('Content-type', 'text/html');
res.end(templates.layout(`
${templates.error(req.flash())}
${templates.resetPassword(user.resetPasswordToken)}
`));
});
app.post('/reset/:token', async (req, res) => {
const user = users.find(u => (
(u.resetPasswordExpires > Date.now()) &&
crypto.timingSafeEqual(Buffer.from(u.resetPasswordToken), Buffer.from(req.params.token))
));
if (!user) {
req.flash('error', 'Password reset token is invalid or has expired.');
return res.redirect('/forgot');
}
user.password = req.body.password;
delete user.resetPasswordToken;
delete user.resetPasswordExpires;
const resetEmail = {
to: user.email,
from: 'passwordreset@example.com',
subject: 'Your password has been changed',
text: `
This is a confirmation that the password for your account "${user.email}" has just been changed.
`,
};
await transport.sendMail(resetEmail);
req.flash('success', `Success! Your password has been changed.`);
res.redirect('/');
});
app.get('/auth/google',
pass.authenticate('google', { scope: ['profile'] }));
app.get('/auth/google/callback',
pass.authenticate('google', { failureRedirect: '/login' }),
(req, res) => res.redirect('/'));
app.post('/signup', (req, res, next) => {
const user = {
id: 'local/a0234aDdfj-2f4sdfa3oEerq-2U4',
fullName: 'Boy Good',
email: req.body.email,
password: req.body.password,
};
users.push(user);
req.login(user, (err) => {
if (err) next(err);
else res.redirect('/');
});
});
app.listen(PORT, () => console.log(`on :${PORT}`));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment