(loginData, _, req)
| 125 | throw 'Invalid MFA data'; |
| 126 | } |
| 127 | async validateLogin(loginData, _, req) { |
| 128 | const saveResponse = { |
| 129 | doNotSave: true, |
| 130 | }; |
| 131 | const token = loginData.token; |
| 132 | const auth = req.original.get('authData') || {}; |
| 133 | const { secret, recovery, mobile, token: saved, expiry } = auth.mfa || {}; |
| 134 | if (this.sms && mobile) { |
| 135 | if (token === 'request') { |
| 136 | const { token: sendToken, expiry } = await this.sendSMS(mobile); |
| 137 | auth.mfa.token = sendToken; |
| 138 | auth.mfa.expiry = expiry; |
| 139 | req.object.set('authData', auth); |
| 140 | await req.object.save(null, { useMasterKey: true }); |
| 141 | throw 'Please enter the token'; |
| 142 | } |
| 143 | if (!saved || token !== saved) { |
| 144 | throw 'Invalid MFA token 1'; |
| 145 | } |
| 146 | if (new Date() > expiry) { |
| 147 | throw 'Invalid MFA token 2'; |
| 148 | } |
| 149 | delete auth.mfa.token; |
| 150 | delete auth.mfa.expiry; |
| 151 | return { |
| 152 | save: auth.mfa, |
| 153 | }; |
| 154 | } |
| 155 | if (this.totp) { |
| 156 | if (typeof token !== 'string') { |
| 157 | throw 'Invalid MFA token'; |
| 158 | } |
| 159 | if (!secret) { |
| 160 | return saveResponse; |
| 161 | } |
| 162 | const recoveryIndex = recovery?.indexOf(token) ?? -1; |
| 163 | if (recoveryIndex >= 0) { |
| 164 | const updatedRecovery = [...recovery]; |
| 165 | updatedRecovery.splice(recoveryIndex, 1); |
| 166 | return { |
| 167 | save: { ...auth.mfa, recovery: updatedRecovery }, |
| 168 | }; |
| 169 | } |
| 170 | const totp = new TOTP({ |
| 171 | algorithm: this.algorithm, |
| 172 | digits: this.digits, |
| 173 | period: this.period, |
| 174 | secret: Secret.fromBase32(secret), |
| 175 | }); |
| 176 | const valid = totp.validate({ |
| 177 | token, |
| 178 | }); |
| 179 | if (valid === null) { |
| 180 | throw 'Invalid MFA token'; |
| 181 | } |
| 182 | } |
| 183 | return saveResponse; |
| 184 | } |
no test coverage detected