82 lines
3.2 KiB
C#
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!));
|
|
}
|
|
}
|