@@ -252,6 +252,120 @@ describe("access token refresh on user property changes", () => {
252252 } ) ;
253253 } ) ;
254254
255+ describe ( "requires_totp_mfa changes" , ( ) => {
256+ it ( "should have requires_totp_mfa=false for a new user without MFA" , async ( { expect } ) => {
257+ const { clientApp } = await createApp ( {
258+ config : {
259+ credentialEnabled : true ,
260+ } ,
261+ } ) ;
262+
263+ await clientApp . signUpWithCredential ( {
264+ email : "test@example.com" ,
265+ password : "password123" ,
266+ verificationCallbackUrl : "http://localhost:3000" ,
267+ } ) ;
268+
269+ const user = await clientApp . getUser ( { or : "throw" } ) ;
270+ const token = await user . getAccessToken ( ) ;
271+ expect ( token ) . toBeDefined ( ) ;
272+
273+ const payload = decodeAccessToken ( token ! ) ;
274+ expect ( payload . requires_totp_mfa ) . toBe ( false ) ;
275+ } ) ;
276+
277+ it ( "should return a new access token with requires_totp_mfa=true after enabling TOTP MFA" , async ( { expect } ) => {
278+ const { clientApp } = await createApp ( {
279+ config : {
280+ credentialEnabled : true ,
281+ } ,
282+ } ) ;
283+
284+ await clientApp . signUpWithCredential ( {
285+ email : "test@example.com" ,
286+ password : "password123" ,
287+ verificationCallbackUrl : "http://localhost:3000" ,
288+ } ) ;
289+
290+ const user = await clientApp . getUser ( { or : "throw" } ) ;
291+ const initialToken = await user . getAccessToken ( ) ;
292+ expect ( decodeAccessToken ( initialToken ! ) . requires_totp_mfa ) . toBe ( false ) ;
293+
294+ const totpSecret = crypto . getRandomValues ( new Uint8Array ( 20 ) ) ;
295+ await user . update ( { totpMultiFactorSecret : totpSecret } ) ;
296+
297+ const updatedToken = await user . getAccessToken ( ) ;
298+ expect ( updatedToken ) . toBeDefined ( ) ;
299+
300+ const updatedPayload = decodeAccessToken ( updatedToken ! ) ;
301+ expect ( updatedPayload . requires_totp_mfa ) . toBe ( true ) ;
302+
303+ expect ( updatedToken ) . not . toBe ( initialToken ) ;
304+ } ) ;
305+
306+ it ( "should return a new access token with requires_totp_mfa=false after disabling TOTP MFA" , async ( { expect } ) => {
307+ const { clientApp } = await createApp ( {
308+ config : {
309+ credentialEnabled : true ,
310+ } ,
311+ } ) ;
312+
313+ await clientApp . signUpWithCredential ( {
314+ email : "test@example.com" ,
315+ password : "password123" ,
316+ verificationCallbackUrl : "http://localhost:3000" ,
317+ } ) ;
318+
319+ const user = await clientApp . getUser ( { or : "throw" } ) ;
320+
321+ const totpSecret = crypto . getRandomValues ( new Uint8Array ( 20 ) ) ;
322+ await user . update ( { totpMultiFactorSecret : totpSecret } ) ;
323+
324+ const mfaEnabledToken = await user . getAccessToken ( ) ;
325+ expect ( decodeAccessToken ( mfaEnabledToken ! ) . requires_totp_mfa ) . toBe ( true ) ;
326+
327+ await user . update ( { totpMultiFactorSecret : null } ) ;
328+
329+ const mfaDisabledToken = await user . getAccessToken ( ) ;
330+ expect ( mfaDisabledToken ) . toBeDefined ( ) ;
331+
332+ const disabledPayload = decodeAccessToken ( mfaDisabledToken ! ) ;
333+ expect ( disabledPayload . requires_totp_mfa ) . toBe ( false ) ;
334+
335+ expect ( mfaDisabledToken ) . not . toBe ( mfaEnabledToken ) ;
336+ } ) ;
337+
338+ it ( "should update requires_totp_mfa in access token when admin enables MFA for a user" , async ( { expect } ) => {
339+ const { clientApp, adminApp } = await createApp ( {
340+ config : {
341+ credentialEnabled : true ,
342+ } ,
343+ } ) ;
344+
345+ await clientApp . signUpWithCredential ( {
346+ email : "test@example.com" ,
347+ password : "password123" ,
348+ verificationCallbackUrl : "http://localhost:3000" ,
349+ } ) ;
350+
351+ const user = await clientApp . getUser ( { or : "throw" } ) ;
352+ const initialToken = await user . getAccessToken ( ) ;
353+ expect ( decodeAccessToken ( initialToken ! ) . requires_totp_mfa ) . toBe ( false ) ;
354+
355+ const adminUsers = await adminApp . listUsers ( { query : "test@example.com" } ) ;
356+ const totpSecret = crypto . getRandomValues ( new Uint8Array ( 20 ) ) ;
357+ await adminUsers [ 0 ] . update ( { totpMultiFactorSecret : totpSecret } ) ;
358+
359+ await user . update ( { } ) ;
360+
361+ const updatedToken = await user . getAccessToken ( ) ;
362+ expect ( updatedToken ) . toBeDefined ( ) ;
363+
364+ const updatedPayload = decodeAccessToken ( updatedToken ! ) ;
365+ expect ( updatedPayload . requires_totp_mfa ) . toBe ( true ) ;
366+ } ) ;
367+ } ) ;
368+
255369 describe ( "getAccessToken reflects current state" , ( ) => {
256370 it ( "should always return a token reflecting the current user state" , async ( { expect } ) => {
257371 const { clientApp, serverApp } = await createApp ( {
0 commit comments