-
Notifications
You must be signed in to change notification settings - Fork 21
/
sessions.js
executable file
·109 lines (100 loc) · 3.93 KB
/
sessions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
const express = require('express');
const _ = require('lodash-checkit');
const path = require('path');
const { sendPassCodeEmail } = require('../lib/email');
const { validatePostLoginRedirectPath } = require('../lib/redirect_validation');
const { isMicrosoftSafeLinksRequest } = require('../lib/access-helpers');
const router = express.Router({ mergeParams: true });
const {
getUser,
getAccessToken,
createAccessToken,
incrementAccessTokenUses,
markAccessTokenUsed,
} = require('../db');
const { isUSDRSuperAdmin } = require('../lib/access-helpers');
// Increasing the max-uses to ensure users are able to log-in even if their email client/security provider has clicked on the link already.
// Specifically the issue was identified with Microsoft Safe Links, which clicks on the link to check if it is safe.
const MAX_ACCESS_TOKEN_USES = 4;
// the validation URL is sent in the authentication email:
// http://localhost:8080/api/sessions/?passcode=97fa7091-77ae-4905-b62e-97a7b4699abd
//
router.get('/', isMicrosoftSafeLinksRequest, async (req, res) => {
const { passcode } = req.query;
if (passcode) {
res.sendFile(path.join(__dirname, '../static/login_redirect.html'));
} else if (req.signedCookies && req.signedCookies.userId) {
const user = await getUser(req.signedCookies.userId);
res.json({ user: { ...user, isUSDRSuperAdmin: isUSDRSuperAdmin(user) } });
} else {
res.json({ message: 'No session' });
}
});
router.post('/init', async (req, res) => {
const WEBSITE_DOMAIN = process.env.WEBSITE_DOMAIN || '';
const { passcode } = req.body;
if (!passcode) {
res.redirect(`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent('Invalid access token')}`);
return;
}
const token = await getAccessToken(passcode);
if (!token) {
res.redirect(`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent('Invalid access token')}`);
} else if (new Date() > token.expires) {
res.redirect(
`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent('Access token has expired')}`,
);
} else if (token.used) {
res.redirect(`${WEBSITE_DOMAIN}/login?message=${encodeURIComponent(
'Login link has already been used - please re-submit your email address',
)}`);
} else {
const uses = await incrementAccessTokenUses(passcode);
if (uses >= MAX_ACCESS_TOKEN_USES) {
await markAccessTokenUsed(passcode);
}
let destination = WEBSITE_DOMAIN || '/';
const redirectTo = validatePostLoginRedirectPath(req.body.redirect_to);
if (redirectTo) {
destination = (WEBSITE_DOMAIN || '') + redirectTo;
}
res.cookie('userId', token.user_id, { signed: true });
res.redirect(destination);
}
});
router.get('/logout', (req, res) => {
res.clearCookie('userId');
res.json({});
});
// eslint-disable-next-line consistent-return
router.post('/', async (req, res, next) => {
if (!req.body.email) {
res.statusMessage = 'No Email Address provided';
return res.sendStatus(400);
}
const email = req.body.email.toLowerCase();
if (!_.isEmail(email)) {
res.statusMessage = 'Invalid Email Address';
return res.sendStatus(400);
}
try {
const passcode = await createAccessToken(email);
const domain = process.env.API_DOMAIN || process.env.WEBSITE_DOMAIN || req.headers.origin;
const redirectTo = validatePostLoginRedirectPath(req.body.redirect_to);
await sendPassCodeEmail(email, passcode, domain, redirectTo);
res.json({
success: true,
message: `Email sent to ${email}. Check your inbox`,
});
} catch (e) {
if (e.message.match(/User .* not found/)) {
res.json({
success: false,
message: e.message,
});
} else {
next(e);
}
}
});
module.exports = router;