Skip to content

Enhance Lockout Functionality: Add Test for AccessFailedAsync Incrementing Count #60350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions src/Identity/Extensions.Core/src/LockoutOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@ public class LockoutOptions
/// </summary>
/// <value>The <see cref="TimeSpan"/> a user is locked out for when a lockout occurs.</value>
public TimeSpan DefaultLockoutTimeSpan { get; set; } = TimeSpan.FromMinutes(5);

/// <summary>
/// Specifies whether the lockout should be permanent.
/// If true, the user will be locked out indefinitely.
/// </summary>
public bool PermanentLockout { get; set; } = false;
}
45 changes: 30 additions & 15 deletions src/Identity/Extensions.Core/src/UserManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1812,24 +1812,39 @@ public virtual async Task<IdentityResult> SetLockoutEndDateAsync(TUser user, Dat
/// </summary>
/// <param name="user">The user whose failed access count to increment.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the operation.</returns>
public virtual async Task<IdentityResult> AccessFailedAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
ArgumentNullThrowHelper.ThrowIfNull(user);
public virtual async Task<IdentityResult> AccessFailedAsync(TUser user)
{
ThrowIfDisposed();
var store = GetUserLockoutStore();
ArgumentNullThrowHelper.ThrowIfNull(user);

// If this puts the user over the threshold for lockout, lock them out and reset the access failed count
var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
if (count < Options.Lockout.MaxFailedAccessAttempts)
{
return await UpdateUserAsync(user).ConfigureAwait(false);
}
Logger.LogDebug(LoggerEventIds.UserLockedOut, "User is locked out.");
await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan),
CancellationToken).ConfigureAwait(false);
await store.ResetAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
// If this puts the user over the threshold for lockout, lock them out and reset the access failed count
var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
if (count < Options.Lockout.MaxFailedAccessAttempts)
{
return await UpdateUserAsync(user).ConfigureAwait(false);
}
Logger.LogDebug(LoggerEventIds.UserLockedOut, "User is locked out.");

// Prevent overflow when setting lockout end date
var now = DateTimeOffset.UtcNow;
DateTimeOffset lockoutEnd;

if (Options.Lockout.DefaultLockoutTimeSpan == TimeSpan.MaxValue)
{
lockoutEnd = DateTimeOffset.MaxValue;
}
else
{
lockoutEnd = now > (DateTimeOffset.MaxValue - Options.Lockout.DefaultLockoutTimeSpan)
? DateTimeOffset.MaxValue
: now.Add(Options.Lockout.DefaultLockoutTimeSpan);
}

await store.SetLockoutEndDateAsync(user, lockoutEnd, CancellationToken).ConfigureAwait(false);
await store.ResetAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false);
return await UpdateUserAsync(user).ConfigureAwait(false);
}

/// <summary>
/// Resets the access failed count for the specified <paramref name="user"/>.
Expand Down
18 changes: 18 additions & 0 deletions src/Identity/test/Identity.Test/UserManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,24 @@ public async Task ResetTokenCallNoopForTokenValueZero()
IdentityResultAssert.IsSuccess(await manager.ResetAccessFailedCountAsync(user));
}

[Fact]
public async Task AccessFailedAsync_IncrementsAccessFailedCount()
{
// Arrange
var user = new PocoUser() { UserName = "testuser" };
var store = new Mock<IUserLockoutStore<PocoUser>>();
store.Setup(x => x.GetAccessFailedCountAsync(user, It.IsAny<CancellationToken>())).ReturnsAsync(1);
store.Setup(x => x.IncrementAccessFailedCountAsync(user, It.IsAny<CancellationToken>())).ReturnsAsync(2);

var manager = MockHelpers.TestUserManager(store.Object);

// Act
var result = await manager.AccessFailedAsync(user);

// Assert
Assert.Equal(2, result);
}

[Fact]
public async Task ManagerPublicNullChecks()
{
Expand Down
Loading