(feature): migrate to MaksIT.Results

This commit is contained in:
Maksym Sadovnychyy 2025-10-14 18:43:38 +02:00
parent 30f5ededa3
commit 7c962a4924
11 changed files with 308 additions and 363 deletions

View File

@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.3.1" /> <PackageReference Include="MaksIT.Results" Version="1.0.6" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />

View File

@ -4,37 +4,35 @@
*/ */
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Net.Http.Headers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using DomainResults.Common;
using MaksIT.LetsEncrypt.Entities;
using MaksIT.LetsEncrypt.Exceptions;
using MaksIT.Core.Extensions; using MaksIT.Core.Extensions;
using MaksIT.LetsEncrypt.Models.Responses; using MaksIT.LetsEncrypt.Entities;
using MaksIT.LetsEncrypt.Models.Interfaces;
using MaksIT.LetsEncrypt.Models.Requests;
using MaksIT.LetsEncrypt.Entities.Jws; using MaksIT.LetsEncrypt.Entities.Jws;
using MaksIT.LetsEncrypt.Entities.LetsEncrypt; using MaksIT.LetsEncrypt.Entities.LetsEncrypt;
using MaksIT.LetsEncrypt.Exceptions;
using MaksIT.LetsEncrypt.Models.Interfaces;
using MaksIT.LetsEncrypt.Models.Requests;
using MaksIT.LetsEncrypt.Models.Responses;
using MaksIT.Results;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace MaksIT.LetsEncrypt.Services; namespace MaksIT.LetsEncrypt.Services;
public interface ILetsEncryptService { public interface ILetsEncryptService {
Task<IDomainResult> ConfigureClient(Guid sessionId, bool isStaging); Task<Result> ConfigureClient(Guid sessionId, bool isStaging);
Task<IDomainResult> Init(Guid sessionId,Guid accountId, string description, string[] contacts, RegistrationCache? registrationCache); Task<Result> Init(Guid sessionId,Guid accountId, string description, string[] contacts, RegistrationCache? registrationCache);
(RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId); Result<RegistrationCache?> GetRegistrationCache(Guid sessionId);
(string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId); Result<string?> GetTermsOfServiceUri(Guid sessionId);
Task<(Dictionary<string, string>?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType); Task<Result<Dictionary<string, string>?>> NewOrder(Guid sessionId, string[] hostnames, string challengeType);
Task<IDomainResult> CompleteChallenges(Guid sessionId); Task<Result> CompleteChallenges(Guid sessionId);
Task<IDomainResult> GetOrder(Guid sessionId, string[] hostnames); Task<Result> GetOrder(Guid sessionId, string[] hostnames);
Task<IDomainResult> GetCertificate(Guid sessionId, string subject); Task<Result> GetCertificate(Guid sessionId, string subject);
Task<IDomainResult> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason); Task<Result> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason);
} }
public class LetsEncryptService : ILetsEncryptService { public class LetsEncryptService : ILetsEncryptService {
@ -77,8 +75,8 @@ public class LetsEncryptService : ILetsEncryptService {
} }
// Helper: Poll challenge status until valid or timeout // Helper: Poll challenge status until valid or timeout
private async Task<IDomainResult> PollChallengeStatus(Guid sessionId, AuthorizationChallengeChallenge challenge, State state) { private async Task<Result> PollChallengeStatus(Guid sessionId, AuthorizationChallengeChallenge challenge, State state) {
if (challenge?.Url == null) return IDomainResult.Failed("Challenge URL is null"); if (challenge?.Url == null) return Result.InternalServerError("Challenge URL is null");
var start = DateTime.UtcNow; var start = DateTime.UtcNow;
while (true) { while (true) {
var pollRequest = new HttpRequestMessage(HttpMethod.Post, challenge.Url); var pollRequest = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
@ -94,15 +92,15 @@ public class LetsEncryptService : ILetsEncryptService {
HandleProblemResponseAsync(pollResponse, pollResponseText); HandleProblemResponseAsync(pollResponse, pollResponseText);
var authChallenge = ProcessResponseContent<AuthorizationChallengeResponse>(pollResponse, pollResponseText); var authChallenge = ProcessResponseContent<AuthorizationChallengeResponse>(pollResponse, pollResponseText);
if (authChallenge.Result?.Status != "pending") if (authChallenge.Result?.Status != "pending")
return authChallenge.Result?.Status == "valid" ? IDomainResult.Success() : IDomainResult.Failed(); return authChallenge.Result?.Status == "valid" ? Result.Ok() : Result.InternalServerError();
if ((DateTime.UtcNow - start).Seconds > 120) if ((DateTime.UtcNow - start).Seconds > 120)
return IDomainResult.Failed("Timeout"); return Result.InternalServerError("Timeout");
await Task.Delay(1000); await Task.Delay(1000);
} }
} }
#region ConfigureClient #region ConfigureClient
public async Task<IDomainResult> ConfigureClient(Guid sessionId, bool isStaging) { public async Task<Result> ConfigureClient(Guid sessionId, bool isStaging) {
try { try {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
state.IsStaging = isStaging; state.IsStaging = isStaging;
@ -113,29 +111,29 @@ public class LetsEncryptService : ILetsEncryptService {
var directory = await SendAcmeRequest<AcmeDirectory>(request, state, HttpMethod.Get); var directory = await SendAcmeRequest<AcmeDirectory>(request, state, HttpMethod.Get);
state.Directory = directory.Result ?? throw new InvalidOperationException("Directory response is null"); state.Directory = directory.Result ?? throw new InvalidOperationException("Directory response is null");
} }
return IDomainResult.Success(); return Result.Ok();
} catch (Exception ex) { } catch (Exception ex) {
const string message = "Let's Encrypt client unhandled exception"; const string message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return IDomainResult.CriticalDependencyError(message); return Result.InternalServerError(message);
} }
} }
#endregion #endregion
#region Init #region Init
public async Task<IDomainResult> Init(Guid sessionId, Guid accountId, string description, string[] contacts, RegistrationCache? cache) { public async Task<Result> Init(Guid sessionId, Guid accountId, string description, string[] contacts, RegistrationCache? cache) {
if (sessionId == Guid.Empty) { if (sessionId == Guid.Empty) {
_logger.LogError("Invalid sessionId"); _logger.LogError("Invalid sessionId");
return IDomainResult.Failed(); return Result.InternalServerError();
} }
if (contacts == null || contacts.Length == 0) { if (contacts == null || contacts.Length == 0) {
_logger.LogError("Contacts are null or empty"); _logger.LogError("Contacts are null or empty");
return IDomainResult.Failed(); return Result.InternalServerError();
} }
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
if (state.Directory == null) { if (state.Directory == null) {
_logger.LogError("State directory is null"); _logger.LogError("State directory is null");
return IDomainResult.Failed(); return Result.InternalServerError();
} }
_logger.LogInformation($"Executing {nameof(Init)}..."); _logger.LogInformation($"Executing {nameof(Init)}...");
try { try {
@ -162,7 +160,7 @@ public class LetsEncryptService : ILetsEncryptService {
state.JwsService.SetKeyId(result.Result?.Location?.ToString() ?? string.Empty); state.JwsService.SetKeyId(result.Result?.Location?.ToString() ?? string.Empty);
if (result.Result?.Status != "valid") { if (result.Result?.Status != "valid") {
_logger.LogError($"Account status is not valid, was: {result.Result?.Status} \r\n {result.ResponseText}"); _logger.LogError($"Account status is not valid, was: {result.Result?.Status} \r\n {result.ResponseText}");
return IDomainResult.Failed(); return Result.InternalServerError();
} }
state.Cache = new RegistrationCache { state.Cache = new RegistrationCache {
AccountId = accountId, AccountId = accountId,
@ -175,41 +173,41 @@ public class LetsEncryptService : ILetsEncryptService {
Key = result.Result.Key Key = result.Result.Key
}; };
} }
return IDomainResult.Success(); return Result.Ok();
} catch (Exception ex) { } catch (Exception ex) {
const string message = "Let's Encrypt client unhandled exception"; const string message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return IDomainResult.CriticalDependencyError(message); return Result.InternalServerError(message);
} }
} }
#endregion #endregion
public (RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId) { public Result<RegistrationCache?> GetRegistrationCache(Guid sessionId) {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
if(state?.Cache == null) if(state?.Cache == null)
return IDomainResult.Failed<RegistrationCache?>(); return Result<RegistrationCache?>.InternalServerError(null);
return IDomainResult.Success(state.Cache); return Result<RegistrationCache?>.Ok(state.Cache);
} }
#region GetTermsOfService #region GetTermsOfService
public (string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId) { public Result<string?> GetTermsOfServiceUri(Guid sessionId) {
try { try {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
_logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}..."); _logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}...");
if (state.Directory?.Meta?.TermsOfService == null) { if (state.Directory?.Meta?.TermsOfService == null) {
return IDomainResult.Failed<string?>(); return Result<string?>.Ok(null);
} }
return IDomainResult.Success(state.Directory.Meta.TermsOfService); return Result<string?>.Ok(state.Directory.Meta.TermsOfService);
} catch (Exception ex) { } catch (Exception ex) {
var message = "Let's Encrypt client unhandled exception"; var message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return IDomainResult.CriticalDependencyError<string?>(message); return Result<string?>.InternalServerError(message);
} }
} }
#endregion #endregion
#region NewOrder #region NewOrder
public async Task<(Dictionary<string, string>?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType) { public async Task<Result<Dictionary<string, string>?>> NewOrder(Guid sessionId, string[] hostnames, string challengeType) {
try { try {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
_logger.LogInformation($"Executing {nameof(NewOrder)}..."); _logger.LogInformation($"Executing {nameof(NewOrder)}...");
@ -222,7 +220,7 @@ public class LetsEncryptService : ILetsEncryptService {
}).ToArray() ?? Array.Empty<OrderIdentifier>() }).ToArray() ?? Array.Empty<OrderIdentifier>()
}; };
if (state.Directory == null || state.Directory.NewOrder == null) if (state.Directory == null || state.Directory.NewOrder == null)
return (null, IDomainResult.Failed()); return Result<Dictionary<string, string>?>.InternalServerError(null);
var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewOrder); var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewOrder);
await HandleNonceAsync(sessionId, state.Directory.NewOrder, state); await HandleNonceAsync(sessionId, state.Directory.NewOrder, state);
var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader { var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader {
@ -232,10 +230,10 @@ public class LetsEncryptService : ILetsEncryptService {
PrepareRequestContent(request, json, HttpMethod.Post); PrepareRequestContent(request, json, HttpMethod.Post);
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post); var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
if (StatusEquals(order.Result?.Status, OrderStatus.Ready)) if (StatusEquals(order.Result?.Status, OrderStatus.Ready))
return (new Dictionary<string, string>(), IDomainResult.Success()); return Result<Dictionary<string, string>?>.Ok(new Dictionary<string, string>());
if (!StatusEquals(order.Result?.Status, OrderStatus.Pending)) { if (!StatusEquals(order.Result?.Status, OrderStatus.Pending)) {
_logger.LogError($"Created new order and expected status '{OrderStatus.Pending.GetDisplayName()}', but got: {order.Result?.Status} \r\n {order.Result}"); _logger.LogError($"Created new order and expected status '{OrderStatus.Pending.GetDisplayName()}', but got: {order.Result?.Status} \r\n {order.Result}");
return (null, IDomainResult.Failed()); return Result<Dictionary<string, string>?>.InternalServerError(null);
} }
state.CurrentOrder = order.Result; state.CurrentOrder = order.Result;
var results = new Dictionary<string, string>(); var results = new Dictionary<string, string>();
@ -253,12 +251,12 @@ public class LetsEncryptService : ILetsEncryptService {
continue; continue;
if (!StatusEquals(challengeResponse.Result?.Status, OrderStatus.Pending)) { if (!StatusEquals(challengeResponse.Result?.Status, OrderStatus.Pending)) {
_logger.LogError($"Expected authorization status '{OrderStatus.Pending.GetDisplayName()}', but got: {state.CurrentOrder?.Status} \r\n {challengeResponse.ResponseText}"); _logger.LogError($"Expected authorization status '{OrderStatus.Pending.GetDisplayName()}', but got: {state.CurrentOrder?.Status} \r\n {challengeResponse.ResponseText}");
return (null, IDomainResult.Failed()); return Result<Dictionary<string, string>?>.InternalServerError(null);
} }
var challenge = challengeResponse.Result?.Challenges?.FirstOrDefault(x => x?.Type == challengeType); var challenge = challengeResponse.Result?.Challenges?.FirstOrDefault(x => x?.Type == challengeType);
if (challenge == null || challenge.Token == null) { if (challenge == null || challenge.Token == null) {
_logger.LogError("Challenge or token is null"); _logger.LogError("Challenge or token is null");
return (null, IDomainResult.Failed()); return Result<Dictionary<string, string>?>.InternalServerError(null);
} }
state.Challenges.Add(challenge); state.Challenges.Add(challenge);
if (state.Cache != null) state.Cache.ChallengeType = challengeType; if (state.Cache != null) state.Cache.ChallengeType = challengeType;
@ -277,28 +275,29 @@ public class LetsEncryptService : ILetsEncryptService {
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
return (results, IDomainResult.Success());
return Result<Dictionary<string, string>?>.Ok(results);
} catch (Exception ex) { } catch (Exception ex) {
var message = "Let's Encrypt client unhandled exception"; var message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return (null, IDomainResult.CriticalDependencyError(message)); return Result<Dictionary<string, string>?>.InternalServerError(null, message);
} }
} }
#endregion #endregion
#region CompleteChallenges #region CompleteChallenges
public async Task<IDomainResult> CompleteChallenges(Guid sessionId) { public async Task<Result> CompleteChallenges(Guid sessionId) {
try { try {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
_logger.LogInformation($"Executing {nameof(CompleteChallenges)}..."); _logger.LogInformation($"Executing {nameof(CompleteChallenges)}...");
if (state.CurrentOrder?.Identifiers == null) { if (state.CurrentOrder?.Identifiers == null) {
return IDomainResult.Failed("Current order identifiers are null"); return Result.InternalServerError("Current order identifiers are null");
} }
for (var index = 0; index < state.Challenges.Count; index++) { for (var index = 0; index < state.Challenges.Count; index++) {
var challenge = state.Challenges[index]; var challenge = state.Challenges[index];
if (challenge?.Url == null) { if (challenge?.Url == null) {
_logger.LogError("Challenge URL is null"); _logger.LogError("Challenge URL is null");
return IDomainResult.Failed(); return Result.InternalServerError();
} }
var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url); var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url);
await HandleNonceAsync(sessionId, challenge.Url, state); await HandleNonceAsync(sessionId, challenge.Url, state);
@ -312,17 +311,17 @@ public class LetsEncryptService : ILetsEncryptService {
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
} }
return IDomainResult.Success(); return Result.Ok();
} catch (Exception ex) { } catch (Exception ex) {
var message = "Let's Encrypt client unhandled exception"; var message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return IDomainResult.CriticalDependencyError(message); return Result.InternalServerError(message);
} }
} }
#endregion #endregion
#region GetOrder #region GetOrder
public async Task<IDomainResult> GetOrder(Guid sessionId, string[] hostnames) { public async Task<Result> GetOrder(Guid sessionId, string[] hostnames) {
try { try {
_logger.LogInformation($"Executing {nameof(GetOrder)}"); _logger.LogInformation($"Executing {nameof(GetOrder)}");
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
@ -342,22 +341,22 @@ public class LetsEncryptService : ILetsEncryptService {
PrepareRequestContent(request, json, HttpMethod.Post); PrepareRequestContent(request, json, HttpMethod.Post);
var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post); var order = await SendAcmeRequest<Order>(request, state, HttpMethod.Post);
state.CurrentOrder = order.Result; state.CurrentOrder = order.Result;
return IDomainResult.Success(); return Result.Ok();
} catch (Exception ex) { } catch (Exception ex) {
var message = "Let's Encrypt client unhandled exception"; var message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return IDomainResult.CriticalDependencyError(message); return Result.InternalServerError(message);
} }
} }
#endregion #endregion
#region GetCertificates #region GetCertificates
public async Task<IDomainResult> GetCertificate(Guid sessionId, string subject) { public async Task<Result> GetCertificate(Guid sessionId, string subject) {
try { try {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
_logger.LogInformation($"Executing {nameof(GetCertificate)}..."); _logger.LogInformation($"Executing {nameof(GetCertificate)}...");
if (state.CurrentOrder?.Identifiers == null) { if (state.CurrentOrder?.Identifiers == null) {
return IDomainResult.Failed(); return Result.InternalServerError();
} }
var key = new RSACryptoServiceProvider(4096); var key = new RSACryptoServiceProvider(4096);
var csr = new CertificateRequest("CN=" + subject, var csr = new CertificateRequest("CN=" + subject,
@ -417,7 +416,7 @@ public class LetsEncryptService : ILetsEncryptService {
var pem = await SendAcmeRequest<string>(finalRequest, state, HttpMethod.Post); var pem = await SendAcmeRequest<string>(finalRequest, state, HttpMethod.Post);
if (state.Cache == null) { if (state.Cache == null) {
_logger.LogError($"{nameof(state.Cache)} is null"); _logger.LogError($"{nameof(state.Cache)} is null");
return IDomainResult.Failed(); return Result.InternalServerError();
} }
state.Cache.CachedCerts ??= new Dictionary<string, CertificateCache>(); state.Cache.CachedCerts ??= new Dictionary<string, CertificateCache>();
state.Cache.CachedCerts[subject] = new CertificateCache { state.Cache.CachedCerts[subject] = new CertificateCache {
@ -429,31 +428,31 @@ public class LetsEncryptService : ILetsEncryptService {
if (!string.IsNullOrEmpty(certPem)) { if (!string.IsNullOrEmpty(certPem)) {
var cert = new X509Certificate2(Encoding.UTF8.GetBytes(certPem)); var cert = new X509Certificate2(Encoding.UTF8.GetBytes(certPem));
} }
return IDomainResult.Success(); return Result.Ok();
} catch (Exception ex) { } catch (Exception ex) {
var message = "Let's Encrypt client unhandled exception"; var message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return IDomainResult.CriticalDependencyError(message); return Result.InternalServerError(message);
} }
} }
#endregion #endregion
public Task<IDomainResult> KeyChange(Guid sessionId) { public Task<Result> KeyChange(Guid sessionId) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public async Task<IDomainResult> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason) { public async Task<Result> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason) {
try { try {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
_logger.LogInformation($"Executing {nameof(RevokeCertificate)}..."); _logger.LogInformation($"Executing {nameof(RevokeCertificate)}...");
if (state.Cache?.CachedCerts == null || !state.Cache.CachedCerts.TryGetValue(subject, out var certificateCache) || certificateCache == null) { if (state.Cache?.CachedCerts == null || !state.Cache.CachedCerts.TryGetValue(subject, out var certificateCache) || certificateCache == null) {
_logger.LogError("Certificate not found in cache"); _logger.LogError("Certificate not found in cache");
return IDomainResult.Failed("Certificate not found"); return Result.InternalServerError("Certificate not found");
} }
var certPem = certificateCache.Cert ?? string.Empty; var certPem = certificateCache.Cert ?? string.Empty;
if (string.IsNullOrEmpty(certPem)) { if (string.IsNullOrEmpty(certPem)) {
_logger.LogError("Certificate PEM is null or empty"); _logger.LogError("Certificate PEM is null or empty");
return IDomainResult.Failed("Certificate PEM is null or empty"); return Result.InternalServerError("Certificate PEM is null or empty");
} }
var certificate = new X509Certificate2(Encoding.UTF8.GetBytes(certPem)); var certificate = new X509Certificate2(Encoding.UTF8.GetBytes(certPem));
var derEncodedCert = certificate.Export(X509ContentType.Cert); var derEncodedCert = certificate.Export(X509ContentType.Cert);
@ -478,14 +477,14 @@ public class LetsEncryptService : ILetsEncryptService {
var erroObj = responseText.ToObject<Problem>(); var erroObj = responseText.ToObject<Problem>();
} }
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
IDomainResult.CriticalDependencyError(responseText); Result.InternalServerError(responseText);
state.Cache.CachedCerts.Remove(subject); state.Cache.CachedCerts.Remove(subject);
_logger.LogInformation("Certificate revoked successfully"); _logger.LogInformation("Certificate revoked successfully");
return IDomainResult.Success(); return Result.Ok();
} catch (Exception ex) { } catch (Exception ex) {
var message = "Let's Encrypt client unhandled exception"; var message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return IDomainResult.CriticalDependencyError($"{message}: {ex.Message}"); return Result.InternalServerError($"{message}: {ex.Message}");
} }
} }
@ -493,31 +492,31 @@ public class LetsEncryptService : ILetsEncryptService {
private async Task HandleNonceAsync(Guid sessionId, Uri uri, State state) { private async Task HandleNonceAsync(Guid sessionId, Uri uri, State state) {
if (uri == null) throw new ArgumentNullException(nameof(uri)); if (uri == null) throw new ArgumentNullException(nameof(uri));
if (uri.OriginalString != "directory") { if (uri.OriginalString != "directory") {
var (nonce, newNonceResult) = await NewNonce(sessionId); var newNonceResult = await NewNonce(sessionId);
if (!newNonceResult.IsSuccess || nonce == null) { if (!newNonceResult.IsSuccess || newNonceResult.Value == null) {
throw new InvalidOperationException("Failed to retrieve nonce."); throw new InvalidOperationException("Failed to retrieve nonce.");
} }
state.Nonce = nonce; state.Nonce = newNonceResult.Value;
} }
else { else {
state.Nonce = default; state.Nonce = default;
} }
} }
private async Task<(string?, IDomainResult)> NewNonce(Guid sessionId) { private async Task<Result<string?>> NewNonce(Guid sessionId) {
try { try {
var state = GetOrCreateState(sessionId); var state = GetOrCreateState(sessionId);
_logger.LogInformation($"Executing {nameof(NewNonce)}..."); _logger.LogInformation($"Executing {nameof(NewNonce)}...");
if (state.Directory?.NewNonce == null) if (state.Directory?.NewNonce == null)
return (null, IDomainResult.Failed()); return Result<string?>.InternalServerError(null);
var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, state.Directory.NewNonce)); var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, state.Directory.NewNonce));
var nonce = result.Headers.GetValues("Replay-Nonce").FirstOrDefault(); var nonce = result.Headers.GetValues("Replay-Nonce").FirstOrDefault();
return (nonce, IDomainResult.Success()); return Result<string?>.Ok(nonce);
} }
catch (Exception ex) { catch (Exception ex) {
var message = "Let's Encrypt client unhandled exception"; var message = "Let's Encrypt client unhandled exception";
_logger.LogError(ex, message); _logger.LogError(ex, message);
return (null, IDomainResult.CriticalDependencyError(message)); return Result<string?>.InternalServerError(null, message);
} }
} }

View File

@ -1,11 +1,9 @@
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using DomainResults.Common;
using MaksIT.LetsEncryptServer.Services; using MaksIT.LetsEncryptServer.Services;
using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Entities;
using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; using MaksIT.Results;
namespace MaksIT.LetsEncryptServer.BackgroundServices { namespace MaksIT.LetsEncryptServer.BackgroundServices {
public class AutoRenewal : BackgroundService { public class AutoRenewal : BackgroundService {
@ -31,12 +29,14 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
while (!stoppingToken.IsCancellationRequested) { while (!stoppingToken.IsCancellationRequested) {
_logger.LogInformation("Background service is running."); _logger.LogInformation("Background service is running.");
var (accountsResponse, getAccountIdsResult) = await _cacheService.LoadAccountsFromCacheAsync(); var loadAccountsFromCacheResult = await _cacheService.LoadAccountsFromCacheAsync();
if (!getAccountIdsResult.IsSuccess || accountsResponse == null) { if (!loadAccountsFromCacheResult.IsSuccess || loadAccountsFromCacheResult.Value == null) {
LogErrors(getAccountIdsResult.Errors); LogErrors(loadAccountsFromCacheResult.Messages);
continue; continue;
} }
var accountsResponse = loadAccountsFromCacheResult.Value;
foreach (var account in accountsResponse.Where(x => !x.IsDisabled)) { foreach (var account in accountsResponse.Where(x => !x.IsDisabled)) {
await ProcessAccountAsync(account); await ProcessAccountAsync(account);
} }
@ -45,27 +45,27 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
} }
} }
private async Task<IDomainResult> ProcessAccountAsync(RegistrationCache cache) { private async Task<Result> ProcessAccountAsync(RegistrationCache cache) {
var hostnames = cache.GetHostsWithUpcomingSslExpiry(); var hostnames = cache.GetHostsWithUpcomingSslExpiry();
if (hostnames == null) { if (hostnames == null) {
_logger.LogError("Unexpected hostnames null"); _logger.LogError("Unexpected hostnames null");
return IDomainResult.Success(); return Result.Ok();
} }
if (!hostnames.Any()) { if (!hostnames.Any()) {
_logger.LogInformation("No hosts found with upcoming SSL expiry"); _logger.LogInformation("No hosts found with upcoming SSL expiry");
return IDomainResult.Success(); return Result.Ok();
} }
var (_, renewResult) = await _certsFlowService.FullFlow(cache.IsStaging, cache.AccountId, cache.Description, cache.Contacts, cache.ChallengeType, hostnames); var fullFlowResult = await _certsFlowService.FullFlow(cache.IsStaging, cache.AccountId, cache.Description, cache.Contacts, cache.ChallengeType, hostnames);
if (!renewResult.IsSuccess) if (!fullFlowResult.IsSuccess)
return renewResult; return fullFlowResult;
_logger.LogInformation($"Certificates renewed for account {cache.AccountId}"); _logger.LogInformation($"Certificates renewed for account {cache.AccountId}");
return IDomainResult.Success(); return Result.Ok();
} }

View File

@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using DomainResults.Mvc;
using MaksIT.LetsEncryptServer.Services; using MaksIT.LetsEncryptServer.Services;
using MaksIT.Models.LetsEncryptServer.Account.Requests; using MaksIT.Models.LetsEncryptServer.Account.Requests;

View File

@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using DomainResults.Mvc;
using MaksIT.LetsEncryptServer.Services; using MaksIT.LetsEncryptServer.Services;
using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests;

View File

@ -1,8 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using DomainResults.Mvc;
using MaksIT.LetsEncryptServer.Services; using MaksIT.LetsEncryptServer.Services;

View File

@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DomainResult" Version="3.3.1" /> <PackageReference Include="MaksIT.Results" Version="1.0.6" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.9" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />

View File

@ -1,9 +1,9 @@
using DomainResults.Common; 
using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Entities;
using MaksIT.Models; using MaksIT.Models;
using MaksIT.Models.LetsEncryptServer.Account.Requests; using MaksIT.Models.LetsEncryptServer.Account.Requests;
using MaksIT.Models.LetsEncryptServer.Account.Responses; using MaksIT.Models.LetsEncryptServer.Account.Responses;
using MaksIT.Results;
namespace MaksIT.LetsEncryptServer.Services; namespace MaksIT.LetsEncryptServer.Services;
@ -14,11 +14,11 @@ public interface IAccountInternalService {
public interface IAccountRestService { public interface IAccountRestService {
Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync(); Task<Result<GetAccountResponse[]?>> GetAccountsAsync();
Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId); Task<Result<GetAccountResponse?>> GetAccountAsync(Guid accountId);
Task<(GetAccountResponse?, IDomainResult)> PostAccountAsync(PostAccountRequest requestData); Task<Result<GetAccountResponse?>> PostAccountAsync(PostAccountRequest requestData);
Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData); Task<Result<GetAccountResponse?>> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData);
Task<IDomainResult> DeleteAccountAsync(Guid accountId); Task<Result> DeleteAccountAsync(Guid accountId);
} }
public interface IAccountService : IAccountInternalService, IAccountRestService { } public interface IAccountService : IAccountInternalService, IAccountRestService { }
@ -41,34 +41,37 @@ public class AccountService : IAccountService {
#region Accounts #region Accounts
public async Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync() { public async Task<Result<GetAccountResponse[]?>> GetAccountsAsync() {
var (caches, result) = await _cacheService.LoadAccountsFromCacheAsync(); var accountsFromCacheResult = await _cacheService.LoadAccountsFromCacheAsync();
if (!result.IsSuccess || caches == null) { if (!accountsFromCacheResult.IsSuccess || accountsFromCacheResult.Value == null) {
return (null, result); return accountsFromCacheResult
.ToResultOfType<GetAccountResponse[]?>(_ => null);
} }
var accounts = caches var accounts = accountsFromCacheResult.Value
.Select(x => CreateGetAccountResponse(x.AccountId, x)) .Select(x => CreateGetAccountResponse(x.AccountId, x))
.ToArray(); .ToArray();
return IDomainResult.Success(accounts); return Result<GetAccountResponse[]?>.Ok(accounts);
} }
public async Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId) { public async Task<Result<GetAccountResponse?>> GetAccountAsync(Guid accountId) {
var (cache, result) = await _cacheService.LoadAccountFromCacheAsync(accountId); var loadFromCacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId);
if (!result.IsSuccess || cache == null) { if (!loadFromCacheResult.IsSuccess || loadFromCacheResult.Value == null) {
return (null, result); return loadFromCacheResult.ToResultOfType<GetAccountResponse?>(_ => null);
} }
return IDomainResult.Success(CreateGetAccountResponse(accountId, cache)); var cache = loadFromCacheResult.Value;
return Result<GetAccountResponse?>.Ok(CreateGetAccountResponse(accountId, cache));
} }
public async Task<(GetAccountResponse?, IDomainResult)> PostAccountAsync(PostAccountRequest requestData) { public async Task<Result<GetAccountResponse?>> PostAccountAsync(PostAccountRequest requestData) {
// TODO: check for overlapping hostnames in already existing accounts // TODO: check for overlapping hostnames in already existing accounts
var (accountId, newCertsResult) = await _certsFlowService.FullFlow( var fullFlowResult = await _certsFlowService.FullFlow(
requestData.IsStaging, requestData.IsStaging,
null, null,
requestData.Description, requestData.Description,
@ -77,25 +80,31 @@ public class AccountService : IAccountService {
requestData.Hostnames requestData.Hostnames
); );
if (!newCertsResult.IsSuccess || accountId == null)
return (null, newCertsResult);
if (!fullFlowResult.IsSuccess || fullFlowResult.Value == null)
return fullFlowResult.ToResultOfType<GetAccountResponse?>(_ => null);
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId.Value); var accountId = fullFlowResult.Value.Value;
if (!loadResult.IsSuccess || cache == null) {
return (null, loadResult); var loadAccauntFromCacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId);
if (!loadAccauntFromCacheResult.IsSuccess || loadAccauntFromCacheResult.Value == null) {
return loadAccauntFromCacheResult.ToResultOfType<GetAccountResponse?>(_ => null);
} }
return IDomainResult.Success(CreateGetAccountResponse(accountId.Value, cache)); var cache = loadAccauntFromCacheResult.Value;
return Result<GetAccountResponse?>.Ok(CreateGetAccountResponse(accountId, cache));
} }
public async Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) { public async Task<Result<GetAccountResponse?>> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) {
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); var loadAccountResult = await _cacheService.LoadAccountFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null) { if (!loadAccountResult.IsSuccess || loadAccountResult.Value == null) {
return (null, loadResult); return loadAccountResult.ToResultOfType<GetAccountResponse?>(_ => null);
} }
var cache = loadAccountResult.Value;
if (requestData.Description != null) { if (requestData.Description != null) {
switch (requestData.Description.Op) { switch (requestData.Description.Op) {
case PatchOperation.Replace: case PatchOperation.Replace:
@ -172,11 +181,11 @@ public class AccountService : IAccountService {
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache); var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
if (!saveResult.IsSuccess) { if (!saveResult.IsSuccess) {
return (null, saveResult); return saveResult.ToResultOfType<GetAccountResponse?>(default);
} }
if (hostnamesToAdd.Count > 0) { if (hostnamesToAdd.Count > 0) {
var (_, newCertsResult) = await _certsFlowService.FullFlow( var fullFlowResult = await _certsFlowService.FullFlow(
cache.IsStaging, cache.IsStaging,
cache.AccountId, cache.AccountId,
cache.Description, cache.Description,
@ -185,8 +194,8 @@ public class AccountService : IAccountService {
hostnamesToAdd.ToArray() hostnamesToAdd.ToArray()
); );
if (!newCertsResult.IsSuccess) if (!fullFlowResult.IsSuccess)
return (null, newCertsResult); return fullFlowResult.ToResultOfType<GetAccountResponse?>(_ => null);
} }
if (hostnamesToRemove.Count > 0) { if (hostnamesToRemove.Count > 0) {
@ -199,18 +208,18 @@ public class AccountService : IAccountService {
); );
if (!revokeResult.IsSuccess) if (!revokeResult.IsSuccess)
return (null, revokeResult); return revokeResult.ToResultOfType<GetAccountResponse?>(default);
} }
(cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); loadAccountResult = await _cacheService.LoadAccountFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null) { if (!loadAccountResult.IsSuccess || loadAccountResult.Value == null) {
return (null, loadResult); return loadAccountResult.ToResultOfType<GetAccountResponse?>(_ => null);
} }
return IDomainResult.Success(CreateGetAccountResponse(accountId, cache)); return Result<GetAccountResponse?>.Ok(CreateGetAccountResponse(accountId, cache));
} }
public async Task<IDomainResult> DeleteAccountAsync(Guid accountId) { public async Task<Result> DeleteAccountAsync(Guid accountId) {
// TODO: Revoke all certificates // TODO: Revoke all certificates
// Remove from cache // Remove from cache

View File

@ -1,5 +1,5 @@
using DomainResults.Common; using MaksIT.Models.Agent.Requests;
using MaksIT.Models.Agent.Requests; using MaksIT.Results;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
@ -7,9 +7,9 @@ using System.Text.Json;
namespace MaksIT.LetsEncryptServer.Services { namespace MaksIT.LetsEncryptServer.Services {
public interface IAgentService { public interface IAgentService {
Task<IDomainResult> GetHelloWorld(); Task<Result> GetHelloWorld();
Task<IDomainResult> UploadCerts(Dictionary<string, string> certs); Task<Result> UploadCerts(Dictionary<string, string> certs);
Task<IDomainResult> ReloadService(string serviceName); Task<Result> ReloadService(string serviceName);
} }
public class AgentService : IAgentService { public class AgentService : IAgentService {
@ -28,23 +28,23 @@ namespace MaksIT.LetsEncryptServer.Services {
_httpClient = httpClient; _httpClient = httpClient;
} }
public Task<IDomainResult> GetHelloWorld() { public Task<Result> GetHelloWorld() {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public async Task<IDomainResult> ReloadService(string serviceName) { public async Task<Result> ReloadService(string serviceName) {
var requestBody = new ServiceReloadRequest { ServiceName = serviceName }; var requestBody = new ServiceReloadRequest { ServiceName = serviceName };
var endpoint = $"/Service/Reload"; var endpoint = $"/Service/Reload";
return await SendHttpRequest(requestBody, endpoint); return await SendHttpRequest(requestBody, endpoint);
} }
public async Task<IDomainResult> UploadCerts(Dictionary<string, string> certs) { public async Task<Result> UploadCerts(Dictionary<string, string> certs) {
var requestBody = new CertsUploadRequest { Certs = certs }; var requestBody = new CertsUploadRequest { Certs = certs };
var endpoint = $"/Certs/Upload"; var endpoint = $"/Certs/Upload";
return await SendHttpRequest(requestBody, endpoint); return await SendHttpRequest(requestBody, endpoint);
} }
private async Task<IDomainResult> SendHttpRequest<T>(T requestBody, string endpoint) { private async Task<Result> SendHttpRequest<T>(T requestBody, string endpoint) {
try { try {
var request = new HttpRequestMessage(HttpMethod.Post, $"{_appSettings.Agent.AgentHostname}:{_appSettings.Agent.AgentPort}{endpoint}") { var request = new HttpRequestMessage(HttpMethod.Post, $"{_appSettings.Agent.AgentHostname}:{_appSettings.Agent.AgentPort}{endpoint}") {
Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json") Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json")
@ -56,16 +56,16 @@ namespace MaksIT.LetsEncryptServer.Services {
var response = await _httpClient.SendAsync(request); var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
return IDomainResult.Success(); return Result.Ok();
} }
else { else {
_logger.LogError($"Request to {endpoint} failed with status code: {response.StatusCode}"); _logger.LogError($"Request to {endpoint} failed with status code: {response.StatusCode}");
return IDomainResult.Failed($"Request to {endpoint} failed with status code: {response.StatusCode}"); return Result.InternalServerError($"Request to {endpoint} failed with status code: {response.StatusCode}");
} }
} }
catch (Exception ex) { catch (Exception ex) {
_logger.LogError(ex, "Something went wrong"); _logger.LogError(ex, "Something went wrong");
return IDomainResult.Failed("Something went wrong"); return Result.InternalServerError("Something went wrong");
} }
} }
} }

View File

@ -1,18 +1,17 @@
using System.Text.Json; using System.Text.Json;
using DomainResults.Common;
using MaksIT.Core.Extensions; using MaksIT.Core.Extensions;
using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Entities;
using MaksIT.Models; using MaksIT.Results;
namespace MaksIT.LetsEncryptServer.Services; namespace MaksIT.LetsEncryptServer.Services;
public interface ICacheService { public interface ICacheService {
Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync(); Task<Result<RegistrationCache[]?>> LoadAccountsFromCacheAsync();
Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId); Task<Result<RegistrationCache?>> LoadAccountFromCacheAsync(Guid accountId);
Task<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache); Task<Result> SaveToCacheAsync(Guid accountId, RegistrationCache cache);
Task<IDomainResult> DeleteFromCacheAsync(Guid accountId); Task<Result> DeleteFromCacheAsync(Guid accountId);
} }
public class CacheService : ICacheService, IDisposable { public class CacheService : ICacheService, IDisposable {
@ -47,63 +46,58 @@ public class CacheService : ICacheService, IDisposable {
#region Cache Operations #region Cache Operations
public async Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync() { public async Task<Result<RegistrationCache[]?>> LoadAccountsFromCacheAsync() {
return await _lockManager.ExecuteWithLockAsync(async () => { return await _lockManager.ExecuteWithLockAsync(async () => {
var accountIds = GetCachedAccounts(); var accountIds = GetCachedAccounts();
var cacheLoadTasks = accountIds.Select(accountId => LoadFromCacheInternalAsync(accountId)).ToList(); var cacheLoadTasks = accountIds.Select(accountId => LoadFromCacheInternalAsync(accountId)).ToList();
var caches = new List<RegistrationCache>(); var caches = new List<RegistrationCache>();
foreach (var task in cacheLoadTasks) { foreach (var task in cacheLoadTasks) {
var (registrationCache, getRegistrationCacheResult) = await task; var taskResult = await task;
if (!getRegistrationCacheResult.IsSuccess || registrationCache == null) { if (!taskResult.IsSuccess || taskResult.Value == null) {
// Depending on how you want to handle partial failures, you might want to return here // Depending on how you want to handle partial failures, you might want to return here
// or continue loading other caches. For now, let's continue. // or continue loading other caches. For now, let's continue.
continue; continue;
} }
var registrationCache = taskResult.Value;
caches.Add(registrationCache); caches.Add(registrationCache);
} }
return IDomainResult.Success(caches.ToArray()); return Result<RegistrationCache[]?>.Ok(caches.ToArray());
}); });
} }
private async Task<Result<RegistrationCache?>> LoadFromCacheInternalAsync(Guid accountId) {
private async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheInternalAsync(Guid accountId) {
var cacheFilePath = GetCacheFilePath(accountId); var cacheFilePath = GetCacheFilePath(accountId);
if (!File.Exists(cacheFilePath)) { if (!File.Exists(cacheFilePath)) {
var message = $"Cache file not found for account {accountId}"; var message = $"Cache file not found for account {accountId}";
_logger.LogWarning(message); _logger.LogWarning(message);
return IDomainResult.Failed<RegistrationCache>(message); return Result<RegistrationCache?>.InternalServerError(null, message);
} }
var json = await File.ReadAllTextAsync(cacheFilePath); var json = await File.ReadAllTextAsync(cacheFilePath);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {
var message = $"Cache file is empty for account {accountId}"; var message = $"Cache file is empty for account {accountId}";
_logger.LogWarning(message); _logger.LogWarning(message);
return IDomainResult.Failed<RegistrationCache>(message); return Result<RegistrationCache?>.InternalServerError(null, message);
} }
var cache = JsonSerializer.Deserialize<RegistrationCache>(json); var cache = JsonSerializer.Deserialize<RegistrationCache>(json);
return IDomainResult.Success(cache); return Result<RegistrationCache?>.Ok(cache);
} }
private async Task<Result> SaveToCacheInternalAsync(Guid accountId, RegistrationCache cache) {
private async Task<IDomainResult> SaveToCacheInternalAsync(Guid accountId, RegistrationCache cache) {
var cacheFilePath = GetCacheFilePath(accountId); var cacheFilePath = GetCacheFilePath(accountId);
var json = JsonSerializer.Serialize(cache); var json = JsonSerializer.Serialize(cache);
await File.WriteAllTextAsync(cacheFilePath, json); await File.WriteAllTextAsync(cacheFilePath, json);
_logger.LogInformation($"Cache file saved for account {accountId}"); _logger.LogInformation($"Cache file saved for account {accountId}");
return DomainResult.Success(); return Result.Ok();
} }
private Result DeleteFromCacheInternal(Guid accountId) {
private IDomainResult DeleteFromCacheInternal(Guid accountId) {
var cacheFilePath = GetCacheFilePath(accountId); var cacheFilePath = GetCacheFilePath(accountId);
if (File.Exists(cacheFilePath)) { if (File.Exists(cacheFilePath)) {
File.Delete(cacheFilePath); File.Delete(cacheFilePath);
@ -112,22 +106,22 @@ public class CacheService : ICacheService, IDisposable {
else { else {
_logger.LogWarning($"Cache file not found for account {accountId}"); _logger.LogWarning($"Cache file not found for account {accountId}");
} }
return DomainResult.Success(); return Result.Ok();
} }
#endregion #endregion
public async Task<Result<RegistrationCache?>> LoadAccountFromCacheAsync(Guid accountId) {
return await _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId));
public Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId) {
return _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId));
} }
public Task<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache) { public async Task<Result> SaveToCacheAsync(Guid accountId, RegistrationCache cache) {
return _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache)); return await _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache));
} }
public Task<IDomainResult> DeleteFromCacheAsync(Guid accountId) { public async Task<Result> DeleteFromCacheAsync(Guid accountId) {
return _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId)); return await _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId));
} }
public void Dispose() { public void Dispose() {

View File

@ -1,47 +1,42 @@
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using DomainResults.Common;
using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Entities;
using MaksIT.LetsEncrypt.Services; using MaksIT.LetsEncrypt.Services;
using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests;
using System.Security.Cryptography;
using MaksIT.LetsEncrypt.Entities.LetsEncrypt; using MaksIT.LetsEncrypt.Entities.LetsEncrypt;
using MaksIT.Results;
namespace MaksIT.LetsEncryptServer.Services; namespace MaksIT.LetsEncryptServer.Services;
public interface ICertsCommonService { public interface ICertsCommonService {
Result<string?> GetTermsOfService(Guid sessionId);
(string?, IDomainResult) GetTermsOfService(Guid sessionId); Task<Result> CompleteChallengesAsync(Guid sessionId);
Task<IDomainResult> CompleteChallengesAsync(Guid sessionId);
} }
public interface ICertsInternalService : ICertsCommonService { public interface ICertsInternalService : ICertsCommonService {
Task<(Guid?, IDomainResult)> ConfigureClientAsync(bool isStaging); Task<Result<Guid?>> ConfigureClientAsync(bool isStaging);
Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts); Task<Result<Guid?>> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts);
Task<(List<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType); Task<Result<List<string>?>> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType);
Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames); Task<Result> GetOrderAsync(Guid sessionId, string[] hostnames);
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames); Task<Result> GetCertificatesAsync(Guid sessionId, string[] hostnames);
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames); Task<Result<Dictionary<string, string>?>> ApplyCertificatesAsync(Guid sessionId, string[] hostnames);
Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, string[] hostnames); Task<Result> RevokeCertificatesAsync(Guid sessionId, string[] hostnames);
Task<(Guid?, IDomainResult)> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames); Task<Result<Guid?>> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames);
Task<IDomainResult> FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames); Task<Result> FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames);
} }
public interface ICertsRestService : ICertsCommonService { public interface ICertsRestService : ICertsCommonService {
Task<(Guid?, IDomainResult)> ConfigureClientAsync(ConfigureClientRequest requestData); Task<Result<Guid?>> ConfigureClientAsync(ConfigureClientRequest requestData);
Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData); Task<Result<Guid?>> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData);
Task<(List<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData); Task<Result<List<string>?>> NewOrderAsync(Guid sessionId, NewOrderRequest requestData);
Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData); Task<Result> GetOrderAsync(Guid sessionId, GetOrderRequest requestData);
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); Task<Result> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); Task<Result<Dictionary<string, string>?>> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData); Task<Result> RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData);
} }
public interface ICertsRestChallengeService { public interface ICertsRestChallengeService {
(string?, IDomainResult) AcmeChallenge(string fileName); Result<string?> AcmeChallenge(string fileName);
} }
public interface ICertsFlowService public interface ICertsFlowService
@ -50,13 +45,11 @@ public interface ICertsFlowService
ICertsRestChallengeService { } ICertsRestChallengeService { }
public class CertsFlowService : ICertsFlowService { public class CertsFlowService : ICertsFlowService {
private readonly Configuration _appSettings; private readonly Configuration _appSettings;
private readonly ILogger<CertsFlowService> _logger; private readonly ILogger<CertsFlowService> _logger;
private readonly ILetsEncryptService _letsEncryptService; private readonly ILetsEncryptService _letsEncryptService;
private readonly ICacheService _cacheService; private readonly ICacheService _cacheService;
private readonly IAgentService _agentService; private readonly IAgentService _agentService;
private readonly string _acmePath; private readonly string _acmePath;
public CertsFlowService( public CertsFlowService(
@ -71,256 +64,213 @@ public class CertsFlowService : ICertsFlowService {
_letsEncryptService = letsEncryptService; _letsEncryptService = letsEncryptService;
_cacheService = cashService; _cacheService = cashService;
_agentService = agentService; _agentService = agentService;
_acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme"); _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme");
if (!Directory.Exists(_acmePath)) if (!Directory.Exists(_acmePath))
Directory.CreateDirectory(_acmePath); Directory.CreateDirectory(_acmePath);
} }
#region Common methods #region Common methods
public Result<string?> GetTermsOfService(Guid sessionId) {
var result = _letsEncryptService.GetTermsOfServiceUri(sessionId);
return result;
public (string?, IDomainResult) GetTermsOfService(Guid sessionId) {
var (terms, getTermsResult) = _letsEncryptService.GetTermsOfServiceUri(sessionId);
if (!getTermsResult.IsSuccess || terms == null)
return (null, getTermsResult);
return IDomainResult.Success<string>(terms);
} }
public async Task<IDomainResult> CompleteChallengesAsync(Guid sessionId) { public async Task<Result> CompleteChallengesAsync(Guid sessionId) {
return await _letsEncryptService.CompleteChallenges(sessionId); return await _letsEncryptService.CompleteChallenges(sessionId);
} }
#endregion #endregion
#region Internal methods #region Internal methods
public async Task<(Guid?, IDomainResult)> ConfigureClientAsync(bool isStaging) { public async Task<Result<Guid?>> ConfigureClientAsync(bool isStaging) {
var sessionId = Guid.NewGuid(); var sessionId = Guid.NewGuid();
var result = await _letsEncryptService.ConfigureClient(sessionId, isStaging); var result = await _letsEncryptService.ConfigureClient(sessionId, isStaging);
if (!result.IsSuccess) if (!result.IsSuccess)
return (null, result); return result.ToResultOfType<Guid?>(default);
return Result<Guid?>.Ok(sessionId);
return IDomainResult.Success(sessionId);
} }
public async Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts) { public async Task<Result<Guid?>> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts) {
RegistrationCache? cache = null; RegistrationCache? cache = null;
if (accountId == null) { if (accountId == null) {
accountId = Guid.NewGuid(); accountId = Guid.NewGuid();
} } else {
else { var cacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId.Value);
var (loadedCache, loadCaceResutl) = await _cacheService.LoadAccountFromCacheAsync(accountId.Value); if (!cacheResult.IsSuccess || cacheResult.Value == null) {
if (!loadCaceResutl.IsSuccess || loadCaceResutl == null) {
accountId = Guid.NewGuid(); accountId = Guid.NewGuid();
} } else {
else { cache = cacheResult.Value;
cache = loadedCache;
} }
} }
var result = await _letsEncryptService.Init(sessionId, accountId.Value, description, contacts, cache); var result = await _letsEncryptService.Init(sessionId, accountId.Value, description, contacts, cache);
return result.IsSuccess ? IDomainResult.Success<Guid>(accountId.Value) : (null, result); if (!result.IsSuccess)
return result.ToResultOfType<Guid?>(default);
return Result<Guid?>.Ok(accountId.Value);
} }
public async Task<(List<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType) { public async Task<Result<List<string>?>> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType) {
var (results, newOrderResult) = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType); var orderResult = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType);
if (!newOrderResult.IsSuccess || results == null) if (!orderResult.IsSuccess || orderResult.Value == null)
return (null, newOrderResult); return orderResult.ToResultOfType<List<string>?>(_ => null);
var challenges = new List<string>(); var challenges = new List<string>();
foreach (var result in results) { foreach (var kvp in orderResult.Value) {
string[] splitToken = result.Value.Split('.'); string[] splitToken = kvp.Value.Split('.');
File.WriteAllText(Path.Combine(_acmePath, splitToken[0]), result.Value); File.WriteAllText(Path.Combine(_acmePath, splitToken[0]), kvp.Value);
challenges.Add(splitToken[0]); challenges.Add(splitToken[0]);
} }
return Result<List<string>?>.Ok(challenges);
return IDomainResult.Success(challenges);
} }
public async Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames) { public async Task<Result> GetCertificatesAsync(Guid sessionId, string[] hostnames) {
foreach (var subject in hostnames) { foreach (var subject in hostnames) {
var result = await _letsEncryptService.GetCertificate(sessionId, subject); var result = await _letsEncryptService.GetCertificate(sessionId, subject);
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
Thread.Sleep(1000); Thread.Sleep(1000);
} }
var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId);
// TODO: Move to separate method if (!cacheResult.IsSuccess || cacheResult.Value == null)
// Persist the cache return cacheResult;
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId); var saveResult = await _cacheService.SaveToCacheAsync(cacheResult.Value.AccountId, cacheResult.Value);
if (!getCacheResult.IsSuccess || cache == null)
return getCacheResult;
var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache);
if (!saveResult.IsSuccess) if (!saveResult.IsSuccess)
return saveResult; return saveResult;
return Result.Ok();
return IDomainResult.Success();
} }
public async Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames) { public async Task<Result> GetOrderAsync(Guid sessionId, string[] hostnames) {
return await _letsEncryptService.GetOrder(sessionId, hostnames); return await _letsEncryptService.GetOrder(sessionId, hostnames);
} }
public async Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) { public async Task<Result<Dictionary<string, string>?>> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) {
var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId);
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId); if (!cacheResult.IsSuccess || cacheResult.Value?.CachedCerts == null)
if (!getCacheResult.IsSuccess || cache?.CachedCerts == null) return cacheResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
return (null, getCacheResult);
var results = new Dictionary<string, string>(); var results = new Dictionary<string, string>();
foreach (var hostname in hostnames) { foreach (var hostname in hostnames) {
CertificateCache? cert; CertificateCache? cert;
if (cache.TryGetCachedCertificate(hostname, out cert)) { if (cacheResult.Value.TryGetCachedCertificate(hostname, out cert)) {
var content = $"{cert.Cert}\n{cert.PrivatePem}"; var content = $"{cert.Cert}\n{cert.PrivatePem}";
results.Add(hostname, content); results.Add(hostname, content);
} }
} }
// Send the certificates to the via agent
var uploadResult = await _agentService.UploadCerts(results); var uploadResult = await _agentService.UploadCerts(results);
if (!uploadResult.IsSuccess) if (!uploadResult.IsSuccess)
return (null, uploadResult); return uploadResult.ToResultOfType<Dictionary<string, string>?>(default);
var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload); var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload);
if (!reloadResult.IsSuccess) if (!reloadResult.IsSuccess)
return (null, reloadResult); return reloadResult.ToResultOfType<Dictionary<string, string>?>(default);
return Result<Dictionary<string, string>?>.Ok(results);
return IDomainResult.Success(results);
} }
public async Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, string[] hostnames) { public async Task<Result> RevokeCertificatesAsync(Guid sessionId, string[] hostnames) {
foreach (var hostname in hostnames) { foreach (var hostname in hostnames) {
var result = await _letsEncryptService.RevokeCertificate(sessionId, hostname, RevokeReason.Unspecified); var result = await _letsEncryptService.RevokeCertificate(sessionId, hostname, RevokeReason.Unspecified);
if (!result.IsSuccess) if (!result.IsSuccess)
return result; return result;
} }
var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId);
// TODO: Move to separate method if (!cacheResult.IsSuccess || cacheResult.Value == null)
// Persist the cache return cacheResult;
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId); var saveResult = await _cacheService.SaveToCacheAsync(cacheResult.Value.AccountId, cacheResult.Value);
if (!getCacheResult.IsSuccess || cache == null)
return getCacheResult;
var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache);
if (!saveResult.IsSuccess) if (!saveResult.IsSuccess)
return saveResult; return saveResult;
return Result.Ok();
return IDomainResult.Success();
} }
public async Task<(Guid?, IDomainResult)> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[]hostnames) { public async Task<Result<Guid?>> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames) {
var (sessionId, configureClientResult) = await ConfigureClientAsync(isStaging); var sessionResult = await ConfigureClientAsync(isStaging);
if (!configureClientResult.IsSuccess || sessionId == null) if (!sessionResult.IsSuccess || sessionResult.Value == null)
return (null, configureClientResult); return sessionResult;
(accountId, var initResult) = await InitAsync(sessionId.Value, accountId, description, contacts); var sessionId = sessionResult.Value.Value;
var initResult = await InitAsync(sessionId, accountId, description, contacts);
if (!initResult.IsSuccess) if (!initResult.IsSuccess)
return (null, initResult); return initResult.ToResultOfType<Guid?>(_ => null);
var (challenges, newOrderResult) = await NewOrderAsync(sessionId.Value, hostnames, challengeType); var challengesResult = await NewOrderAsync(sessionId, hostnames, challengeType);
if (!newOrderResult.IsSuccess) if (!challengesResult.IsSuccess)
return (null, newOrderResult); return challengesResult.ToResultOfType<Guid?>(_ => null);
if (challenges?.Count > 0) { if (challengesResult.Value?.Count > 0) {
var challengeResult = await CompleteChallengesAsync(sessionId.Value); var challengeResult = await CompleteChallengesAsync(sessionId);
if (!challengeResult.IsSuccess) if (!challengeResult.IsSuccess)
return (null, challengeResult); return challengeResult.ToResultOfType<Guid?>(default);
}
var getOrderResult = await GetOrderAsync(sessionId, hostnames);
if (!getOrderResult.IsSuccess)
return getOrderResult.ToResultOfType<Guid?>(default);
var certsResult = await GetCertificatesAsync(sessionId, hostnames);
if (!certsResult.IsSuccess)
return certsResult.ToResultOfType<Guid?>(default);
// Bypass applying certificates in staging mode
if (!isStaging) {
var applyCertsResult = await ApplyCertificatesAsync(sessionId, hostnames);
if (!applyCertsResult.IsSuccess)
return applyCertsResult.ToResultOfType<Guid?>(_ => null);
} }
return Result<Guid?>.Ok(initResult.Value);
var getOrderResult = await GetOrderAsync(sessionId.Value, hostnames);
if (!getOrderResult.IsSuccess)
return (null, getOrderResult);
var certs = await GetCertificatesAsync(sessionId.Value, hostnames);
if (!certs.IsSuccess)
return (null, certs);
var (_, applyCertsResult) = await ApplyCertificatesAsync(sessionId.Value, hostnames);
if (!applyCertsResult.IsSuccess)
return (null, applyCertsResult);
return IDomainResult.Success(accountId);
} }
public async Task<IDomainResult> FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames) { public async Task<Result> FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames) {
var (sessionId, configureClientResult) = await ConfigureClientAsync(isStaging); var sessionResult = await ConfigureClientAsync(isStaging);
if (!configureClientResult.IsSuccess || sessionId == null) if (!sessionResult.IsSuccess || sessionResult.Value == null)
return configureClientResult; return sessionResult;
var (_, initResult) = await InitAsync(sessionId.Value, accountId, description, contacts); var sessionId = sessionResult.Value.Value;
var initResult = await InitAsync(sessionId, accountId, description, contacts);
if (!initResult.IsSuccess) if (!initResult.IsSuccess)
return initResult; return initResult;
var revokeResult = await RevokeCertificatesAsync(sessionId, hostnames);
var revokeResult = await RevokeCertificatesAsync(sessionId.Value, hostnames);
if (!revokeResult.IsSuccess) if (!revokeResult.IsSuccess)
return revokeResult; return revokeResult;
return Result.Ok();
return IDomainResult.Success();
} }
#endregion #endregion
#region REST methods #region REST methods
public async Task<Result<Guid?>> ConfigureClientAsync(ConfigureClientRequest requestData) {
return await ConfigureClientAsync(requestData.IsStaging);
}
public async Task<Result<Guid?>> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) {
return await InitAsync(sessionId, accountId, requestData.Description, requestData.Contacts);
}
public async Task<Result<List<string>>> NewOrderAsync(Guid sessionId, NewOrderRequest requestData) {
return await NewOrderAsync(sessionId, requestData.Hostnames, requestData.ChallengeType);
}
public async Task<Result> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) {
return await GetCertificatesAsync(sessionId, requestData.Hostnames);
}
public async Task<Result> GetOrderAsync(Guid sessionId, GetOrderRequest requestData) {
return await GetOrderAsync(sessionId, requestData.Hostnames);
}
public Task<(Guid?, IDomainResult)> ConfigureClientAsync(ConfigureClientRequest requestData) => public async Task<Result<Dictionary<string, string>>> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) =>
ConfigureClientAsync(requestData.IsStaging); await ApplyCertificatesAsync(sessionId, requestData.Hostnames);
public async Task<Result> RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData) =>
public Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) => await RevokeCertificatesAsync(sessionId, requestData.Hostnames);
InitAsync(sessionId, accountId, requestData.Description, requestData.Contacts);
public Task<(List<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData) =>
NewOrderAsync(sessionId, requestData.Hostnames, requestData.ChallengeType);
public Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) =>
GetCertificatesAsync(sessionId, requestData.Hostnames);
public Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData) =>
GetOrderAsync(sessionId, requestData.Hostnames);
public Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) =>
ApplyCertificatesAsync(sessionId, requestData.Hostnames);
public Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData) =>
RevokeCertificatesAsync(sessionId, requestData.Hostnames);
#endregion #endregion
#region Acme Challenge REST methods #region Acme Challenge REST methods
public Result<string?> AcmeChallenge(string fileName) {
public (string?, IDomainResult) AcmeChallenge(string fileName) {
DeleteExporedChallenges(); DeleteExporedChallenges();
var challengePath = Path.Combine(_acmePath, fileName); var challengePath = Path.Combine(_acmePath, fileName);
if(!File.Exists(challengePath)) if(!File.Exists(challengePath))
return IDomainResult.NotFound<string?>(); return Result<string?>.NotFound(null);
var fileContent = File.ReadAllText(Path.Combine(_acmePath, fileName)); var fileContent = File.ReadAllText(Path.Combine(_acmePath, fileName));
return IDomainResult.Success(fileContent); return Result<string?>.Ok(fileContent);
} }
private void DeleteExporedChallenges() { private void DeleteExporedChallenges() {
var currentDate = DateTime.Now; var currentDate = DateTime.Now;
foreach (var file in Directory.GetFiles(_acmePath)) { foreach (var file in Directory.GetFiles(_acmePath)) {
try { try {
var creationTime = File.GetCreationTime(file); var creationTime = File.GetCreationTime(file);
// Calculate the time difference
var timeDifference = currentDate - creationTime; var timeDifference = currentDate - creationTime;
// If the file is older than 1 day, delete it
if (timeDifference.TotalDays > 1) { if (timeDifference.TotalDays > 1) {
File.Delete(file); File.Delete(file);
_logger.LogInformation($"Deleted file: {file}"); _logger.LogInformation($"Deleted file: {file}");
} }
@ -330,6 +280,5 @@ public class CertsFlowService : ICertsFlowService {
} }
} }
} }
#endregion #endregion
} }