ModernUO/Projects/UOContent.Tests/Tests/Accounting/AccountPasswordTests.cs
Kamron Batman c25a644a33 refactor(accounts): drop the migrated-password repair path
Every ModernUO shard has always run Argon2, so the RunUO/ServUO migration
shapes the repair path existed to recover do not occur in practice. It was
never free: it retried a failed verify against the other phrase rule, and
nothing in a stored hash separates a mis-migrated credential from a password
that merely begins with the username -- which is why it needed a per-account
tag on top of the config switch to be safe at all.

Removing it takes accountSecurity.repairMigratedPasswords, the RepairPasswordTag
opt-in and the forced rehash with it. The rehash on a successful login is now
implicit: stale algorithm or stale parameters, nothing else.

Also trims the development narrative out of the comments left behind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 23:51:43 -07:00

74 lines
3 KiB
C#

using System;
using Server.Accounting;
using Server.Accounting.Security;
using Xunit;
namespace Server.Tests.Accounting;
[Collection("Sequential UOContent Tests")]
public class AccountPasswordTests : IDisposable
{
private const string Password = "hunter2";
// CurrentAlgorithm is process-wide state shared with the rest of the collection.
private readonly PasswordProtectionAlgorithm _originalAlgorithm = AccountSecurity.CurrentAlgorithm;
public void Dispose() => AccountSecurity.CurrentAlgorithm = _originalAlgorithm;
[Theory]
[InlineData(PasswordProtectionAlgorithm.SHA1)]
[InlineData(PasswordProtectionAlgorithm.SHA2)]
[InlineData(PasswordProtectionAlgorithm.PBKDF2)]
[InlineData(PasswordProtectionAlgorithm.Argon2)]
public void NewAccount_CanLogIn(PasswordProtectionAlgorithm algorithm)
{
AccountSecurity.CurrentAlgorithm = algorithm;
var account = new Account($"new-{algorithm}-user", Password);
Assert.Equal(algorithm, account.PasswordAlgorithm);
Assert.True(account.CheckPassword(Password));
Assert.False(account.CheckPassword("wrong-password"));
}
// SetPassword assigns PasswordAlgorithm before deriving the phrase from it. Reversing those two
// lines salts the hash by the outgoing algorithm's rule and stores it under the incoming one,
// which verifies once and then never again.
[Theory]
[InlineData(PasswordProtectionAlgorithm.SHA1)]
[InlineData(PasswordProtectionAlgorithm.SHA2)]
[InlineData(PasswordProtectionAlgorithm.PBKDF2)]
public void UpgradingAlgorithm_DoesNotLockTheAccountOut(PasswordProtectionAlgorithm from)
{
AccountSecurity.CurrentAlgorithm = from;
var account = new Account($"upgrade-{from}-user", Password);
Assert.True(account.CheckPassword(Password));
AccountSecurity.CurrentAlgorithm = PasswordProtectionAlgorithm.Argon2;
Assert.True(account.CheckPassword(Password));
Assert.Equal(PasswordProtectionAlgorithm.Argon2, account.PasswordAlgorithm);
// Must verify against what the rehash wrote.
Assert.True(account.CheckPassword(Password));
Assert.False(account.CheckPassword("wrong-password"));
}
[Fact]
public void StaleArgon2Parameters_AreRehashedOnLogin()
{
AccountSecurity.CurrentAlgorithm = PasswordProtectionAlgorithm.Argon2;
var account = new Account("stale-params-user", Password);
// The shipping default before this change: Argon2i, m=8192, t=3, p=1.
account.Password =
"$argon2i$v=19$m=8192,t=3,p=1$LD1XJz7P3wQmIJ+Tu6ScgA$NO5hBABsHQ172C5nDO2X4gWnB4jDef3x6WhLdVE2LFw";
Assert.True(account.CheckPassword(Password));
Assert.StartsWith("$argon2id$v=19$m=16384,t=1,p=1$", account.Password);
// Already current: verifying again must not rewrite the hash.
var afterFirst = account.Password;
Assert.True(account.CheckPassword(Password));
Assert.Equal(afterFirst, account.Password);
}
}