using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Mongo2Go; using MongoDB.Driver; using StellaOps.Authority.Plugins.Abstractions; using StellaOps.Authority.Plugin.Standard.Security; using StellaOps.Authority.Plugin.Standard.Storage; namespace StellaOps.Authority.Plugin.Standard.Tests; public class StandardUserCredentialStoreTests : IAsyncLifetime { private readonly MongoDbRunner runner; private readonly IMongoDatabase database; private readonly StandardPluginOptions options; private readonly StandardUserCredentialStore store; public StandardUserCredentialStoreTests() { runner = MongoDbRunner.Start(singleNodeReplSet: true); var client = new MongoClient(runner.ConnectionString); database = client.GetDatabase("authority-tests"); options = new StandardPluginOptions { PasswordPolicy = new PasswordPolicyOptions { MinimumLength = 8, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, RequireSymbol = false }, Lockout = new LockoutOptions { Enabled = true, MaxAttempts = 2, WindowMinutes = 1 } }; store = new StandardUserCredentialStore( "standard", database, options, new Pbkdf2PasswordHasher(), NullLogger.Instance); } [Fact] public async Task VerifyPasswordAsync_ReturnsSuccess_ForValidCredentials() { var registration = new AuthorityUserRegistration( "alice", "Password1!", "Alice", null, false, new[] { "admin" }, new Dictionary()); var upsert = await store.UpsertUserAsync(registration, CancellationToken.None); Assert.True(upsert.Succeeded); var result = await store.VerifyPasswordAsync("alice", "Password1!", CancellationToken.None); Assert.True(result.Succeeded); Assert.Equal("alice", result.User?.Username); } [Fact] public async Task VerifyPasswordAsync_EnforcesLockout_AfterRepeatedFailures() { await store.UpsertUserAsync( new AuthorityUserRegistration( "bob", "Password1!", "Bob", null, false, new[] { "operator" }, new Dictionary()), CancellationToken.None); var first = await store.VerifyPasswordAsync("bob", "wrong", CancellationToken.None); Assert.False(first.Succeeded); Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, first.FailureCode); var second = await store.VerifyPasswordAsync("bob", "stillwrong", CancellationToken.None); Assert.False(second.Succeeded); Assert.Equal(AuthorityCredentialFailureCode.LockedOut, second.FailureCode); Assert.NotNull(second.RetryAfter); Assert.True(second.RetryAfter.Value > System.TimeSpan.Zero); } public Task InitializeAsync() => Task.CompletedTask; public Task DisposeAsync() { runner.Dispose(); return Task.CompletedTask; } }