Automatisches rollierendes Backup der Datenbank beim Start mit Wiederherstellung über die Einstellungen, versionierte Schema-Migration, optionale Passwort-Verschlüsselung der LiteDB-Datei und eine App-Sperre nach Inaktivität mit eigenem Passwort. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
74 lines
2.4 KiB
C#
74 lines
2.4 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
|
|
namespace LehrerApp.Core.Services;
|
|
|
|
internal class AppLockConfig
|
|
{
|
|
public bool Enabled { get; set; }
|
|
public string? Salt { get; set; }
|
|
public string? PasswordHash { get; set; }
|
|
public int TimeoutMinutes { get; set; } = 10;
|
|
}
|
|
|
|
/// <summary>
|
|
/// App-Sperre nach Inaktivität: eigenes Passwort (unabhängig von einer eventuellen
|
|
/// Datenbank-Verschlüsselung), als gesalzener Hash in <c><AppData>/LehrerApp/applock.json</c>
|
|
/// abgelegt. Das Klartext-Passwort wird nie gespeichert.
|
|
/// </summary>
|
|
public class AppLockService
|
|
{
|
|
private const int Pbkdf2Iterations = 100_000;
|
|
private readonly string _configPath;
|
|
private AppLockConfig _config;
|
|
|
|
public bool IsEnabled => _config.Enabled;
|
|
public int TimeoutMinutes => _config.TimeoutMinutes;
|
|
public bool HasPassword => !string.IsNullOrEmpty(_config.PasswordHash);
|
|
|
|
public AppLockService(string appDataPath)
|
|
{
|
|
_configPath = Path.Combine(appDataPath, "applock.json");
|
|
_config = Load();
|
|
}
|
|
|
|
public void Configure(bool enabled, int timeoutMinutes)
|
|
{
|
|
_config.Enabled = enabled;
|
|
_config.TimeoutMinutes = Math.Max(1, timeoutMinutes);
|
|
Save();
|
|
}
|
|
|
|
public void SetPassword(string password)
|
|
{
|
|
var salt = RandomNumberGenerator.GetBytes(16);
|
|
_config.Salt = Convert.ToBase64String(salt);
|
|
_config.PasswordHash = Hash(password, salt);
|
|
Save();
|
|
}
|
|
|
|
public bool VerifyPassword(string password)
|
|
{
|
|
if (string.IsNullOrEmpty(_config.PasswordHash) || string.IsNullOrEmpty(_config.Salt)) return false;
|
|
var salt = Convert.FromBase64String(_config.Salt);
|
|
return _config.PasswordHash == Hash(password, salt);
|
|
}
|
|
|
|
private static string Hash(string password, byte[] salt) =>
|
|
Convert.ToBase64String(Rfc2898DeriveBytes.Pbkdf2(password, salt, Pbkdf2Iterations, HashAlgorithmName.SHA256, 32));
|
|
|
|
private AppLockConfig Load()
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(_configPath))
|
|
return JsonSerializer.Deserialize<AppLockConfig>(File.ReadAllText(_configPath)) ?? new AppLockConfig();
|
|
}
|
|
catch { /* beschädigte Konfiguration -> Standardwerte, Sperre bleibt deaktiviert */ }
|
|
return new AppLockConfig();
|
|
}
|
|
|
|
private void Save() =>
|
|
File.WriteAllText(_configPath, JsonSerializer.Serialize(_config));
|
|
}
|