forked from juice-shop/juice-shop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path2fa.js
164 lines (136 loc) · 4.71 KB
/
2fa.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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
const insecurity = require('../lib/insecurity')
const models = require('../models/')
const otplib = require('otplib')
const utils = require('../lib/utils')
const challenges = require('../data/datacache').challenges
const config = require('config')
otplib.authenticator.options = {
// Accepts tokens as valid even when they are 30sec to old or to new
// This is a standard as the clocks of the authenticator and server might not align perfectly.
window: 1
}
async function verify (req, res) {
const { tmpToken, totpToken } = req.body
try {
const { userId, type } = insecurity.verify(tmpToken)
if (type !== 'password_valid_needs_second_factor_token') {
throw new Error('Invalid token type')
}
const user = await models.User.findByPk(userId)
const isValid = otplib.authenticator.check(totpToken, user.totpSecret)
const plainUser = utils.queryResultToJson(user)
if (!isValid) {
return res.status(401).send()
}
if (utils.notSolved(challenges.twoFactorAuthUnsafeSecretStorageChallenge) && user.email === 'wurstbrot@' + config.get('application.domain')) {
utils.solve(challenges.twoFactorAuthUnsafeSecretStorageChallenge)
}
const [ basket ] = await models.Basket.findOrCreate({ where: { userId }, defaults: {} })
const token = insecurity.authorize(plainUser)
plainUser.bid = basket.id // keep track of original basket for challenge solution check
insecurity.authenticatedUsers.put(token, plainUser)
res.json({ authentication: { token, bid: basket.id, umail: user.email } })
} catch (error) {
res.status(401).send()
}
}
/**
* Check the 2FA status of the currently signed in user.
*
* When 2FA isnt setup, the result will include data requried to start the setup.
*/
async function status (req, res) {
try {
const data = insecurity.authenticatedUsers.from(req)
if (!data) {
throw new Error('You need to be logged in to see this')
}
const { data: user } = data
if (user.totpSecret === '') {
const secret = await otplib.authenticator.generateSecret()
res.json({
setup: false,
secret,
email: user.email,
setupToken: insecurity.authorize({
secret,
type: 'totp_setup_secret'
})
})
} else {
res.json({
setup: true
})
}
} catch (error) {
res.status(401).send()
}
}
/**
* Sets Up 2FA for a User
* Requires 3 params:
* 1. The Users Password as a confirmation.
* 2. A Setup token. This is returned by the status endpoint.
* This containes a signed TOTP secret to ensure that the secret
* was generated by the server and wasnt tampered with by the client
* 3. The first TOTP Token, generated by the TOTP App. (e.g. Google Authenticator)
*/
async function setup (req, res) {
try {
const data = insecurity.authenticatedUsers.from(req)
if (!data) {
throw new Error('Need to login before setting up 2FA')
}
const { data: user } = data
const { password, setupToken, initialToken } = req.body
if (user.password !== insecurity.hash(password)) {
throw new Error('Passoword doesnt match stored password')
}
if (user.totpSecret !== '') {
throw new Error('User has 2fa already setup')
}
const { secret, type } = insecurity.verify(setupToken)
if (type !== 'totp_setup_secret') {
throw new Error('SetupToken is of wrong type')
}
if (!otplib.authenticator.check(initialToken, secret)) {
throw new Error('Inital token doesnt match the secret from the setupToken')
}
// Update db model and cached object
const userModel = await models.User.findByPk(user.id)
userModel.totpSecret = secret
await userModel.save()
insecurity.authenticatedUsers.updateFrom(req, utils.queryResultToJson(userModel))
res.status(200).send()
} catch (error) {
res.status(401).send()
}
}
/**
* Disables 2fa for the current user
*/
async function disable (req, res) {
try {
const data = insecurity.authenticatedUsers.from(req)
if (!data) {
throw new Error('Need to login before setting up 2FA')
}
const { data: user } = data
const { password } = req.body
if (user.password !== insecurity.hash(password)) {
throw new Error('Passoword doesnt match stored password')
}
// Update db model and cached object
const userModel = await models.User.findByPk(user.id)
userModel.totpSecret = ''
await userModel.save()
insecurity.authenticatedUsers.updateFrom(req, utils.queryResultToJson(userModel))
res.status(200).send()
} catch (error) {
res.status(401).send()
}
}
module.exports.disable = () => disable
module.exports.verify = () => verify
module.exports.status = () => status
module.exports.setup = () => setup