@@ -425,6 +425,45 @@ public async Task TwoFactorAuthenticatorSignInFailWithoutLockout()
425425 auth . Verify ( ) ;
426426 }
427427
428+ [ Fact ]
429+ public async Task TwoFactorAuthenticatorSignInAsyncReturnsLockedOut ( )
430+ {
431+ // Setup
432+ var user = new PocoUser { UserName = "Foo" } ;
433+ string providerName = "Authenticator" ;
434+ const string code = "3123" ;
435+ var manager = SetupUserManager ( user ) ;
436+ var lockedout = false ;
437+ manager . Setup ( m => m . AccessFailedAsync ( user ) ) . Returns ( ( ) =>
438+ {
439+ lockedout = true ;
440+ return Task . FromResult ( IdentityResult . Success ) ;
441+ } ) . Verifiable ( ) ;
442+ manager . Setup ( m => m . SupportsUserLockout ) . Returns ( true ) . Verifiable ( ) ;
443+ manager . Setup ( m => m . VerifyTwoFactorTokenAsync ( user , providerName ?? TokenOptions . DefaultAuthenticatorProvider , code ) ) . ReturnsAsync ( false ) . Verifiable ( ) ;
444+ manager . Setup ( m => m . IsLockedOutAsync ( user ) ) . Returns ( ( ) => Task . FromResult ( lockedout ) ) ;
445+
446+ var context = new DefaultHttpContext ( ) ;
447+ var auth = MockAuth ( context ) ;
448+ var helper = SetupSignInManager ( manager . Object , context ) ;
449+ var twoFactorInfo = new SignInManager < PocoUser > . TwoFactorAuthenticationInfo { User = user } ;
450+ if ( providerName != null )
451+ {
452+ helper . Options . Tokens . AuthenticatorTokenProvider = providerName ;
453+ }
454+ var id = SignInManager < PocoUser > . StoreTwoFactorInfo ( user . Id , null ) ;
455+ auth . Setup ( a => a . AuthenticateAsync ( context , IdentityConstants . TwoFactorUserIdScheme ) )
456+ . ReturnsAsync ( AuthenticateResult . Success ( new AuthenticationTicket ( id , null , IdentityConstants . TwoFactorUserIdScheme ) ) ) . Verifiable ( ) ;
457+
458+ // Act
459+ var result = await helper . TwoFactorAuthenticatorSignInAsync ( code , isPersistent : false , rememberClient : false ) ;
460+
461+ // Assert
462+ Assert . True ( result . IsLockedOut ) ;
463+ manager . Verify ( ) ;
464+ auth . Verify ( ) ;
465+ }
466+
428467 [ Theory ]
429468 [ InlineData ( true , true , true ) ]
430469 [ InlineData ( true , true , false ) ]
@@ -670,6 +709,41 @@ public async Task CanTwoFactorSignIn(bool isPersistent, bool supportsLockout, bo
670709 auth . Verify ( ) ;
671710 }
672711
712+ [ Fact ]
713+ public async Task TwoFactorSignInAsyncReturnsLockedOut ( )
714+ {
715+ // Setup
716+ var user = new PocoUser { UserName = "Foo" } ;
717+ var manager = SetupUserManager ( user ) ;
718+ var provider = "twofactorprovider" ;
719+ var code = "123456" ;
720+ var lockedout = false ;
721+ manager . Setup ( m => m . AccessFailedAsync ( user ) ) . Returns ( ( ) =>
722+ {
723+ lockedout = true ;
724+ return Task . FromResult ( IdentityResult . Success ) ;
725+ } ) . Verifiable ( ) ;
726+ manager . Setup ( m => m . SupportsUserLockout ) . Returns ( true ) . Verifiable ( ) ;
727+ manager . Setup ( m => m . IsLockedOutAsync ( user ) ) . Returns ( ( ) => Task . FromResult ( lockedout ) ) ;
728+ manager . Setup ( m => m . VerifyTwoFactorTokenAsync ( user , provider , code ) ) . ReturnsAsync ( false ) . Verifiable ( ) ;
729+
730+ var context = new DefaultHttpContext ( ) ;
731+ var auth = MockAuth ( context ) ;
732+ var helper = SetupSignInManager ( manager . Object , context ) ;
733+ var id = SignInManager < PocoUser > . StoreTwoFactorInfo ( user . Id , loginProvider : null ) ;
734+
735+ auth . Setup ( a => a . AuthenticateAsync ( context , IdentityConstants . TwoFactorUserIdScheme ) )
736+ . ReturnsAsync ( AuthenticateResult . Success ( new AuthenticationTicket ( id , null , IdentityConstants . TwoFactorUserIdScheme ) ) ) . Verifiable ( ) ;
737+
738+ // Act
739+ var result = await helper . TwoFactorSignInAsync ( provider , code , isPersistent : false , rememberClient : false ) ;
740+
741+ // Assert
742+ Assert . True ( result . IsLockedOut ) ;
743+ manager . Verify ( ) ;
744+ auth . Verify ( ) ;
745+ }
746+
673747 [ Fact ]
674748 public async Task RememberClientStoresUserId ( )
675749 {
@@ -1169,16 +1243,9 @@ public async Task CheckPasswordSignInLockedOutResultIsDependentOnTheAccessFailed
11691243 manager . Verify ( ) ;
11701244 }
11711245
1172- public static object [ ] [ ] AccessFailedResults => new object [ ] [ ]
1173- {
1174- new object [ ] { IdentityResult . Success } ,
1175- new object [ ] { null } ,
1176- new object [ ] { IdentityResult . Failed ( ) } ,
1177- } ;
1178-
11791246 [ Theory ]
1180- [ MemberData ( nameof ( AccessFailedResults ) ) ]
1181- public async Task TwoFactorSignInLockedOutResultIsAlwaysGenericFailureRegardlessOfTheAccessFailedAsyncResult ( IdentityResult accessFailedResult )
1247+ [ MemberData ( nameof ( ExpectedLockedOutSignInResultsGivenAccessFailedResults ) ) ]
1248+ public async Task TwoFactorSignInLockedOutResultIsDependentOnTheAccessFailedAsyncResult ( IdentityResult accessFailedResult , SignInResult expectedSignInResult )
11821249 {
11831250 // Setup
11841251 var isLockedOutCallCount = 0 ;
@@ -1204,8 +1271,7 @@ public async Task TwoFactorSignInLockedOutResultIsAlwaysGenericFailureRegardless
12041271 var result = await helper . TwoFactorSignInAsync ( provider , code , false , false ) ;
12051272
12061273 // Assert
1207- // Unlike password sign in, 2fa always returns SignInResult.Failed rather than LockedOut.
1208- Assert . Same ( SignInResult . Failed , result ) ;
1274+ Assert . Same ( expectedSignInResult , result ) ;
12091275 manager . Verify ( ) ;
12101276 auth . Verify ( ) ;
12111277 }
0 commit comments