mirror of
https://github.com/MAKS-IT-COM/maksit-postclient.git
synced 2026-09-30 03:18:09 +02:00
127 lines
3.5 KiB
C#
127 lines
3.5 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using MaksIT.Results;
|
|
|
|
|
|
namespace MaksIT.PostClient.Shared;
|
|
|
|
|
|
public sealed class FileSecretStore : ISecretStore {
|
|
private readonly string _path;
|
|
private readonly Lock _gate = new();
|
|
|
|
public FileSecretStore(string? path = null) {
|
|
_path = string.IsNullOrWhiteSpace(path) ? AppPaths.SecretsFile() : path;
|
|
}
|
|
|
|
public Result Put(string key, string secret) {
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
return Result.BadRequest("Secret key is required.");
|
|
|
|
lock (_gate) {
|
|
var map = Load();
|
|
map[key] = Protect(secret);
|
|
Save(map);
|
|
}
|
|
|
|
return Result.Ok();
|
|
}
|
|
|
|
public Result<string?> Get(string key) {
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
return Result<string?>.BadRequest(null, "Secret key is required.");
|
|
|
|
lock (_gate) {
|
|
var map = Load();
|
|
if (!map.TryGetValue(key, out var packed))
|
|
return Result<string?>.Ok(null);
|
|
try {
|
|
return Result<string?>.Ok(Unprotect(packed));
|
|
}
|
|
catch (Exception ex) {
|
|
return Result<string?>.UnprocessableEntity(null, ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Result Delete(string key) {
|
|
lock (_gate) {
|
|
var map = Load();
|
|
map.Remove(key);
|
|
Save(map);
|
|
}
|
|
|
|
return Result.Ok();
|
|
}
|
|
|
|
public static string MailboxKey(string mailboxId) =>
|
|
"mailbox:" + mailboxId;
|
|
|
|
public static string OAuthKey(string mailboxId) =>
|
|
"oauth:" + mailboxId;
|
|
|
|
public static string OAuthClientSecretKey(string? authKind) =>
|
|
"oauth-client-secret:" + MailAuthKind.Normalize(authKind);
|
|
|
|
public static int MergeMissingKeys(string sourcePath, string destPath) {
|
|
var source = ReadMap(sourcePath);
|
|
if (source.Count == 0)
|
|
return 0;
|
|
var dest = ReadMap(destPath);
|
|
var added = 0;
|
|
foreach (var pair in source) {
|
|
if (dest.ContainsKey(pair.Key))
|
|
continue;
|
|
dest[pair.Key] = pair.Value;
|
|
added++;
|
|
}
|
|
|
|
if (added > 0)
|
|
WriteMap(destPath, dest);
|
|
return added;
|
|
}
|
|
|
|
private Dictionary<string, string> Load() =>
|
|
ReadMap(_path);
|
|
|
|
private void Save(Dictionary<string, string> map) =>
|
|
WriteMap(_path, map);
|
|
|
|
private static Dictionary<string, string> ReadMap(string path) {
|
|
if (!File.Exists(path))
|
|
return new Dictionary<string, string>(StringComparer.Ordinal);
|
|
try {
|
|
var json = File.ReadAllText(path);
|
|
return JsonSerializer.Deserialize<Dictionary<string, string>>(json)
|
|
?? new Dictionary<string, string>(StringComparer.Ordinal);
|
|
}
|
|
catch {
|
|
return new Dictionary<string, string>(StringComparer.Ordinal);
|
|
}
|
|
}
|
|
|
|
private static void WriteMap(string path, Dictionary<string, string> map) {
|
|
var dir = Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrEmpty(dir))
|
|
Directory.CreateDirectory(dir);
|
|
File.WriteAllText(path, JsonSerializer.Serialize(map));
|
|
if (!OperatingSystem.IsWindows())
|
|
File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite);
|
|
}
|
|
|
|
private static string Protect(string secret) {
|
|
var bytes = Encoding.UTF8.GetBytes(secret);
|
|
if (OperatingSystem.IsWindows())
|
|
bytes = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);
|
|
return Convert.ToBase64String(bytes);
|
|
}
|
|
|
|
private static string Unprotect(string packed) {
|
|
var bytes = Convert.FromBase64String(packed);
|
|
if (OperatingSystem.IsWindows())
|
|
bytes = ProtectedData.Unprotect(bytes, null, DataProtectionScope.CurrentUser);
|
|
return Encoding.UTF8.GetString(bytes);
|
|
}
|
|
}
|