diff --git a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs
index 6aee7b4..0c49e77 100644
--- a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs
+++ b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs
@@ -12,6 +12,13 @@ public class CertificateCache {
}
public class RegistrationCache {
+
+ ///
+ /// Field used to identify cache by account id
+ ///
+ public Guid AccountId { get; set; }
+
+
public Dictionary? CachedCerts { get; set; }
public byte[]? AccountKey { get; set; }
public string? Id { get; set; }
diff --git a/src/LetsEncrypt/Services/LetsEncryptService.cs b/src/LetsEncrypt/Services/LetsEncryptService.cs
index a09f6ed..4aed135 100644
--- a/src/LetsEncrypt/Services/LetsEncryptService.cs
+++ b/src/LetsEncrypt/Services/LetsEncryptService.cs
@@ -17,8 +17,8 @@ namespace MaksIT.LetsEncrypt.Services;
public interface ILetsEncryptService {
Task ConfigureClient(Guid sessionId, string url);
- Task Init(Guid sessionId, string[] contacts, RegistrationCache? registrationCache);
- RegistrationCache? GetRegistrationCache(Guid sessionId);
+ Task Init(Guid sessionId,Guid accountId, string[] contacts, RegistrationCache? registrationCache);
+ (RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId);
(string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId);
Task<(Dictionary?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType);
Task CompleteChallenges(Guid sessionId);
@@ -30,7 +30,7 @@ public interface ILetsEncryptService {
public class LetsEncryptService : ILetsEncryptService {
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
- private readonly IMemoryCache _cache;
+ private readonly IMemoryCache _memoryCache;
public LetsEncryptService(
ILogger logger,
@@ -38,13 +38,13 @@ public class LetsEncryptService : ILetsEncryptService {
IMemoryCache cache) {
_logger = logger;
_httpClient = httpClient;
- _cache = cache;
+ _memoryCache = cache;
}
private State GetOrCreateState(Guid sessionId) {
- if (!_cache.TryGetValue(sessionId, out State state)) {
+ if (!_memoryCache.TryGetValue(sessionId, out State state)) {
state = new State();
- _cache.Set(sessionId, state, TimeSpan.FromHours(1));
+ _memoryCache.Set(sessionId, state, TimeSpan.FromHours(1));
}
return state;
}
@@ -74,7 +74,7 @@ public class LetsEncryptService : ILetsEncryptService {
#endregion
#region Init
- public async Task Init(Guid sessionId, string[] contacts, RegistrationCache? cache) {
+ public async Task Init(Guid sessionId, Guid accountId, string[] contacts, RegistrationCache? cache) {
if (sessionId == Guid.Empty) {
_logger.LogError("Invalid sessionId");
return IDomainResult.Failed();
@@ -119,6 +119,8 @@ public class LetsEncryptService : ILetsEncryptService {
}
state.Cache = new RegistrationCache {
+ AccountId = accountId,
+
Location = account.Result.Location,
AccountKey = accountKey.ExportCspBlob(true),
Id = account.Result.Id,
@@ -135,12 +137,15 @@ public class LetsEncryptService : ILetsEncryptService {
}
}
-
#endregion
- public RegistrationCache? GetRegistrationCache(Guid sessionId) {
+ public (RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId) {
var state = GetOrCreateState(sessionId);
- return state.Cache;
+
+ if(state?.Cache == null)
+ return IDomainResult.Failed();
+
+ return IDomainResult.Success(state.Cache);
}
#region GetTermsOfService
diff --git a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs
new file mode 100644
index 0000000..5a14e10
--- /dev/null
+++ b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs
@@ -0,0 +1,18 @@
+namespace LetsEncryptServer.BackgroundServices {
+ public class AutoRenewal : BackgroundService {
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
+ while (!stoppingToken.IsCancellationRequested) {
+ // Your background task logic here
+ Console.WriteLine("Background service is running.");
+
+ // Simulate some work by delaying for 5 seconds
+ await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
+ }
+ }
+
+ public override Task StopAsync(CancellationToken stoppingToken) {
+ Console.WriteLine("Background service is stopping.");
+ return base.StopAsync(stoppingToken);
+ }
+ }
+}
diff --git a/src/LetsEncryptServer/Program.cs b/src/LetsEncryptServer/Program.cs
index 3c87b9a..756ac93 100644
--- a/src/LetsEncryptServer/Program.cs
+++ b/src/LetsEncryptServer/Program.cs
@@ -27,6 +27,7 @@ builder.Services.AddMemoryCache();
builder.Services.AddHttpClient();
builder.Services.AddScoped();
+builder.Services.AddSingleton();
var app = builder.Build();
diff --git a/src/LetsEncryptServer/Services/CacheService.cs b/src/LetsEncryptServer/Services/CacheService.cs
new file mode 100644
index 0000000..e9f3de8
--- /dev/null
+++ b/src/LetsEncryptServer/Services/CacheService.cs
@@ -0,0 +1,123 @@
+using System.Text.Json;
+
+using DomainResults.Common;
+
+using MaksIT.LetsEncrypt.Entities;
+
+namespace MaksIT.LetsEncryptServer.Services;
+
+public interface ICacheService {
+ Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId);
+ Task SaveToCacheAsync(Guid accountId, RegistrationCache cache);
+ Task DeleteFromCacheAsync(Guid accountId);
+}
+
+public class CacheService : ICacheService, IDisposable {
+
+ private readonly ILogger _logger;
+ private readonly string _cacheDirectory;
+ private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1);
+
+ public CacheService(
+ ILogger logger
+ ) {
+ _logger = logger;
+ _cacheDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache");
+
+ if (!Directory.Exists(_cacheDirectory)) {
+ Directory.CreateDirectory(_cacheDirectory);
+ }
+ }
+
+ private string GetCacheFilePath(Guid accountId) {
+ return Path.Combine(_cacheDirectory, $"{accountId}.json");
+ }
+
+ public async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId) {
+ var cacheFilePath = GetCacheFilePath(accountId);
+
+ await _cacheLock.WaitAsync();
+
+ try {
+ if (!File.Exists(cacheFilePath)) {
+ var message = $"Cache file not found for account {accountId}";
+ _logger.LogWarning(message);
+
+ return IDomainResult.Failed(message);
+ }
+
+ var json = await File.ReadAllTextAsync(cacheFilePath);
+ if (string.IsNullOrEmpty(json)) {
+ var message = $"Cache file is empty for account {accountId}";
+ _logger.LogWarning(message);
+
+ return IDomainResult.Failed(message);
+ }
+
+ var cache = JsonSerializer.Deserialize(json);
+ return IDomainResult.Success(cache);
+ }
+ catch (Exception ex) {
+ var message = "Error reading cache file for account {accountId}";
+ _logger.LogError(ex, message);
+
+ return IDomainResult.Failed(message);
+ }
+ finally {
+ _cacheLock.Release();
+ }
+ }
+
+ public async Task SaveToCacheAsync(Guid accountId, RegistrationCache cache) {
+ var cacheFilePath = GetCacheFilePath(accountId);
+ await _cacheLock.WaitAsync();
+
+ try {
+ var json = JsonSerializer.Serialize(cache);
+ await File.WriteAllTextAsync(cacheFilePath, json);
+
+ _logger.LogInformation($"Cache file saved for account {accountId}");
+
+ return DomainResult.Success();
+ }
+ catch (Exception ex) {
+ var message = "Error writing cache file for account {accountId}";
+ _logger.LogError(ex, message);
+
+ return IDomainResult.Failed(message);
+ }
+ finally {
+ _cacheLock.Release();
+ }
+ }
+
+ public async Task DeleteFromCacheAsync(Guid accountId) {
+ var cacheFilePath = GetCacheFilePath(accountId);
+ await _cacheLock.WaitAsync();
+
+ try {
+ if (File.Exists(cacheFilePath)) {
+ File.Delete(cacheFilePath);
+ _logger.LogInformation($"Cache file deleted for account {accountId}");
+ }
+ else {
+ _logger.LogWarning($"Cache file not found for account {accountId}");
+ }
+
+ return IDomainResult.Success();
+ }
+ catch (Exception ex) {
+ var message = $"Error deleting cache file for account {accountId}";
+ _logger.LogError(ex, message);
+
+ return IDomainResult.Failed(message);
+ }
+ finally {
+ _cacheLock.Release();
+ }
+ }
+
+ public void Dispose() {
+ _cacheLock?.Dispose();
+ }
+}
diff --git a/src/LetsEncryptServer/Services/CertsFlowService.cs b/src/LetsEncryptServer/Services/CertsFlowService.cs
index bb2646e..313d9ce 100644
--- a/src/LetsEncryptServer/Services/CertsFlowService.cs
+++ b/src/LetsEncryptServer/Services/CertsFlowService.cs
@@ -32,17 +32,22 @@ public class CertsFlowService : ICertsFlowService {
private readonly Configuration _appSettings;
private readonly ILogger _logger;
private readonly ILetsEncryptService _letsEncryptService;
- private readonly string _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
+ private readonly ICacheService _cacheService;
+
+ private readonly string _acmePath;
public CertsFlowService(
IOptions appSettings,
ILogger logger,
- ILetsEncryptService letsEncryptService
+ ILetsEncryptService letsEncryptService,
+ ICacheService cashService
) {
_appSettings = appSettings.Value;
_logger = logger;
_letsEncryptService = letsEncryptService;
+ _cacheService = cashService;
+ _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
if (!Directory.Exists(_acmePath))
Directory.CreateDirectory(_acmePath);
}
@@ -70,12 +75,22 @@ public class CertsFlowService : ICertsFlowService {
}
public async Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) {
- var cache = default(RegistrationCache);
+ RegistrationCache? cache = null;
+
if (accountId == null) {
accountId = Guid.NewGuid();
}
+ else {
+ var (loadedCache, loadCaceResutl) = await _cacheService.LoadFromCacheAsync(accountId.Value);
+ if (!loadCaceResutl.IsSuccess || loadCaceResutl == null) {
+ accountId = Guid.NewGuid();
+ }
+ else {
+ cache = loadedCache;
+ }
+ }
- var result = await _letsEncryptService.Init(sessionId, requestData.Contacts, cache);
+ var result = await _letsEncryptService.Init(sessionId, accountId.Value, requestData.Contacts, cache);
return result.IsSuccess ? IDomainResult.Success(accountId.Value) : (null, result);
}
@@ -111,6 +126,15 @@ public class CertsFlowService : ICertsFlowService {
Thread.Sleep(1000);
}
+ // Persist the cache
+ var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId);
+ if (!getCacheResult.IsSuccess || cache == null)
+ return getCacheResult;
+
+ var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache);
+ if(!saveResult.IsSuccess)
+ return saveResult;
+
return IDomainResult.Success();
}
@@ -178,98 +202,9 @@ public class CertsFlowService : ICertsFlowService {
return IDomainResult.Success();
}
-
- /**
- abort ssl cert : abort a transaction for a certificate file
- add acl [@] : add an acl entry
- add map [@]