PersonalAuthMod/DatabaseManager.cs

220 lines
7.4 KiB
C#

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
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 string? LoginUser(string username, string password)
{
try
{
using var conn = new NpgsqlConnection(_connectionString);
conn.Open();
int userId;
string storedHash, storedSalt;
using (var cmd = new NpgsqlCommand("SELECT id, password_hash, salt FROM users WHERE username = @u", conn))
{
cmd.Parameters.AddWithValue("u", username);
using var reader = cmd.ExecuteReader();
if (!reader.Read()) return null; // User not found
userId = reader.GetInt32(0);
storedHash = reader.GetString(1);
storedSalt = reader.GetString(2);
}
var hash = HashPassword(password, storedSalt);
if (hash != storedHash) return null; // Wrong password
// Generate Session (Must be 24-character hex for MongoId compatibility)
var sessionBytes = new byte[12];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(sessionBytes);
}
var sessionId = Convert.ToHexString(sessionBytes).ToLower();
// Invalidate old sessions for this user? Requirement: "block existing login sessions".
using (var delCmd = new NpgsqlCommand("DELETE FROM sessions WHERE user_id = @uid", conn))
{
delCmd.Parameters.AddWithValue("uid", userId);
delCmd.ExecuteNonQuery();
}
using (var insertCmd = new NpgsqlCommand("INSERT INTO sessions (session_id, user_id) VALUES (@sid, @uid)", conn))
{
insertCmd.Parameters.AddWithValue("sid", sessionId);
insertCmd.Parameters.AddWithValue("uid", userId);
insertCmd.ExecuteNonQuery();
}
return sessionId;
}
catch (Exception ex)
{
Console.WriteLine($"[PersonalAuthMod] LoginUser Failed: {ex.Message}");
return null;
}
}
public bool ValidateSession(string sessionId)
{
if (string.IsNullOrEmpty(sessionId)) return false;
try
{
using var conn = new NpgsqlConnection(_connectionString);
conn.Open();
using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM sessions WHERE session_id = @sid", conn))
{
cmd.Parameters.AddWithValue("sid", sessionId);
var count = (long)cmd.ExecuteScalar();
return count > 0;
}
}
catch (Exception ex)
{
Console.WriteLine($"[PersonalAuthMod] ValidateSession Failed: {ex.Message}");
return false;
}
}
public string? GetUsernameBySession(string sessionId)
{
try
{
using var conn = new NpgsqlConnection(_connectionString);
conn.Open();
using (var cmd = new NpgsqlCommand(@"
SELECT u.username
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE s.session_id = @sid", conn))
{
cmd.Parameters.AddWithValue("sid", sessionId);
return cmd.ExecuteScalar() as string;
}
}
catch (Exception ex)
{
Console.WriteLine($"[PersonalAuthMod] GetUsernameBySession Failed: {ex.Message}");
return null;
}
}
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);
}
}