Files
OwnCord/Client/OwnCord.Client/Services/CredentialService.cs
T
jevb 25449eb204 feat: redesign login UI, add save-password, fix permissions, add audit logging, member_join broadcast
- Redesign ConnectPage with modern dark theme, profile cards with delete buttons, login/register toggle
- Add DPAPI-encrypted password saving with "Remember my password" checkbox
- Fix permission bit constants to match SCHEMA.md (Member role 0x663)
- Add migration 004 to fix existing Member role permissions
- Add comprehensive audit logging across all server packages (auth, admin, ws, setup)
- Add member_join WebSocket broadcast so new users appear in members list in real-time
- Add host URL normalization (strip scheme prefix) for reverse proxy compatibility
- Add REST API client, ChatService orchestrator, WebSocket service with reconnection
- Add model types (WsEnvelope payloads, API responses), converters, tests
2026-03-15 00:31:39 +01:00

84 lines
2.9 KiB
C#

using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace OwnCord.Client.Services;
/// <summary>
/// Stores auth tokens encrypted with DPAPI (CurrentUser scope) in AppData.
/// Equivalent security to Windows Credential Manager without requiring WinRT.
/// </summary>
public sealed class CredentialService : ICredentialService
{
private readonly string _dir;
public CredentialService()
: this(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OwnCord", "creds")) { }
internal CredentialService(string dir) => _dir = dir;
public void SaveToken(string host, string username, string token)
{
Directory.CreateDirectory(_dir);
var plain = Encoding.UTF8.GetBytes(token);
var encrypted = ProtectedData.Protect(plain, GetEntropy(host, username), DataProtectionScope.CurrentUser);
File.WriteAllBytes(CredPath(host, username, "tok"), encrypted);
}
public string? LoadToken(string host, string username)
{
var path = CredPath(host, username, "tok");
if (!File.Exists(path)) return null;
try
{
var encrypted = File.ReadAllBytes(path);
var plain = ProtectedData.Unprotect(encrypted, GetEntropy(host, username), DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(plain);
}
catch { return null; }
}
public void DeleteToken(string host, string username)
{
var path = CredPath(host, username, "tok");
if (File.Exists(path)) File.Delete(path);
}
public void SavePassword(string host, string username, string password)
{
Directory.CreateDirectory(_dir);
var plain = Encoding.UTF8.GetBytes(password);
var encrypted = ProtectedData.Protect(plain, GetEntropy(host, username), DataProtectionScope.CurrentUser);
File.WriteAllBytes(CredPath(host, username, "pwd"), encrypted);
}
public string? LoadPassword(string host, string username)
{
var path = CredPath(host, username, "pwd");
if (!File.Exists(path)) return null;
try
{
var encrypted = File.ReadAllBytes(path);
var plain = ProtectedData.Unprotect(encrypted, GetEntropy(host, username), DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(plain);
}
catch { return null; }
}
public void DeletePassword(string host, string username)
{
var path = CredPath(host, username, "pwd");
if (File.Exists(path)) File.Delete(path);
}
private string CredPath(string host, string username, string prefix = "tok")
{
var key = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes($"{host}:{username}")));
return Path.Combine(_dir, $"{prefix}_{key}.dat");
}
private static byte[] GetEntropy(string host, string username)
=> Encoding.UTF8.GetBytes($"owncord:{host}:{username}");
}