Skip to content

Commit

Permalink
Use PassportJS (#39)
Browse files Browse the repository at this point in the history
* Use passportjs

* add head to signup

* Update next, react

* Add compat for req.redirect

* update readme

* fix passport

* Fix passportjs

* Done intergrating passportjs

* Log out

* prettier
  • Loading branch information
hoangvvo committed Jan 8, 2020
1 parent a1747b7 commit 9862275
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 100 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Check out the [demo](https://nextjs-mongodb-app.hoangvvo.now.sh/).

### Authentication

*without passportjs*: [`a1747b7`](https://github.com/hoangvvo/nextjs-mongodb-app/commit/c36c5826f691032803760b5404ccec3446575504)
*with passportjs*: `master`

- Session
- Sign up/Sign in/Sign out

Expand Down
29 changes: 29 additions & 0 deletions lib/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import passport from 'passport';
import bcrypt from 'bcryptjs';
import { Strategy as LocalStrategy } from 'passport-local';
import { ObjectId } from 'mongodb';

passport.serializeUser((user, done) => {
done(null, user._id.toString());
});

// passport#160
passport.deserializeUser((req, id, done) => {
req.db
.collection('users')
.findOne(ObjectId(id))
.then(user => done(null, user));
});

passport.use(
new LocalStrategy(
{ usernameField: 'email', passReqToCallback: true },
async (req, email, password, done) => {
const user = await req.db.collection('users').findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) done(null, user);
else done(null, false, { message: 'Email or password is incorrect' });
},
),
);

export default passport;
14 changes: 0 additions & 14 deletions middlewares/authentication.js

This file was deleted.

12 changes: 12 additions & 0 deletions middlewares/compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function (req, res, next) {
res.redirect = (code, path) => {
let location = path;
let status = code;
if (typeof code === 'string') {
status = 302;
location = code;
}
res.writeHead(status, { Location: location }).end();
};
next();
}
7 changes: 5 additions & 2 deletions middlewares/middleware.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import nextConnect from 'next-connect';
import database from './database';
import session from './session';
import authentication from './authentication';
import passport from '../lib/passport';
import compat from './compat';

const middleware = nextConnect();

middleware.use(compat);
middleware.use(database);
middleware.use(session);
middleware.use(authentication);
middleware.use(passport.initialize());
middleware.use(passport.session());

export default middleware;
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
"dotenv": "^8.2.0",
"formidable": "^1.2.1",
"mongodb": "^3.3.0-beta2",
"next": "^9.0.4-canary.2",
"next": "^9.1.7",
"next-connect": "^0.5.1",
"next-session": "^2.1.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"passport": "^0.4.1",
"passport-local": "^1.0.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"validator": "^12.0.0"
},
"devDependencies": {
Expand Down
33 changes: 5 additions & 28 deletions pages/api/authenticate.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
import nextConnect from 'next-connect';
import bcrypt from 'bcryptjs';
import middleware from '../../middlewares/middleware';
import passport from '../../lib/passport';

const handler = nextConnect();

handler.use(middleware);

handler.post((req, res) => {
const { email, password } = req.body;

return req.db
.collection('users')
.findOne({ email })
.then((user) => {
if (user) {
return bcrypt.compare(password, user.password).then((result) => {
if (result) return Promise.resolve(user);
return Promise.reject(Error('The password you entered is incorrect'));
});
}
return Promise.reject(Error('The email does not exist'));
})
.then((user) => {
req.session.userId = user._id;
return res.send({
status: 'ok',
message: `Welcome back, ${user.name}!`,
});
})
.catch(error => res.send({
status: 'error',
message: error.toString(),
}));
});
handler.post(passport.authenticate('local', {
failureRedirect: '/login?fail=1',
successRedirect: '/',
}));

export default handler;
2 changes: 1 addition & 1 deletion pages/api/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ handler.get((req, res) => {
});

handler.delete((req, res) => {
delete req.session.userId;
req.logOut();
return res.status(200).send({
status: 'ok',
message: 'You have been logged out.',
Expand Down
46 changes: 18 additions & 28 deletions pages/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,28 @@ handler.use(middleware);

handler.post(async (req, res) => {
const { email, name, password } = req.body;
if (!isEmail(email)) {
return res.send({
status: 'error',
message: 'The email you entered is invalid.',
});
}

return req.db
.collection('users')
.countDocuments({ email })
.then((count) => {
if (count) {
return Promise.reject(Error('The email has already been used.'));
}
return bcrypt.hash(password, 10);
})
.then(hashedPassword => req.db.collection('users').insertOne({
email,
password: hashedPassword,
name,
}))
.then((user) => {
req.session.userId = user.insertedId;
res.status(201).send({
try {
if (!isEmail(email)) throw new Error('The email you entered is invalid.');
if (!password || !name) throw new Error('Missing field(s)');
if ((await req.db.collection('users').countDocuments({ email })) > 0) throw new Error('The email has already been used.');
const hashedPassword = await bcrypt.hash(password, 10);
const user = await req.db
.collection('users')
.insertOne({ email, password: hashedPassword, name })
.then(({ ops }) => ops[0]);
req.logIn(user, (err) => {
if (err) throw err;
res.status(201).json({
status: 'ok',
message: 'User signed up successfully',
});
})
.catch(error => res.send({
});
} catch (err) {
res.json({
status: 'error',
message: error.toString(),
}));
message: err.toString(),
});
}
});

export default handler;
30 changes: 7 additions & 23 deletions pages/login.jsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,26 @@
import React, { useState, useContext } from 'react';
import React, { useState } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import axioswal from 'axioswal';
import { UserContext } from '../components/UserContext';
import { useRouter } from 'next/router';
import Layout from '../components/layout';
import redirectTo from '../lib/redirectTo';

const LoginPage = () => {
const { dispatch } = useContext(UserContext);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (event) => {
event.preventDefault();
axioswal
.post('/api/authenticate', {
email,
password,
})
.then((data) => {
if (data.status === 'ok') {
// Fetch the user data for UserContext here
dispatch({ type: 'fetch' });
redirectTo('/');
}
});
};

const router = useRouter();
return (
<Layout>
<Head>
<title>Sign in</title>
</Head>
<h2>Sign in</h2>
<form onSubmit={handleSubmit} autoComplete="off">
<form action="/api/authenticate" method="post">
{router && router.query && router.query.fail ? <p style={{ color: 'red' }}>Incorrect email or password</p> : null}
<label htmlFor="email">
<input
id="email"
type="email"
name="email"
placeholder="Email address"
value={email}
onChange={e => setEmail(e.target.value)}
Expand All @@ -47,6 +30,7 @@ const LoginPage = () => {
<input
id="password"
type="password"
name="password"
placeholder="Password"
value={password}
onChange={e => setPassword(e.target.value)}
Expand Down
6 changes: 5 additions & 1 deletion pages/signup.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useContext } from 'react';
import Head from 'next/head';
import axioswal from 'axioswal';
import { UserContext } from '../components/UserContext';
import Layout from '../components/layout';
Expand Down Expand Up @@ -28,9 +29,12 @@ const SignupPage = () => {

return (
<Layout>
<Head>
<title>Sign up</title>
</Head>
<div>
<h2>Sign up</h2>
<form onSubmit={handleSubmit} autoComplete="off">
<form onSubmit={handleSubmit}>
<label htmlFor="name">
<input
id="name"
Expand Down

1 comment on commit 9862275

@vercel
Copy link

@vercel vercel bot commented on 9862275 Jan 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.