using System.Security.Cryptography; using System.Text; using Npgsql; using SPTarkov.DI; using SPTarkov.DI.Annotations; namespace PersonalAuthMod; [Injectable] public class DatabaseManager { private readonly AuthConfig _config; private readonly string _connectionString; public DatabaseManager() { _config = AuthConfig.Load(); _connectionString = $"Host={_config.DbUrl};Port={_config.DbPort};Username={_config.DbUser};Password={_config.DbPassword};Database={_config.DbName}"; Console.WriteLine($"[PersonalAuthMod] DB Manager connecting to: Host={_config.DbUrl}, Port={_config.DbPort}, User={_config.DbUser}, DB={_config.DbName}"); } public void Initialize() { try { using var conn = new NpgsqlConnection(_connectionString); conn.Open(); // Create Users Table using (var cmd = new NpgsqlCommand(@" CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password_hash TEXT NOT NULL, salt TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );", conn)) { cmd.ExecuteNonQuery(); } // Create Sessions Table using (var cmd = new NpgsqlCommand(@" CREATE TABLE IF NOT EXISTS sessions ( session_id VARCHAR(100) PRIMARY KEY, user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );", conn)) { cmd.ExecuteNonQuery(); } } catch (Exception ex) { // Log error (using Console for now as we don't have logger injected here yet, or we can use DI) Console.WriteLine($"[PersonalAuthMod] Database Initialization Failed: {ex.Message}"); } } public bool RegisterUser(string username, string password) { try { using var conn = new NpgsqlConnection(_connectionString); conn.Open(); // Check if user exists using (var checkCmd = new NpgsqlCommand("SELECT COUNT(*) FROM users WHERE username = @u", conn)) { checkCmd.Parameters.AddWithValue("u", username); var count = (long)checkCmd.ExecuteScalar(); if (count > 0) return false; } // Hash Password var salt = GenerateSalt(); var hash = HashPassword(password, salt); // Insert User and get ID using (var cmd = new NpgsqlCommand("INSERT INTO users (username, password_hash, salt) VALUES (@u, @p, @s)", conn)) { cmd.Parameters.AddWithValue("u", username); cmd.Parameters.AddWithValue("p", hash); cmd.Parameters.AddWithValue("s", salt); cmd.ExecuteNonQuery(); } return true; } catch (Exception ex) { Console.WriteLine($"[PersonalAuthMod] RegisterUser Failed: {ex.Message}"); return false; } } public bool ValidateCredentials(string username, string password) { try { using var conn = new NpgsqlConnection(_connectionString); conn.Open(); string storedHash, storedSalt; using (var cmd = new NpgsqlCommand("SELECT password_hash, salt FROM users WHERE username = @u", conn)) { cmd.Parameters.AddWithValue("u", username); using var reader = cmd.ExecuteReader(); if (!reader.Read()) { Console.WriteLine($"[PersonalAuthMod] ValidateCredentials Failed: User '{username}' not found in DB."); return false; } storedHash = reader.GetString(0); storedSalt = reader.GetString(1); } var hash = HashPassword(password, storedSalt); if (hash != storedHash) { Console.WriteLine($"[PersonalAuthMod] ValidateCredentials Failed: Password mismatch for user '{username}'."); return false; } return true; } catch (Exception ex) { Console.WriteLine($"[PersonalAuthMod] ValidateCredentials Failed: {ex.Message}"); return false; } } private string GenerateSalt() { var bytes = new byte[16]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(bytes); } return Convert.ToBase64String(bytes); } private string HashPassword(string password, string salt) { // Simple SHA256 with salt. Pepper is mentioned in requirements but simplified here to salt. // If pepper is strictly required, we can add a hardcoded string constant. var pepper = "spt-server-pepper"; using var sha256 = SHA256.Create(); var combined = password + salt + pepper; var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined)); return Convert.ToBase64String(bytes); } }