From 7c962a492462b4a8614eb1bcd3dc18f5b02d8cbb Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Tue, 14 Oct 2025 18:43:38 +0200 Subject: [PATCH] (feature): migrate to MaksIT.Results --- src/LetsEncrypt/LetsEncrypt.csproj | 2 +- .../Services/LetsEncryptService.cs | 159 +++++---- .../BackgroundServices/AutoRenewal.cs | 26 +- .../Controllers/AccountController.cs | 2 - .../Controllers/CertsFlowController.cs | 2 - .../Controllers/WellKnownController.cs | 2 - .../LetsEncryptServer.csproj | 2 +- .../Services/AccoutService.cs | 89 ++--- .../Services/AgentService.cs | 24 +- .../Services/CacheService.cs | 56 ++-- .../Services/CertsFlowService.cs | 307 ++++++++---------- 11 files changed, 308 insertions(+), 363 deletions(-) diff --git a/src/LetsEncrypt/LetsEncrypt.csproj b/src/LetsEncrypt/LetsEncrypt.csproj index 194f00c..ffe6384 100644 --- a/src/LetsEncrypt/LetsEncrypt.csproj +++ b/src/LetsEncrypt/LetsEncrypt.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/LetsEncrypt/Services/LetsEncryptService.cs b/src/LetsEncrypt/Services/LetsEncryptService.cs index 1960e7a..daab003 100644 --- a/src/LetsEncrypt/Services/LetsEncryptService.cs +++ b/src/LetsEncrypt/Services/LetsEncryptService.cs @@ -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.LetsEncrypt.Models.Responses; -using MaksIT.LetsEncrypt.Models.Interfaces; -using MaksIT.LetsEncrypt.Models.Requests; +using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Entities.Jws; 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; public interface ILetsEncryptService { - Task ConfigureClient(Guid sessionId, bool isStaging); - Task Init(Guid sessionId,Guid accountId, string description, 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); - Task GetOrder(Guid sessionId, string[] hostnames); - Task GetCertificate(Guid sessionId, string subject); - Task RevokeCertificate(Guid sessionId, string subject, RevokeReason reason); + Task ConfigureClient(Guid sessionId, bool isStaging); + Task Init(Guid sessionId,Guid accountId, string description, string[] contacts, RegistrationCache? registrationCache); + Result GetRegistrationCache(Guid sessionId); + Result GetTermsOfServiceUri(Guid sessionId); + Task?>> NewOrder(Guid sessionId, string[] hostnames, string challengeType); + Task CompleteChallenges(Guid sessionId); + Task GetOrder(Guid sessionId, string[] hostnames); + Task GetCertificate(Guid sessionId, string subject); + Task RevokeCertificate(Guid sessionId, string subject, RevokeReason reason); } public class LetsEncryptService : ILetsEncryptService { @@ -77,8 +75,8 @@ public class LetsEncryptService : ILetsEncryptService { } // Helper: Poll challenge status until valid or timeout - private async Task PollChallengeStatus(Guid sessionId, AuthorizationChallengeChallenge challenge, State state) { - if (challenge?.Url == null) return IDomainResult.Failed("Challenge URL is null"); + private async Task PollChallengeStatus(Guid sessionId, AuthorizationChallengeChallenge challenge, State state) { + if (challenge?.Url == null) return Result.InternalServerError("Challenge URL is null"); var start = DateTime.UtcNow; while (true) { var pollRequest = new HttpRequestMessage(HttpMethod.Post, challenge.Url); @@ -94,15 +92,15 @@ public class LetsEncryptService : ILetsEncryptService { HandleProblemResponseAsync(pollResponse, pollResponseText); var authChallenge = ProcessResponseContent(pollResponse, pollResponseText); 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) - return IDomainResult.Failed("Timeout"); + return Result.InternalServerError("Timeout"); await Task.Delay(1000); } } #region ConfigureClient - public async Task ConfigureClient(Guid sessionId, bool isStaging) { + public async Task ConfigureClient(Guid sessionId, bool isStaging) { try { var state = GetOrCreateState(sessionId); state.IsStaging = isStaging; @@ -113,29 +111,29 @@ public class LetsEncryptService : ILetsEncryptService { var directory = await SendAcmeRequest(request, state, HttpMethod.Get); state.Directory = directory.Result ?? throw new InvalidOperationException("Directory response is null"); } - return IDomainResult.Success(); + return Result.Ok(); } catch (Exception ex) { const string message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return IDomainResult.CriticalDependencyError(message); + return Result.InternalServerError(message); } } #endregion #region Init - public async Task Init(Guid sessionId, Guid accountId, string description, string[] contacts, RegistrationCache? cache) { + public async Task Init(Guid sessionId, Guid accountId, string description, string[] contacts, RegistrationCache? cache) { if (sessionId == Guid.Empty) { _logger.LogError("Invalid sessionId"); - return IDomainResult.Failed(); + return Result.InternalServerError(); } if (contacts == null || contacts.Length == 0) { _logger.LogError("Contacts are null or empty"); - return IDomainResult.Failed(); + return Result.InternalServerError(); } var state = GetOrCreateState(sessionId); if (state.Directory == null) { _logger.LogError("State directory is null"); - return IDomainResult.Failed(); + return Result.InternalServerError(); } _logger.LogInformation($"Executing {nameof(Init)}..."); try { @@ -162,7 +160,7 @@ public class LetsEncryptService : ILetsEncryptService { state.JwsService.SetKeyId(result.Result?.Location?.ToString() ?? string.Empty); if (result.Result?.Status != "valid") { _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 { AccountId = accountId, @@ -175,41 +173,41 @@ public class LetsEncryptService : ILetsEncryptService { Key = result.Result.Key }; } - return IDomainResult.Success(); + return Result.Ok(); } catch (Exception ex) { const string message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return IDomainResult.CriticalDependencyError(message); + return Result.InternalServerError(message); } } #endregion - public (RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId) { + public Result GetRegistrationCache(Guid sessionId) { var state = GetOrCreateState(sessionId); if(state?.Cache == null) - return IDomainResult.Failed(); - return IDomainResult.Success(state.Cache); + return Result.InternalServerError(null); + return Result.Ok(state.Cache); } #region GetTermsOfService - public (string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId) { + public Result GetTermsOfServiceUri(Guid sessionId) { try { var state = GetOrCreateState(sessionId); _logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}..."); if (state.Directory?.Meta?.TermsOfService == null) { - return IDomainResult.Failed(); + return Result.Ok(null); } - return IDomainResult.Success(state.Directory.Meta.TermsOfService); + return Result.Ok(state.Directory.Meta.TermsOfService); } catch (Exception ex) { var message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return IDomainResult.CriticalDependencyError(message); + return Result.InternalServerError(message); } } #endregion #region NewOrder - public async Task<(Dictionary?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType) { + public async Task?>> NewOrder(Guid sessionId, string[] hostnames, string challengeType) { try { var state = GetOrCreateState(sessionId); _logger.LogInformation($"Executing {nameof(NewOrder)}..."); @@ -222,7 +220,7 @@ public class LetsEncryptService : ILetsEncryptService { }).ToArray() ?? Array.Empty() }; if (state.Directory == null || state.Directory.NewOrder == null) - return (null, IDomainResult.Failed()); + return Result?>.InternalServerError(null); var request = new HttpRequestMessage(HttpMethod.Post, state.Directory.NewOrder); await HandleNonceAsync(sessionId, state.Directory.NewOrder, state); var json = EncodeMessage(false, letsEncryptOrder, state, new JwsHeader { @@ -232,10 +230,10 @@ public class LetsEncryptService : ILetsEncryptService { PrepareRequestContent(request, json, HttpMethod.Post); var order = await SendAcmeRequest(request, state, HttpMethod.Post); if (StatusEquals(order.Result?.Status, OrderStatus.Ready)) - return (new Dictionary(), IDomainResult.Success()); + return Result?>.Ok(new Dictionary()); 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}"); - return (null, IDomainResult.Failed()); + return Result?>.InternalServerError(null); } state.CurrentOrder = order.Result; var results = new Dictionary(); @@ -253,12 +251,12 @@ public class LetsEncryptService : ILetsEncryptService { continue; if (!StatusEquals(challengeResponse.Result?.Status, OrderStatus.Pending)) { _logger.LogError($"Expected authorization status '{OrderStatus.Pending.GetDisplayName()}', but got: {state.CurrentOrder?.Status} \r\n {challengeResponse.ResponseText}"); - return (null, IDomainResult.Failed()); + return Result?>.InternalServerError(null); } var challenge = challengeResponse.Result?.Challenges?.FirstOrDefault(x => x?.Type == challengeType); if (challenge == null || challenge.Token == null) { _logger.LogError("Challenge or token is null"); - return (null, IDomainResult.Failed()); + return Result?>.InternalServerError(null); } state.Challenges.Add(challenge); if (state.Cache != null) state.Cache.ChallengeType = challengeType; @@ -277,28 +275,29 @@ public class LetsEncryptService : ILetsEncryptService { throw new NotImplementedException(); } } - return (results, IDomainResult.Success()); + + return Result?>.Ok(results); } catch (Exception ex) { var message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return (null, IDomainResult.CriticalDependencyError(message)); + return Result?>.InternalServerError(null, message); } } #endregion #region CompleteChallenges - public async Task CompleteChallenges(Guid sessionId) { + public async Task CompleteChallenges(Guid sessionId) { try { var state = GetOrCreateState(sessionId); _logger.LogInformation($"Executing {nameof(CompleteChallenges)}..."); 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++) { var challenge = state.Challenges[index]; if (challenge?.Url == null) { _logger.LogError("Challenge URL is null"); - return IDomainResult.Failed(); + return Result.InternalServerError(); } var request = new HttpRequestMessage(HttpMethod.Post, challenge.Url); await HandleNonceAsync(sessionId, challenge.Url, state); @@ -312,17 +311,17 @@ public class LetsEncryptService : ILetsEncryptService { if (!result.IsSuccess) return result; } - return IDomainResult.Success(); + return Result.Ok(); } catch (Exception ex) { var message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return IDomainResult.CriticalDependencyError(message); + return Result.InternalServerError(message); } } #endregion #region GetOrder - public async Task GetOrder(Guid sessionId, string[] hostnames) { + public async Task GetOrder(Guid sessionId, string[] hostnames) { try { _logger.LogInformation($"Executing {nameof(GetOrder)}"); var state = GetOrCreateState(sessionId); @@ -342,22 +341,22 @@ public class LetsEncryptService : ILetsEncryptService { PrepareRequestContent(request, json, HttpMethod.Post); var order = await SendAcmeRequest(request, state, HttpMethod.Post); state.CurrentOrder = order.Result; - return IDomainResult.Success(); + return Result.Ok(); } catch (Exception ex) { var message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return IDomainResult.CriticalDependencyError(message); + return Result.InternalServerError(message); } } #endregion #region GetCertificates - public async Task GetCertificate(Guid sessionId, string subject) { + public async Task GetCertificate(Guid sessionId, string subject) { try { var state = GetOrCreateState(sessionId); _logger.LogInformation($"Executing {nameof(GetCertificate)}..."); if (state.CurrentOrder?.Identifiers == null) { - return IDomainResult.Failed(); + return Result.InternalServerError(); } var key = new RSACryptoServiceProvider(4096); var csr = new CertificateRequest("CN=" + subject, @@ -417,7 +416,7 @@ public class LetsEncryptService : ILetsEncryptService { var pem = await SendAcmeRequest(finalRequest, state, HttpMethod.Post); if (state.Cache == null) { _logger.LogError($"{nameof(state.Cache)} is null"); - return IDomainResult.Failed(); + return Result.InternalServerError(); } state.Cache.CachedCerts ??= new Dictionary(); state.Cache.CachedCerts[subject] = new CertificateCache { @@ -429,31 +428,31 @@ public class LetsEncryptService : ILetsEncryptService { if (!string.IsNullOrEmpty(certPem)) { var cert = new X509Certificate2(Encoding.UTF8.GetBytes(certPem)); } - return IDomainResult.Success(); + return Result.Ok(); } catch (Exception ex) { var message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return IDomainResult.CriticalDependencyError(message); + return Result.InternalServerError(message); } } #endregion - public Task KeyChange(Guid sessionId) { + public Task KeyChange(Guid sessionId) { throw new NotImplementedException(); } - public async Task RevokeCertificate(Guid sessionId, string subject, RevokeReason reason) { + public async Task RevokeCertificate(Guid sessionId, string subject, RevokeReason reason) { try { var state = GetOrCreateState(sessionId); _logger.LogInformation($"Executing {nameof(RevokeCertificate)}..."); if (state.Cache?.CachedCerts == null || !state.Cache.CachedCerts.TryGetValue(subject, out var certificateCache) || certificateCache == null) { _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; if (string.IsNullOrEmpty(certPem)) { _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 derEncodedCert = certificate.Export(X509ContentType.Cert); @@ -478,14 +477,14 @@ public class LetsEncryptService : ILetsEncryptService { var erroObj = responseText.ToObject(); } if (!response.IsSuccessStatusCode) - IDomainResult.CriticalDependencyError(responseText); + Result.InternalServerError(responseText); state.Cache.CachedCerts.Remove(subject); _logger.LogInformation("Certificate revoked successfully"); - return IDomainResult.Success(); + return Result.Ok(); } catch (Exception ex) { var message = "Let's Encrypt client unhandled exception"; _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) { if (uri == null) throw new ArgumentNullException(nameof(uri)); if (uri.OriginalString != "directory") { - var (nonce, newNonceResult) = await NewNonce(sessionId); - if (!newNonceResult.IsSuccess || nonce == null) { + var newNonceResult = await NewNonce(sessionId); + if (!newNonceResult.IsSuccess || newNonceResult.Value == null) { throw new InvalidOperationException("Failed to retrieve nonce."); } - state.Nonce = nonce; + state.Nonce = newNonceResult.Value; } else { state.Nonce = default; } } - private async Task<(string?, IDomainResult)> NewNonce(Guid sessionId) { + private async Task> NewNonce(Guid sessionId) { try { var state = GetOrCreateState(sessionId); _logger.LogInformation($"Executing {nameof(NewNonce)}..."); if (state.Directory?.NewNonce == null) - return (null, IDomainResult.Failed()); + return Result.InternalServerError(null); var result = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, state.Directory.NewNonce)); var nonce = result.Headers.GetValues("Replay-Nonce").FirstOrDefault(); - return (nonce, IDomainResult.Success()); + return Result.Ok(nonce); } catch (Exception ex) { var message = "Let's Encrypt client unhandled exception"; _logger.LogError(ex, message); - return (null, IDomainResult.CriticalDependencyError(message)); + return Result.InternalServerError(null, message); } } diff --git a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs index c8462fe..988db57 100644 --- a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs +++ b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs @@ -1,11 +1,9 @@ using Microsoft.Extensions.Options; -using DomainResults.Common; - using MaksIT.LetsEncryptServer.Services; using MaksIT.LetsEncrypt.Entities; -using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; +using MaksIT.Results; namespace MaksIT.LetsEncryptServer.BackgroundServices { public class AutoRenewal : BackgroundService { @@ -31,12 +29,14 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation("Background service is running."); - var (accountsResponse, getAccountIdsResult) = await _cacheService.LoadAccountsFromCacheAsync(); - if (!getAccountIdsResult.IsSuccess || accountsResponse == null) { - LogErrors(getAccountIdsResult.Errors); + var loadAccountsFromCacheResult = await _cacheService.LoadAccountsFromCacheAsync(); + if (!loadAccountsFromCacheResult.IsSuccess || loadAccountsFromCacheResult.Value == null) { + LogErrors(loadAccountsFromCacheResult.Messages); continue; } + var accountsResponse = loadAccountsFromCacheResult.Value; + foreach (var account in accountsResponse.Where(x => !x.IsDisabled)) { await ProcessAccountAsync(account); } @@ -45,27 +45,27 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { } } - private async Task ProcessAccountAsync(RegistrationCache cache) { + private async Task ProcessAccountAsync(RegistrationCache cache) { var hostnames = cache.GetHostsWithUpcomingSslExpiry(); if (hostnames == null) { _logger.LogError("Unexpected hostnames null"); - return IDomainResult.Success(); + return Result.Ok(); } if (!hostnames.Any()) { _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); - if (!renewResult.IsSuccess) - return renewResult; + var fullFlowResult = await _certsFlowService.FullFlow(cache.IsStaging, cache.AccountId, cache.Description, cache.Contacts, cache.ChallengeType, hostnames); + if (!fullFlowResult.IsSuccess) + return fullFlowResult; _logger.LogInformation($"Certificates renewed for account {cache.AccountId}"); - return IDomainResult.Success(); + return Result.Ok(); } diff --git a/src/LetsEncryptServer/Controllers/AccountController.cs b/src/LetsEncryptServer/Controllers/AccountController.cs index 47863e2..a9a4784 100644 --- a/src/LetsEncryptServer/Controllers/AccountController.cs +++ b/src/LetsEncryptServer/Controllers/AccountController.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using DomainResults.Mvc; - using MaksIT.LetsEncryptServer.Services; using MaksIT.Models.LetsEncryptServer.Account.Requests; diff --git a/src/LetsEncryptServer/Controllers/CertsFlowController.cs b/src/LetsEncryptServer/Controllers/CertsFlowController.cs index 56ef963..d36619e 100644 --- a/src/LetsEncryptServer/Controllers/CertsFlowController.cs +++ b/src/LetsEncryptServer/Controllers/CertsFlowController.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using DomainResults.Mvc; - using MaksIT.LetsEncryptServer.Services; using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; diff --git a/src/LetsEncryptServer/Controllers/WellKnownController.cs b/src/LetsEncryptServer/Controllers/WellKnownController.cs index 000e0ae..407ffb7 100644 --- a/src/LetsEncryptServer/Controllers/WellKnownController.cs +++ b/src/LetsEncryptServer/Controllers/WellKnownController.cs @@ -1,8 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using DomainResults.Mvc; - using MaksIT.LetsEncryptServer.Services; diff --git a/src/LetsEncryptServer/LetsEncryptServer.csproj b/src/LetsEncryptServer/LetsEncryptServer.csproj index 0605ead..737e764 100644 --- a/src/LetsEncryptServer/LetsEncryptServer.csproj +++ b/src/LetsEncryptServer/LetsEncryptServer.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/LetsEncryptServer/Services/AccoutService.cs b/src/LetsEncryptServer/Services/AccoutService.cs index 51597ae..22b6f78 100644 --- a/src/LetsEncryptServer/Services/AccoutService.cs +++ b/src/LetsEncryptServer/Services/AccoutService.cs @@ -1,9 +1,9 @@ -using DomainResults.Common; - + using MaksIT.LetsEncrypt.Entities; using MaksIT.Models; using MaksIT.Models.LetsEncryptServer.Account.Requests; using MaksIT.Models.LetsEncryptServer.Account.Responses; +using MaksIT.Results; namespace MaksIT.LetsEncryptServer.Services; @@ -14,11 +14,11 @@ public interface IAccountInternalService { public interface IAccountRestService { - Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync(); - Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId); - Task<(GetAccountResponse?, IDomainResult)> PostAccountAsync(PostAccountRequest requestData); - Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData); - Task DeleteAccountAsync(Guid accountId); + Task> GetAccountsAsync(); + Task> GetAccountAsync(Guid accountId); + Task> PostAccountAsync(PostAccountRequest requestData); + Task> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData); + Task DeleteAccountAsync(Guid accountId); } public interface IAccountService : IAccountInternalService, IAccountRestService { } @@ -41,34 +41,37 @@ public class AccountService : IAccountService { #region Accounts - public async Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync() { + public async Task> GetAccountsAsync() { - var (caches, result) = await _cacheService.LoadAccountsFromCacheAsync(); - if (!result.IsSuccess || caches == null) { - return (null, result); + var accountsFromCacheResult = await _cacheService.LoadAccountsFromCacheAsync(); + if (!accountsFromCacheResult.IsSuccess || accountsFromCacheResult.Value == null) { + return accountsFromCacheResult + .ToResultOfType(_ => null); } - var accounts = caches + var accounts = accountsFromCacheResult.Value .Select(x => CreateGetAccountResponse(x.AccountId, x)) .ToArray(); - return IDomainResult.Success(accounts); + return Result.Ok(accounts); } - public async Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId) { - var (cache, result) = await _cacheService.LoadAccountFromCacheAsync(accountId); - if (!result.IsSuccess || cache == null) { - return (null, result); + public async Task> GetAccountAsync(Guid accountId) { + var loadFromCacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadFromCacheResult.IsSuccess || loadFromCacheResult.Value == null) { + return loadFromCacheResult.ToResultOfType(_ => null); } - return IDomainResult.Success(CreateGetAccountResponse(accountId, cache)); + var cache = loadFromCacheResult.Value; + + return Result.Ok(CreateGetAccountResponse(accountId, cache)); } - public async Task<(GetAccountResponse?, IDomainResult)> PostAccountAsync(PostAccountRequest requestData) { + public async Task> PostAccountAsync(PostAccountRequest requestData) { // TODO: check for overlapping hostnames in already existing accounts - var (accountId, newCertsResult) = await _certsFlowService.FullFlow( + var fullFlowResult = await _certsFlowService.FullFlow( requestData.IsStaging, null, requestData.Description, @@ -77,25 +80,31 @@ public class AccountService : IAccountService { requestData.Hostnames ); - if (!newCertsResult.IsSuccess || accountId == null) - return (null, newCertsResult); + if (!fullFlowResult.IsSuccess || fullFlowResult.Value == null) + return fullFlowResult.ToResultOfType(_ => null); - var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId.Value); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); + var accountId = fullFlowResult.Value.Value; + + var loadAccauntFromCacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadAccauntFromCacheResult.IsSuccess || loadAccauntFromCacheResult.Value == null) { + return loadAccauntFromCacheResult.ToResultOfType(_ => null); } - return IDomainResult.Success(CreateGetAccountResponse(accountId.Value, cache)); + var cache = loadAccauntFromCacheResult.Value; + + return Result.Ok(CreateGetAccountResponse(accountId, cache)); } - public async Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) { - var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); + public async Task> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) { + var loadAccountResult = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadAccountResult.IsSuccess || loadAccountResult.Value == null) { + return loadAccountResult.ToResultOfType(_ => null); } + var cache = loadAccountResult.Value; + if (requestData.Description != null) { switch (requestData.Description.Op) { case PatchOperation.Replace: @@ -172,11 +181,11 @@ public class AccountService : IAccountService { var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache); if (!saveResult.IsSuccess) { - return (null, saveResult); + return saveResult.ToResultOfType(default); } if (hostnamesToAdd.Count > 0) { - var (_, newCertsResult) = await _certsFlowService.FullFlow( + var fullFlowResult = await _certsFlowService.FullFlow( cache.IsStaging, cache.AccountId, cache.Description, @@ -185,8 +194,8 @@ public class AccountService : IAccountService { hostnamesToAdd.ToArray() ); - if (!newCertsResult.IsSuccess) - return (null, newCertsResult); + if (!fullFlowResult.IsSuccess) + return fullFlowResult.ToResultOfType(_ => null); } if (hostnamesToRemove.Count > 0) { @@ -199,18 +208,18 @@ public class AccountService : IAccountService { ); if (!revokeResult.IsSuccess) - return (null, revokeResult); + return revokeResult.ToResultOfType(default); } - (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); + loadAccountResult = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadAccountResult.IsSuccess || loadAccountResult.Value == null) { + return loadAccountResult.ToResultOfType(_ => null); } - return IDomainResult.Success(CreateGetAccountResponse(accountId, cache)); + return Result.Ok(CreateGetAccountResponse(accountId, cache)); } - public async Task DeleteAccountAsync(Guid accountId) { + public async Task DeleteAccountAsync(Guid accountId) { // TODO: Revoke all certificates // Remove from cache diff --git a/src/LetsEncryptServer/Services/AgentService.cs b/src/LetsEncryptServer/Services/AgentService.cs index 8d98f34..44cc67f 100644 --- a/src/LetsEncryptServer/Services/AgentService.cs +++ b/src/LetsEncryptServer/Services/AgentService.cs @@ -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 System.Text; using System.Text.Json; @@ -7,9 +7,9 @@ using System.Text.Json; namespace MaksIT.LetsEncryptServer.Services { public interface IAgentService { - Task GetHelloWorld(); - Task UploadCerts(Dictionary certs); - Task ReloadService(string serviceName); + Task GetHelloWorld(); + Task UploadCerts(Dictionary certs); + Task ReloadService(string serviceName); } public class AgentService : IAgentService { @@ -28,23 +28,23 @@ namespace MaksIT.LetsEncryptServer.Services { _httpClient = httpClient; } - public Task GetHelloWorld() { + public Task GetHelloWorld() { throw new NotImplementedException(); } - public async Task ReloadService(string serviceName) { + public async Task ReloadService(string serviceName) { var requestBody = new ServiceReloadRequest { ServiceName = serviceName }; var endpoint = $"/Service/Reload"; return await SendHttpRequest(requestBody, endpoint); } - public async Task UploadCerts(Dictionary certs) { + public async Task UploadCerts(Dictionary certs) { var requestBody = new CertsUploadRequest { Certs = certs }; var endpoint = $"/Certs/Upload"; return await SendHttpRequest(requestBody, endpoint); } - private async Task SendHttpRequest(T requestBody, string endpoint) { + private async Task SendHttpRequest(T requestBody, string endpoint) { try { var request = new HttpRequestMessage(HttpMethod.Post, $"{_appSettings.Agent.AgentHostname}:{_appSettings.Agent.AgentPort}{endpoint}") { Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json") @@ -56,16 +56,16 @@ namespace MaksIT.LetsEncryptServer.Services { var response = await _httpClient.SendAsync(request); if (response.IsSuccessStatusCode) { - return IDomainResult.Success(); + return Result.Ok(); } else { _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) { _logger.LogError(ex, "Something went wrong"); - return IDomainResult.Failed("Something went wrong"); + return Result.InternalServerError("Something went wrong"); } } } diff --git a/src/LetsEncryptServer/Services/CacheService.cs b/src/LetsEncryptServer/Services/CacheService.cs index 2981225..8b7733b 100644 --- a/src/LetsEncryptServer/Services/CacheService.cs +++ b/src/LetsEncryptServer/Services/CacheService.cs @@ -1,18 +1,17 @@ using System.Text.Json; -using DomainResults.Common; using MaksIT.Core.Extensions; using MaksIT.LetsEncrypt.Entities; -using MaksIT.Models; +using MaksIT.Results; namespace MaksIT.LetsEncryptServer.Services; public interface ICacheService { - Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync(); - Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId); - Task SaveToCacheAsync(Guid accountId, RegistrationCache cache); - Task DeleteFromCacheAsync(Guid accountId); + Task> LoadAccountsFromCacheAsync(); + Task> LoadAccountFromCacheAsync(Guid accountId); + Task SaveToCacheAsync(Guid accountId, RegistrationCache cache); + Task DeleteFromCacheAsync(Guid accountId); } public class CacheService : ICacheService, IDisposable { @@ -47,63 +46,58 @@ public class CacheService : ICacheService, IDisposable { #region Cache Operations - public async Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync() { + public async Task> LoadAccountsFromCacheAsync() { return await _lockManager.ExecuteWithLockAsync(async () => { var accountIds = GetCachedAccounts(); var cacheLoadTasks = accountIds.Select(accountId => LoadFromCacheInternalAsync(accountId)).ToList(); var caches = new List(); foreach (var task in cacheLoadTasks) { - var (registrationCache, getRegistrationCacheResult) = await task; - if (!getRegistrationCacheResult.IsSuccess || registrationCache == null) { + var taskResult = await task; + if (!taskResult.IsSuccess || taskResult.Value == null) { // 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. continue; } + var registrationCache = taskResult.Value; + caches.Add(registrationCache); } - return IDomainResult.Success(caches.ToArray()); + return Result.Ok(caches.ToArray()); }); } - - - - private async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheInternalAsync(Guid accountId) { + private async Task> LoadFromCacheInternalAsync(Guid accountId) { var cacheFilePath = GetCacheFilePath(accountId); if (!File.Exists(cacheFilePath)) { var message = $"Cache file not found for account {accountId}"; _logger.LogWarning(message); - return IDomainResult.Failed(message); + return Result.InternalServerError(null, 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); + return Result.InternalServerError(null, message); } var cache = JsonSerializer.Deserialize(json); - return IDomainResult.Success(cache); + return Result.Ok(cache); } - - - private async Task SaveToCacheInternalAsync(Guid accountId, RegistrationCache cache) { + private async Task SaveToCacheInternalAsync(Guid accountId, RegistrationCache cache) { var cacheFilePath = GetCacheFilePath(accountId); var json = JsonSerializer.Serialize(cache); await File.WriteAllTextAsync(cacheFilePath, json); _logger.LogInformation($"Cache file saved for account {accountId}"); - return DomainResult.Success(); + return Result.Ok(); } - - - private IDomainResult DeleteFromCacheInternal(Guid accountId) { + private Result DeleteFromCacheInternal(Guid accountId) { var cacheFilePath = GetCacheFilePath(accountId); if (File.Exists(cacheFilePath)) { File.Delete(cacheFilePath); @@ -112,22 +106,22 @@ public class CacheService : ICacheService, IDisposable { else { _logger.LogWarning($"Cache file not found for account {accountId}"); } - return DomainResult.Success(); + return Result.Ok(); } #endregion + public async Task> LoadAccountFromCacheAsync(Guid accountId) { + return await _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId)); - public Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId) { - return _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId)); } - public Task SaveToCacheAsync(Guid accountId, RegistrationCache cache) { - return _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache)); + public async Task SaveToCacheAsync(Guid accountId, RegistrationCache cache) { + return await _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache)); } - public Task DeleteFromCacheAsync(Guid accountId) { - return _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId)); + public async Task DeleteFromCacheAsync(Guid accountId) { + return await _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId)); } public void Dispose() { diff --git a/src/LetsEncryptServer/Services/CertsFlowService.cs b/src/LetsEncryptServer/Services/CertsFlowService.cs index 41ca61f..4fa27e7 100644 --- a/src/LetsEncryptServer/Services/CertsFlowService.cs +++ b/src/LetsEncryptServer/Services/CertsFlowService.cs @@ -1,47 +1,42 @@ using Microsoft.Extensions.Options; - -using DomainResults.Common; - using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Services; using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; -using System.Security.Cryptography; using MaksIT.LetsEncrypt.Entities.LetsEncrypt; +using MaksIT.Results; namespace MaksIT.LetsEncryptServer.Services; public interface ICertsCommonService { - - (string?, IDomainResult) GetTermsOfService(Guid sessionId); - Task CompleteChallengesAsync(Guid sessionId); + Result GetTermsOfService(Guid sessionId); + Task CompleteChallengesAsync(Guid sessionId); } public interface ICertsInternalService : ICertsCommonService { - Task<(Guid?, IDomainResult)> ConfigureClientAsync(bool isStaging); - Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts); - Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType); - Task GetOrderAsync(Guid sessionId, string[] hostnames); - Task GetCertificatesAsync(Guid sessionId, string[] hostnames); - Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames); - Task RevokeCertificatesAsync(Guid sessionId, string[] hostnames); - Task<(Guid?, IDomainResult)> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames); - Task FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames); - + Task> ConfigureClientAsync(bool isStaging); + Task> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts); + Task?>> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType); + Task GetOrderAsync(Guid sessionId, string[] hostnames); + Task GetCertificatesAsync(Guid sessionId, string[] hostnames); + Task?>> ApplyCertificatesAsync(Guid sessionId, string[] hostnames); + Task RevokeCertificatesAsync(Guid sessionId, string[] hostnames); + Task> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames); + Task FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames); } public interface ICertsRestService : ICertsCommonService { - Task<(Guid?, IDomainResult)> ConfigureClientAsync(ConfigureClientRequest requestData); - Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData); - Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData); - Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData); - Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); - Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); - Task RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData); + Task> ConfigureClientAsync(ConfigureClientRequest requestData); + Task> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData); + Task?>> NewOrderAsync(Guid sessionId, NewOrderRequest requestData); + Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData); + Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); + Task?>> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); + Task RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData); } public interface ICertsRestChallengeService { - (string?, IDomainResult) AcmeChallenge(string fileName); + Result AcmeChallenge(string fileName); } public interface ICertsFlowService @@ -50,13 +45,11 @@ public interface ICertsFlowService ICertsRestChallengeService { } public class CertsFlowService : ICertsFlowService { - private readonly Configuration _appSettings; private readonly ILogger _logger; private readonly ILetsEncryptService _letsEncryptService; private readonly ICacheService _cacheService; private readonly IAgentService _agentService; - private readonly string _acmePath; public CertsFlowService( @@ -71,256 +64,213 @@ public class CertsFlowService : ICertsFlowService { _letsEncryptService = letsEncryptService; _cacheService = cashService; _agentService = agentService; - _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme"); if (!Directory.Exists(_acmePath)) Directory.CreateDirectory(_acmePath); } #region Common methods - - - - public (string?, IDomainResult) GetTermsOfService(Guid sessionId) { - var (terms, getTermsResult) = _letsEncryptService.GetTermsOfServiceUri(sessionId); - if (!getTermsResult.IsSuccess || terms == null) - return (null, getTermsResult); - - return IDomainResult.Success(terms); + public Result GetTermsOfService(Guid sessionId) { + var result = _letsEncryptService.GetTermsOfServiceUri(sessionId); + return result; } - public async Task CompleteChallengesAsync(Guid sessionId) { + public async Task CompleteChallengesAsync(Guid sessionId) { return await _letsEncryptService.CompleteChallenges(sessionId); } - #endregion #region Internal methods - public async Task<(Guid?, IDomainResult)> ConfigureClientAsync(bool isStaging) { + public async Task> ConfigureClientAsync(bool isStaging) { var sessionId = Guid.NewGuid(); - var result = await _letsEncryptService.ConfigureClient(sessionId, isStaging); if (!result.IsSuccess) - return (null, result); - - return IDomainResult.Success(sessionId); + return result.ToResultOfType(default); + return Result.Ok(sessionId); } - public async Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts) { + public async Task> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts) { RegistrationCache? cache = null; - if (accountId == null) { accountId = Guid.NewGuid(); - } - else { - var (loadedCache, loadCaceResutl) = await _cacheService.LoadAccountFromCacheAsync(accountId.Value); - if (!loadCaceResutl.IsSuccess || loadCaceResutl == null) { + } else { + var cacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId.Value); + if (!cacheResult.IsSuccess || cacheResult.Value == null) { accountId = Guid.NewGuid(); - } - else { - cache = loadedCache; + } else { + cache = cacheResult.Value; } } - var result = await _letsEncryptService.Init(sessionId, accountId.Value, description, contacts, cache); - return result.IsSuccess ? IDomainResult.Success(accountId.Value) : (null, result); + if (!result.IsSuccess) + return result.ToResultOfType(default); + return Result.Ok(accountId.Value); } - public async Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType) { - var (results, newOrderResult) = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType); - if (!newOrderResult.IsSuccess || results == null) - return (null, newOrderResult); - + public async Task?>> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType) { + var orderResult = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType); + if (!orderResult.IsSuccess || orderResult.Value == null) + return orderResult.ToResultOfType?>(_ => null); var challenges = new List(); - foreach (var result in results) { - string[] splitToken = result.Value.Split('.'); - File.WriteAllText(Path.Combine(_acmePath, splitToken[0]), result.Value); + foreach (var kvp in orderResult.Value) { + string[] splitToken = kvp.Value.Split('.'); + File.WriteAllText(Path.Combine(_acmePath, splitToken[0]), kvp.Value); challenges.Add(splitToken[0]); } - - return IDomainResult.Success(challenges); + return Result?>.Ok(challenges); } - public async Task GetCertificatesAsync(Guid sessionId, string[] hostnames) { + public async Task GetCertificatesAsync(Guid sessionId, string[] hostnames) { foreach (var subject in hostnames) { var result = await _letsEncryptService.GetCertificate(sessionId, subject); if (!result.IsSuccess) return result; - Thread.Sleep(1000); } - - // TODO: Move to separate method - // Persist the cache - var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId); - if (!getCacheResult.IsSuccess || cache == null) - return getCacheResult; - - var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache); + var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId); + if (!cacheResult.IsSuccess || cacheResult.Value == null) + return cacheResult; + var saveResult = await _cacheService.SaveToCacheAsync(cacheResult.Value.AccountId, cacheResult.Value); if (!saveResult.IsSuccess) return saveResult; - - return IDomainResult.Success(); + return Result.Ok(); } - public async Task GetOrderAsync(Guid sessionId, string[] hostnames) { + public async Task GetOrderAsync(Guid sessionId, string[] hostnames) { return await _letsEncryptService.GetOrder(sessionId, hostnames); } - public async Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) { - - var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId); - if (!getCacheResult.IsSuccess || cache?.CachedCerts == null) - return (null, getCacheResult); - - + public async Task?>> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) { + var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId); + if (!cacheResult.IsSuccess || cacheResult.Value?.CachedCerts == null) + return cacheResult.ToResultOfType?>(_ => null); var results = new Dictionary(); foreach (var hostname in hostnames) { CertificateCache? cert; - if (cache.TryGetCachedCertificate(hostname, out cert)) { + if (cacheResult.Value.TryGetCachedCertificate(hostname, out cert)) { var content = $"{cert.Cert}\n{cert.PrivatePem}"; results.Add(hostname, content); } } - - // Send the certificates to the via agent var uploadResult = await _agentService.UploadCerts(results); if (!uploadResult.IsSuccess) - return (null, uploadResult); - + return uploadResult.ToResultOfType?>(default); var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload); if (!reloadResult.IsSuccess) - return (null, reloadResult); - - return IDomainResult.Success(results); + return reloadResult.ToResultOfType?>(default); + return Result?>.Ok(results); } - public async Task RevokeCertificatesAsync(Guid sessionId, string[] hostnames) { + public async Task RevokeCertificatesAsync(Guid sessionId, string[] hostnames) { foreach (var hostname in hostnames) { var result = await _letsEncryptService.RevokeCertificate(sessionId, hostname, RevokeReason.Unspecified); if (!result.IsSuccess) return result; } - - // TODO: Move to separate method - // Persist the cache - var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId); - if (!getCacheResult.IsSuccess || cache == null) - return getCacheResult; - - var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache); + var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId); + if (!cacheResult.IsSuccess || cacheResult.Value == null) + return cacheResult; + var saveResult = await _cacheService.SaveToCacheAsync(cacheResult.Value.AccountId, cacheResult.Value); if (!saveResult.IsSuccess) return saveResult; - - return IDomainResult.Success(); + return Result.Ok(); } - public async Task<(Guid?, IDomainResult)> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[]hostnames) { - var (sessionId, configureClientResult) = await ConfigureClientAsync(isStaging); - if (!configureClientResult.IsSuccess || sessionId == null) - return (null, configureClientResult); - - (accountId, var initResult) = await InitAsync(sessionId.Value, accountId, description, contacts); - if (!initResult.IsSuccess) - return (null, initResult); - - var (challenges, newOrderResult) = await NewOrderAsync(sessionId.Value, hostnames, challengeType); - if (!newOrderResult.IsSuccess) - return (null, newOrderResult); + public async Task> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames) { + var sessionResult = await ConfigureClientAsync(isStaging); + if (!sessionResult.IsSuccess || sessionResult.Value == null) + return sessionResult; - if (challenges?.Count > 0) { - var challengeResult = await CompleteChallengesAsync(sessionId.Value); + var sessionId = sessionResult.Value.Value; + + var initResult = await InitAsync(sessionId, accountId, description, contacts); + if (!initResult.IsSuccess) + return initResult.ToResultOfType(_ => null); + + var challengesResult = await NewOrderAsync(sessionId, hostnames, challengeType); + if (!challengesResult.IsSuccess) + return challengesResult.ToResultOfType(_ => null); + + if (challengesResult.Value?.Count > 0) { + var challengeResult = await CompleteChallengesAsync(sessionId); if (!challengeResult.IsSuccess) - return (null, challengeResult); + return challengeResult.ToResultOfType(default); + } + var getOrderResult = await GetOrderAsync(sessionId, hostnames); + if (!getOrderResult.IsSuccess) + return getOrderResult.ToResultOfType(default); + + var certsResult = await GetCertificatesAsync(sessionId, hostnames); + if (!certsResult.IsSuccess) + return certsResult.ToResultOfType(default); + + // Bypass applying certificates in staging mode + if (!isStaging) { + var applyCertsResult = await ApplyCertificatesAsync(sessionId, hostnames); + if (!applyCertsResult.IsSuccess) + return applyCertsResult.ToResultOfType(_ => null); } - - 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); + return Result.Ok(initResult.Value); } - public async Task FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames) { - var (sessionId, configureClientResult) = await ConfigureClientAsync(isStaging); - if (!configureClientResult.IsSuccess || sessionId == null) - return configureClientResult; + public async Task FullRevocationFlow(bool isStaging, Guid accountId, string description, string[] contacts, string[] hostnames) { + var sessionResult = await ConfigureClientAsync(isStaging); + if (!sessionResult.IsSuccess || sessionResult.Value == null) + 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) return initResult; - - var revokeResult = await RevokeCertificatesAsync(sessionId.Value, hostnames); + var revokeResult = await RevokeCertificatesAsync(sessionId, hostnames); if (!revokeResult.IsSuccess) return revokeResult; - - return IDomainResult.Success(); + return Result.Ok(); } - - #endregion #region REST methods + public async Task> ConfigureClientAsync(ConfigureClientRequest requestData) { + return await ConfigureClientAsync(requestData.IsStaging); + } + public async Task> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) { + return await InitAsync(sessionId, accountId, requestData.Description, requestData.Contacts); + } + public async Task>> NewOrderAsync(Guid sessionId, NewOrderRequest requestData) { + return await NewOrderAsync(sessionId, requestData.Hostnames, requestData.ChallengeType); + } + public async Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) { + return await GetCertificatesAsync(sessionId, requestData.Hostnames); + } + public async Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData) { + return await GetOrderAsync(sessionId, requestData.Hostnames); + } - public Task<(Guid?, IDomainResult)> ConfigureClientAsync(ConfigureClientRequest requestData) => - ConfigureClientAsync(requestData.IsStaging); - - public Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) => - InitAsync(sessionId, accountId, requestData.Description, requestData.Contacts); - - public Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData) => - NewOrderAsync(sessionId, requestData.Hostnames, requestData.ChallengeType); - - public Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) => - GetCertificatesAsync(sessionId, requestData.Hostnames); - - public Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData) => - GetOrderAsync(sessionId, requestData.Hostnames); - - public Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) => - ApplyCertificatesAsync(sessionId, requestData.Hostnames); - - public Task RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData) => - RevokeCertificatesAsync(sessionId, requestData.Hostnames); - + public async Task>> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) => + await ApplyCertificatesAsync(sessionId, requestData.Hostnames); + public async Task RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData) => + await RevokeCertificatesAsync(sessionId, requestData.Hostnames); #endregion #region Acme Challenge REST methods - - public (string?, IDomainResult) AcmeChallenge(string fileName) { + public Result AcmeChallenge(string fileName) { DeleteExporedChallenges(); - var challengePath = Path.Combine(_acmePath, fileName); if(!File.Exists(challengePath)) - return IDomainResult.NotFound(); - + return Result.NotFound(null); var fileContent = File.ReadAllText(Path.Combine(_acmePath, fileName)); - return IDomainResult.Success(fileContent); + return Result.Ok(fileContent); } private void DeleteExporedChallenges() { var currentDate = DateTime.Now; - foreach (var file in Directory.GetFiles(_acmePath)) { try { var creationTime = File.GetCreationTime(file); - - // Calculate the time difference var timeDifference = currentDate - creationTime; - - // If the file is older than 1 day, delete it if (timeDifference.TotalDays > 1) { - - File.Delete(file); _logger.LogInformation($"Deleted file: {file}"); } @@ -330,6 +280,5 @@ public class CertsFlowService : ICertsFlowService { } } } - #endregion }