99 lines
4.1 KiB
C#
99 lines
4.1 KiB
C#
using Store.Users.Entities;
|
|
using Store.Users.Services;
|
|
using MAX.Framework.Commons;
|
|
|
|
namespace Store.Users.Tests;
|
|
|
|
public sealed class UserAuthenticationTests
|
|
{
|
|
[Theory]
|
|
[InlineData("", "password")]
|
|
[InlineData(" ", "password")]
|
|
[InlineData("main", "")]
|
|
[InlineData("missing", "legacy-password")]
|
|
[InlineData("main", "wrong")]
|
|
[InlineData("main' OR 1=1 --", "legacy-password")]
|
|
public async Task InvalidCredentials_ReturnNullWithoutChangingPassword(string name, string password)
|
|
{
|
|
using var context = new UserTestContext();
|
|
Assert.Null(await context.Authentication.Authenticate(name, password));
|
|
Assert.Equal("legacy-password", (await context.Read("main")).Password);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyPassword_RemainsUnchangedAfterRepeatedLogins()
|
|
{
|
|
using var context = new UserTestContext();
|
|
var info = await context.Authentication.Authenticate(" main ", "legacy-password");
|
|
Assert.Equal("main", info!.UserName);
|
|
var user = await context.Read("main");
|
|
Assert.Equal("legacy-password", user.Password);
|
|
Assert.True(UserPassword.Verify(user, "legacy-password"));
|
|
Assert.NotNull(await context.Authentication.Authenticate("main", "legacy-password"));
|
|
Assert.Equal("legacy-password", (await context.Read("main")).Password);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HashedPassword_RemainsUnchangedAfterLogin()
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = "main";
|
|
await context.Users.ChangePassword(new() { OldPassword = "legacy-password", NewPassword = "new-password" }, default);
|
|
var hash = (await context.Read("main")).Password;
|
|
|
|
Assert.NotNull(await context.Authentication.Authenticate("main", "new-password"));
|
|
Assert.Equal(hash, (await context.Read("main")).Password);
|
|
}
|
|
|
|
[Fact]
|
|
public void PasswordHashes_AreSalted_AndRejectWrongPassword()
|
|
{
|
|
var user = new UserEntity();
|
|
user.Password = UserPassword.Hash(user, "test-password");
|
|
Assert.NotEqual(user.Password, UserPassword.Hash(user, "test-password"));
|
|
Assert.True(UserPassword.Verify(user, "test-password"));
|
|
Assert.False(UserPassword.Verify(user, "wrong-password"));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("AQAAAAinvalid!")]
|
|
[InlineData("AAinvalid!")]
|
|
[InlineData("")]
|
|
public void MalformedPasswordHash_IsRejected(string hash) =>
|
|
Assert.False(UserPassword.Verify(new UserEntity { Password = hash }, "test-password"));
|
|
|
|
[Fact]
|
|
public async Task WrongOldPassword_DoesNotChangePasswordOrSignOut()
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = "main";
|
|
var error = await Assert.ThrowsAsync<AppException>(() => context.Users.ChangePassword(
|
|
new() { OldPassword = "wrong", NewPassword = "new-password" }, default));
|
|
Assert.Equal("原密码错误", error.Message);
|
|
Assert.Equal("legacy-password", (await context.Read("main")).Password);
|
|
Assert.Empty(context.Sessions.SignedOut);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ChangePassword_InvalidatesOldPasswordAndSession()
|
|
{
|
|
using var context = new UserTestContext();
|
|
context.CurrentUser.UserName = "main";
|
|
await context.Users.ChangePassword(new() { OldPassword = "legacy-password", NewPassword = "new-password" }, default);
|
|
Assert.Contains("main", context.Sessions.SignedOut);
|
|
Assert.Null(await context.Authentication.Authenticate("main", "legacy-password"));
|
|
Assert.NotNull(await context.Authentication.Authenticate("main", "new-password"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelledAuthentication_ThrowsBeforeChangingPassword()
|
|
{
|
|
using var context = new UserTestContext();
|
|
using var cancellation = new CancellationTokenSource();
|
|
cancellation.Cancel();
|
|
await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
|
|
context.Authentication.Authenticate("main", "legacy-password", cancellation.Token));
|
|
Assert.Equal("legacy-password", (await context.Read("main")).Password);
|
|
}
|
|
}
|