Skip to content

Commit 57aa2d2

Browse files
author
DzianisKryvichanin
committed
Create two-factor authentication routes
1 parent 40ae2f8 commit 57aa2d2

File tree

5 files changed

+318
-8
lines changed

5 files changed

+318
-8
lines changed

app/controllers/auth.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
const speakeasy = require('speakeasy');
2+
const { toDataURL } = require('qrcode');
3+
4+
// TODO: move this use to db
5+
const user = {
6+
firstName: 'Jon',
7+
lastName: 'Doe',
8+
email: 'jon.doe@gmail.com',
9+
password: 'test'
10+
};
11+
12+
const setup2Auth = async (req, res, next) => {
13+
try {
14+
const secret = speakeasy.generateSecret({ length: 10 });
15+
const dataUrl = await toDataURL(secret.otpauth_url);
16+
17+
// TODO: save to logged in user.
18+
user.twofactor = {
19+
secret: '',
20+
tempSecret: secret.base32,
21+
dataUrl,
22+
otpURL: secret.otpauth_url
23+
};
24+
25+
res.json({
26+
message: 'Verify OTP',
27+
tempSecret: secret.base32,
28+
dataUrl,
29+
otpURL: secret.otpauth_url
30+
});
31+
} catch (err) {
32+
next(err);
33+
}
34+
};
35+
36+
// get 2fa details
37+
const get2Auth = (req, res) => {
38+
res.json(user.twofactor);
39+
};
40+
41+
// disable 2fa
42+
const remove2Auth = (req, res) => {
43+
delete user.twofactor;
44+
45+
res.send('Successfully remove user 2Auth');
46+
};
47+
48+
const verifyAuth = (req, res) => {
49+
const { token } = req.body;
50+
51+
const verified = speakeasy.totp.verify({
52+
secret: user.twofactor.tempSecret, // TODO: Receive secret from user
53+
encoding: 'base32',
54+
token
55+
});
56+
57+
if (verified) {
58+
user.twofactor.secret = user.twofactor.tempSecret; // set secret, confirm 2fa
59+
return res.send('Two-factor auth enabled');
60+
}
61+
62+
return res.status(400).send('Invalid token, verification failed');
63+
};
64+
65+
const login = (req, res) => {
66+
if (!user.twofactor || !user.twofactor.secret) { // two factor is not enabled by the user
67+
// check credentials
68+
if (req.body.email === user.email && req.body.password === user.password) {
69+
return res.send('Successfully authenticated without OTP'); // authenticate user
70+
}
71+
return res.status(400).send('Invald email or password');
72+
}
73+
// two factor enabled
74+
if (req.body.email !== user.email || req.body.password !== user.password) {
75+
return res.status(400).send('Invald email or password');
76+
}
77+
78+
// check if otp is passed, if not then ask for OTP
79+
if (!req.headers['x-otp']) {
80+
return res.status(206).send('Please enter otp to continue');
81+
}
82+
83+
// validate otp
84+
const verified = speakeasy.totp.verify({
85+
secret: user.twofactor.secret,
86+
encoding: 'base32',
87+
token: req.headers['x-otp']
88+
});
89+
90+
return verified
91+
? res.send('Successfully authenticated with OTP')
92+
: res.status(400).send('Invalid OTP');
93+
};
94+
95+
module.exports = {
96+
setup2Auth,
97+
get2Auth,
98+
remove2Auth,
99+
verifyAuth,
100+
login
101+
};

app/server.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const nconf = require('nconf');
44
const morgan = require('morgan');
55

66
const logger = require('./utils/logger')('server');
7+
const initRoutes = require('./utils/initRoutes.js');
78
const { errorHandler, notFound } = require('./middleware/error_handler');
89

910
const initApp = () => {
@@ -14,6 +15,8 @@ const initApp = () => {
1415
app.use(morgan(env));
1516
}
1617

18+
initRoutes(app);
19+
1720
app.use(notFound);
1821
app.use(errorHandler);
1922

app/utils/initRoutes.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const bodyParser = require('body-parser');
2+
const authRouter = require('express').Router();
3+
4+
const {
5+
setup2Auth, get2Auth, remove2Auth, verifyAuth, login
6+
} = require('../controllers/auth');
7+
8+
const ensureLogin = (req, res, next) => {
9+
// TODO: Check user credentials in the db and set req.user
10+
next();
11+
};
12+
13+
const initRoutes = (app) => {
14+
authRouter.use(bodyParser.json());
15+
16+
authRouter.post('/auth/2fa', ensureLogin, setup2Auth);
17+
authRouter.get('/auth/2fa', ensureLogin, get2Auth);
18+
authRouter.delete('/auth/2fa', ensureLogin, remove2Auth);
19+
20+
authRouter.post('/auth/login', ensureLogin, login);
21+
22+
authRouter.post('/auth/verify', ensureLogin, verifyAuth);
23+
24+
app.use(authRouter);
25+
};
26+
27+
module.exports = initRoutes;

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"author": "Internal Group",
1515
"license": "MIT",
1616
"dependencies": {
17+
"body-parser": "^1.18.3",
1718
"bunyan": "^1.8.12",
1819
"bunyan-prettystream": "^0.1.3",
1920
"cookie-parser": "^1.4.3",
@@ -28,7 +29,9 @@
2829
"oauth2-server": "^3.0.0",
2930
"pg": "^7.4.1",
3031
"pg-hstore": "^2.3.2",
31-
"sequelize": "^4.37.6"
32+
"qrcode": "^1.2.0",
33+
"sequelize": "^4.37.6",
34+
"speakeasy": "^2.0.0"
3235
},
3336
"engines": {
3437
"node": ">=8.10.0"

0 commit comments

Comments
 (0)