8
8
MfaChallenge ,
9
9
} from '@accounts/types' ;
10
10
import { AccountsServer , AccountsJsError } from '@accounts/server' ;
11
+ import ms from 'ms' ;
11
12
import { ErrorMessages } from './types' ;
12
13
import {
13
14
errors ,
@@ -27,6 +28,11 @@ export interface AccountsMfaOptions {
27
28
* Factors used for mfa
28
29
*/
29
30
factors : { [ key : string ] : AuthenticatorService | undefined } ;
31
+ /**
32
+ * Expressed in milliseconds or a string describing a time span zeit/ms.
33
+ * Default to '5m'.
34
+ */
35
+ expiresIn ?: string | number ;
30
36
}
31
37
32
38
interface AccountsMfaAuthenticateParams {
@@ -35,6 +41,7 @@ interface AccountsMfaAuthenticateParams {
35
41
36
42
const defaultOptions = {
37
43
errors,
44
+ expiresIn : '5m' as string | number ,
38
45
} ;
39
46
40
47
export class AccountsMfa < CustomUser extends User = User > implements AuthenticationService {
@@ -46,7 +53,11 @@ export class AccountsMfa<CustomUser extends User = User> implements Authenticati
46
53
47
54
constructor ( options : AccountsMfaOptions ) {
48
55
this . options = { ...defaultOptions , ...options } ;
49
- this . factors = options . factors ;
56
+ if ( typeof this . options . expiresIn === 'string' ) {
57
+ const milliseconds = ms ( this . options . expiresIn ) ;
58
+ this . options . expiresIn = milliseconds ;
59
+ }
60
+ this . factors = this . options . factors ;
50
61
}
51
62
52
63
public setStore ( store : DatabaseInterface < CustomUser > ) {
@@ -66,7 +77,7 @@ export class AccountsMfa<CustomUser extends User = User> implements Authenticati
66
77
) : Promise < CustomUser | null > {
67
78
const mfaToken = params . mfaToken ;
68
79
const mfaChallenge = mfaToken ? await this . db . findMfaChallengeByToken ( mfaToken ) : null ;
69
- if ( ! mfaChallenge || ! mfaChallenge . authenticatorId ) {
80
+ if ( ! mfaChallenge || ! mfaChallenge . authenticatorId || ! this . isMfaChallengeValid ( mfaChallenge ) ) {
70
81
throw new AccountsJsError (
71
82
this . options . errors . invalidMfaToken ,
72
83
AuthenticateErrors . InvalidMfaToken
@@ -86,7 +97,6 @@ export class AccountsMfa<CustomUser extends User = User> implements Authenticati
86
97
AuthenticateErrors . FactorNotFound
87
98
) ;
88
99
}
89
- // TODO we need to implement some time checking for the mfaToken (eg: expire after X minutes, probably based on the authenticator configuration)
90
100
if ( ! ( await factor . authenticate ( mfaChallenge , authenticator , params , infos ) ) ) {
91
101
throw new AccountsJsError (
92
102
this . options . errors . authenticationFailed ( authenticator . type ) ,
@@ -263,10 +273,15 @@ export class AccountsMfa<CustomUser extends User = User> implements Authenticati
263
273
}
264
274
265
275
public isMfaChallengeValid ( mfaChallenge : MfaChallenge ) : boolean {
266
- // TODO need to check that the challenge is not expired
267
276
if ( mfaChallenge . deactivated ) {
268
277
return false ;
269
278
}
279
+ const now = Date . now ( ) ;
280
+ const expirationDate =
281
+ new Date ( mfaChallenge . createdAt ) . getTime ( ) + ( this . options . expiresIn as number ) ;
282
+ if ( now > expirationDate ) {
283
+ return false ;
284
+ }
270
285
return true ;
271
286
}
272
287
}
0 commit comments