Files
VolcanicCore/tests/VolcanicCore.Tests/AdminBootstrapPlaintextReseedTests.cs

82 lines
3.2 KiB
C#

using Microsoft.Data.Sqlite;
using VolcanicCore.Api.Auth;
using VolcanicCore.Persistence;
using VolcanicCore.Persistence.Encryption;
using VolcanicCore.Persistence.Migrations;
using VolcanicCore.Persistence.Repositories;
using Xunit;
namespace VolcanicCore.Tests;
public class AdminBootstrapPlaintextReseedTests : IDisposable
{
private readonly string _dataDir = Path.Combine(Path.GetTempPath(), $"vc-plaintext-{Guid.NewGuid():N}");
public void Dispose()
{
try { Directory.Delete(_dataDir, recursive: true); } catch { }
}
[Fact]
public void Seed_heals_legacy_plaintext_password_source_row()
{
Directory.CreateDirectory(_dataDir);
var dbPath = Path.Combine(_dataDir, "volcaniccore.db");
var factory = new SqliteConnectionFactory(dbPath);
// Migrate the schema so admin_config exists.
using (var conn = factory.Open())
{
var mig = new Migrator(new IMigration[]
{
new Migration001Initial(),
new Migration002Connections(),
new Migration003Workflows(),
new Migration004Executions(),
new Migration005NodeExecutions(),
});
mig.Apply(conn);
}
// Simulate a pre-Plan-2 DB: write the password_hash_source as PLAINTEXT,
// bypassing AdminConfigRepository so the row is not AES-GCM encrypted.
using (var conn = factory.Open())
{
using var cmd = conn.CreateCommand();
cmd.CommandText = @"
INSERT INTO admin_config (key, value) VALUES ($k, $v)
ON CONFLICT(key) DO UPDATE SET value = excluded.value;";
cmd.Parameters.AddWithValue("$k", AdminBootstrap.PasswordSourceKey);
cmd.Parameters.AddWithValue("$v", "legacy-plaintext-password");
cmd.ExecuteNonQuery();
// Also seed the (bcrypt) hash so the "existingHash is not null" branch runs.
using var cmd2 = conn.CreateCommand();
cmd2.CommandText = @"
INSERT INTO admin_config (key, value) VALUES ($k, $v)
ON CONFLICT(key) DO UPDATE SET value = excluded.value;";
cmd2.Parameters.AddWithValue("$k", AdminBootstrap.PasswordHashKey);
cmd2.Parameters.AddWithValue("$v", "$2a$11$fakebcryptvaluetopasslength.check.aaaaaaaaaaaaaaaaaaaaaa");
cmd2.ExecuteNonQuery();
}
var encKey = new byte[32];
var enc = new EncryptionService(encKey);
var repo = new AdminConfigRepository(factory, enc);
var pw = new PasswordService();
// ACT — with the fix, Seed treats the un-decryptable row as "not set"
// and re-seeds it (properly encrypted this time).
AdminBootstrap.Seed(repo, pw, "the-new-env-password");
// Reading it back through the repo should now decrypt cleanly.
var restored = repo.Get(AdminBootstrap.PasswordSourceKey);
Assert.Equal("the-new-env-password", restored);
// And the hash was rewritten so the new password verifies.
var hash = repo.Get(AdminBootstrap.PasswordHashKey);
Assert.NotNull(hash);
Assert.True(pw.Verify("the-new-env-password", hash!));
}
}