mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): migrate to MaksIT.Results
This commit is contained in:
parent
30f5ededa3
commit
7c962a4924
@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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.DependencyInjection.Abstractions" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />
|
||||
|
||||
@ -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<IDomainResult> ConfigureClient(Guid sessionId, bool isStaging);
|
||||
Task<IDomainResult> Init(Guid sessionId,Guid accountId, string description, string[] contacts, RegistrationCache? registrationCache);
|
||||
(RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId);
|
||||
(string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId);
|
||||
Task<(Dictionary<string, string>?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType);
|
||||
Task<IDomainResult> CompleteChallenges(Guid sessionId);
|
||||
Task<IDomainResult> GetOrder(Guid sessionId, string[] hostnames);
|
||||
Task<IDomainResult> GetCertificate(Guid sessionId, string subject);
|
||||
Task<IDomainResult> RevokeCertificate(Guid sessionId, string subject, RevokeReason reason);
|
||||
Task<Result> ConfigureClient(Guid sessionId, bool isStaging);
|
||||
Task<Result> Init(Guid sessionId,Guid accountId, string description, string[] contacts, RegistrationCache? registrationCache);
|
||||
Result<RegistrationCache?> GetRegistrationCache(Guid sessionId);
|
||||
Result<string?> GetTermsOfServiceUri(Guid sessionId);
|
||||
Task<Result<Dictionary<string, string>?>> NewOrder(Guid sessionId, string[] hostnames, string challengeType);
|
||||
Task<Result> CompleteChallenges(Guid sessionId);
|
||||
Task<Result> GetOrder(Guid sessionId, string[] hostnames);
|
||||
Task<Result> GetCertificate(Guid sessionId, string subject);
|
||||
Task<Result> 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<IDomainResult> PollChallengeStatus(Guid sessionId, AuthorizationChallengeChallenge challenge, State state) {
|
||||
if (challenge?.Url == null) return IDomainResult.Failed("Challenge URL is null");
|
||||
private async Task<Result> 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<AuthorizationChallengeResponse>(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<IDomainResult> ConfigureClient(Guid sessionId, bool isStaging) {
|
||||
public async Task<Result> 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<AcmeDirectory>(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<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) {
|
||||
_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<RegistrationCache?> GetRegistrationCache(Guid sessionId) {
|
||||
var state = GetOrCreateState(sessionId);
|
||||
if(state?.Cache == null)
|
||||
return IDomainResult.Failed<RegistrationCache?>();
|
||||
return IDomainResult.Success(state.Cache);
|
||||
return Result<RegistrationCache?>.InternalServerError(null);
|
||||
return Result<RegistrationCache?>.Ok(state.Cache);
|
||||
}
|
||||
|
||||
#region GetTermsOfService
|
||||
public (string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId) {
|
||||
public Result<string?> GetTermsOfServiceUri(Guid sessionId) {
|
||||
try {
|
||||
var state = GetOrCreateState(sessionId);
|
||||
_logger.LogInformation($"Executing {nameof(GetTermsOfServiceUri)}...");
|
||||
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) {
|
||||
var message = "Let's Encrypt client unhandled exception";
|
||||
_logger.LogError(ex, message);
|
||||
return IDomainResult.CriticalDependencyError<string?>(message);
|
||||
return Result<string?>.InternalServerError(message);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#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 {
|
||||
var state = GetOrCreateState(sessionId);
|
||||
_logger.LogInformation($"Executing {nameof(NewOrder)}...");
|
||||
@ -222,7 +220,7 @@ public class LetsEncryptService : ILetsEncryptService {
|
||||
}).ToArray() ?? Array.Empty<OrderIdentifier>()
|
||||
};
|
||||
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);
|
||||
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<Order>(request, state, HttpMethod.Post);
|
||||
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)) {
|
||||
_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;
|
||||
var results = new Dictionary<string, string>();
|
||||
@ -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<Dictionary<string, string>?>.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<Dictionary<string, string>?>.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<Dictionary<string, string>?>.Ok(results);
|
||||
} catch (Exception ex) {
|
||||
var message = "Let's Encrypt client unhandled exception";
|
||||
_logger.LogError(ex, message);
|
||||
return (null, IDomainResult.CriticalDependencyError(message));
|
||||
return Result<Dictionary<string, string>?>.InternalServerError(null, message);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CompleteChallenges
|
||||
public async Task<IDomainResult> CompleteChallenges(Guid sessionId) {
|
||||
public async Task<Result> 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<IDomainResult> GetOrder(Guid sessionId, string[] hostnames) {
|
||||
public async Task<Result> 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<Order>(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<IDomainResult> GetCertificate(Guid sessionId, string subject) {
|
||||
public async Task<Result> 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<string>(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<string, CertificateCache>();
|
||||
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<IDomainResult> KeyChange(Guid sessionId) {
|
||||
public Task<Result> KeyChange(Guid sessionId) {
|
||||
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 {
|
||||
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<Problem>();
|
||||
}
|
||||
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<Result<string?>> NewNonce(Guid sessionId) {
|
||||
try {
|
||||
var state = GetOrCreateState(sessionId);
|
||||
_logger.LogInformation($"Executing {nameof(NewNonce)}...");
|
||||
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 nonce = result.Headers.GetValues("Replay-Nonce").FirstOrDefault();
|
||||
return (nonce, IDomainResult.Success());
|
||||
return Result<string?>.Ok(nonce);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Let's Encrypt client unhandled exception";
|
||||
_logger.LogError(ex, message);
|
||||
return (null, IDomainResult.CriticalDependencyError(message));
|
||||
return Result<string?>.InternalServerError(null, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<IDomainResult> ProcessAccountAsync(RegistrationCache cache) {
|
||||
private async Task<Result> 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using DomainResults.Mvc;
|
||||
|
||||
using MaksIT.LetsEncryptServer.Services;
|
||||
using MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using DomainResults.Mvc;
|
||||
|
||||
using MaksIT.LetsEncryptServer.Services;
|
||||
using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests;
|
||||
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using DomainResults.Mvc;
|
||||
|
||||
using MaksIT.LetsEncryptServer.Services;
|
||||
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
|
||||
|
||||
@ -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<IDomainResult> DeleteAccountAsync(Guid accountId);
|
||||
Task<Result<GetAccountResponse[]?>> GetAccountsAsync();
|
||||
Task<Result<GetAccountResponse?>> GetAccountAsync(Guid accountId);
|
||||
Task<Result<GetAccountResponse?>> PostAccountAsync(PostAccountRequest requestData);
|
||||
Task<Result<GetAccountResponse?>> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData);
|
||||
Task<Result> 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<Result<GetAccountResponse[]?>> 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<GetAccountResponse[]?>(_ => null);
|
||||
}
|
||||
|
||||
var accounts = caches
|
||||
var accounts = accountsFromCacheResult.Value
|
||||
.Select(x => CreateGetAccountResponse(x.AccountId, x))
|
||||
.ToArray();
|
||||
|
||||
return IDomainResult.Success(accounts);
|
||||
return Result<GetAccountResponse[]?>.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<Result<GetAccountResponse?>> GetAccountAsync(Guid accountId) {
|
||||
var loadFromCacheResult = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadFromCacheResult.IsSuccess || loadFromCacheResult.Value == null) {
|
||||
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
|
||||
|
||||
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<GetAccountResponse?>(_ => 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<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) {
|
||||
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadResult.IsSuccess || cache == null) {
|
||||
return (null, loadResult);
|
||||
public async Task<Result<GetAccountResponse?>> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) {
|
||||
var loadAccountResult = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
||||
if (!loadAccountResult.IsSuccess || loadAccountResult.Value == null) {
|
||||
return loadAccountResult.ToResultOfType<GetAccountResponse?>(_ => 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<GetAccountResponse?>(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<GetAccountResponse?>(_ => null);
|
||||
}
|
||||
|
||||
if (hostnamesToRemove.Count > 0) {
|
||||
@ -199,18 +208,18 @@ public class AccountService : IAccountService {
|
||||
);
|
||||
|
||||
if (!revokeResult.IsSuccess)
|
||||
return (null, revokeResult);
|
||||
return revokeResult.ToResultOfType<GetAccountResponse?>(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<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
|
||||
|
||||
// Remove from cache
|
||||
|
||||
@ -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<IDomainResult> GetHelloWorld();
|
||||
Task<IDomainResult> UploadCerts(Dictionary<string, string> certs);
|
||||
Task<IDomainResult> ReloadService(string serviceName);
|
||||
Task<Result> GetHelloWorld();
|
||||
Task<Result> UploadCerts(Dictionary<string, string> certs);
|
||||
Task<Result> ReloadService(string serviceName);
|
||||
}
|
||||
|
||||
public class AgentService : IAgentService {
|
||||
@ -28,23 +28,23 @@ namespace MaksIT.LetsEncryptServer.Services {
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public Task<IDomainResult> GetHelloWorld() {
|
||||
public Task<Result> GetHelloWorld() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> ReloadService(string serviceName) {
|
||||
public async Task<Result> ReloadService(string serviceName) {
|
||||
var requestBody = new ServiceReloadRequest { ServiceName = serviceName };
|
||||
var endpoint = $"/Service/Reload";
|
||||
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 endpoint = $"/Certs/Upload";
|
||||
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 {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache);
|
||||
Task<IDomainResult> DeleteFromCacheAsync(Guid accountId);
|
||||
Task<Result<RegistrationCache[]?>> LoadAccountsFromCacheAsync();
|
||||
Task<Result<RegistrationCache?>> LoadAccountFromCacheAsync(Guid accountId);
|
||||
Task<Result> SaveToCacheAsync(Guid accountId, RegistrationCache cache);
|
||||
Task<Result> 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<Result<RegistrationCache[]?>> LoadAccountsFromCacheAsync() {
|
||||
return await _lockManager.ExecuteWithLockAsync(async () => {
|
||||
var accountIds = GetCachedAccounts();
|
||||
var cacheLoadTasks = accountIds.Select(accountId => LoadFromCacheInternalAsync(accountId)).ToList();
|
||||
|
||||
var caches = new List<RegistrationCache>();
|
||||
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<RegistrationCache[]?>.Ok(caches.ToArray());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheInternalAsync(Guid accountId) {
|
||||
private async Task<Result<RegistrationCache?>> 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<RegistrationCache>(message);
|
||||
return Result<RegistrationCache?>.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<RegistrationCache>(message);
|
||||
return Result<RegistrationCache?>.InternalServerError(null, message);
|
||||
}
|
||||
|
||||
var cache = JsonSerializer.Deserialize<RegistrationCache>(json);
|
||||
return IDomainResult.Success(cache);
|
||||
return Result<RegistrationCache?>.Ok(cache);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task<IDomainResult> SaveToCacheInternalAsync(Guid accountId, RegistrationCache cache) {
|
||||
private async Task<Result> 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<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) {
|
||||
return _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache));
|
||||
public async Task<Result> SaveToCacheAsync(Guid accountId, RegistrationCache cache) {
|
||||
return await _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache));
|
||||
}
|
||||
|
||||
public Task<IDomainResult> DeleteFromCacheAsync(Guid accountId) {
|
||||
return _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId));
|
||||
public async Task<Result> DeleteFromCacheAsync(Guid accountId) {
|
||||
return await _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId));
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
@ -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<IDomainResult> CompleteChallengesAsync(Guid sessionId);
|
||||
Result<string?> GetTermsOfService(Guid sessionId);
|
||||
Task<Result> 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<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType);
|
||||
Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames);
|
||||
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<(Guid?, IDomainResult)> 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<Guid?>> ConfigureClientAsync(bool isStaging);
|
||||
Task<Result<Guid?>> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts);
|
||||
Task<Result<List<string>?>> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType);
|
||||
Task<Result> GetOrderAsync(Guid sessionId, string[] hostnames);
|
||||
Task<Result> GetCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<Result<Dictionary<string, string>?>> ApplyCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<Result> RevokeCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||
Task<Result<Guid?>> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames);
|
||||
Task<Result> 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<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData);
|
||||
Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData);
|
||||
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
|
||||
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
|
||||
Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData);
|
||||
Task<Result<Guid?>> ConfigureClientAsync(ConfigureClientRequest requestData);
|
||||
Task<Result<Guid?>> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData);
|
||||
Task<Result<List<string>?>> NewOrderAsync(Guid sessionId, NewOrderRequest requestData);
|
||||
Task<Result> GetOrderAsync(Guid sessionId, GetOrderRequest requestData);
|
||||
Task<Result> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
|
||||
Task<Result<Dictionary<string, string>?>> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
|
||||
Task<Result> RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData);
|
||||
}
|
||||
|
||||
public interface ICertsRestChallengeService {
|
||||
(string?, IDomainResult) AcmeChallenge(string fileName);
|
||||
Result<string?> 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<CertsFlowService> _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<string>(terms);
|
||||
public Result<string?> GetTermsOfService(Guid sessionId) {
|
||||
var result = _letsEncryptService.GetTermsOfServiceUri(sessionId);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> CompleteChallengesAsync(Guid sessionId) {
|
||||
public async Task<Result> CompleteChallengesAsync(Guid sessionId) {
|
||||
return await _letsEncryptService.CompleteChallenges(sessionId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal methods
|
||||
public async Task<(Guid?, IDomainResult)> ConfigureClientAsync(bool isStaging) {
|
||||
public async Task<Result<Guid?>> 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<Guid?>(default);
|
||||
return Result<Guid?>.Ok(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;
|
||||
|
||||
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<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) {
|
||||
var (results, newOrderResult) = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType);
|
||||
if (!newOrderResult.IsSuccess || results == null)
|
||||
return (null, newOrderResult);
|
||||
|
||||
public async Task<Result<List<string>?>> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType) {
|
||||
var orderResult = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType);
|
||||
if (!orderResult.IsSuccess || orderResult.Value == null)
|
||||
return orderResult.ToResultOfType<List<string>?>(_ => null);
|
||||
var challenges = new List<string>();
|
||||
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<List<string>?>.Ok(challenges);
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames) {
|
||||
public async Task<Result> 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<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames) {
|
||||
public async Task<Result> GetOrderAsync(Guid sessionId, string[] hostnames) {
|
||||
return await _letsEncryptService.GetOrder(sessionId, hostnames);
|
||||
}
|
||||
|
||||
public async Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) {
|
||||
|
||||
var (cache, getCacheResult) = _letsEncryptService.GetRegistrationCache(sessionId);
|
||||
if (!getCacheResult.IsSuccess || cache?.CachedCerts == null)
|
||||
return (null, getCacheResult);
|
||||
|
||||
|
||||
public async Task<Result<Dictionary<string, string>?>> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) {
|
||||
var cacheResult = _letsEncryptService.GetRegistrationCache(sessionId);
|
||||
if (!cacheResult.IsSuccess || cacheResult.Value?.CachedCerts == null)
|
||||
return cacheResult.ToResultOfType<Dictionary<string, string>?>(_ => null);
|
||||
var results = new Dictionary<string, string>();
|
||||
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<Dictionary<string, string>?>(default);
|
||||
var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload);
|
||||
if (!reloadResult.IsSuccess)
|
||||
return (null, reloadResult);
|
||||
|
||||
return IDomainResult.Success(results);
|
||||
return reloadResult.ToResultOfType<Dictionary<string, string>?>(default);
|
||||
return Result<Dictionary<string, string>?>.Ok(results);
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> RevokeCertificatesAsync(Guid sessionId, string[] hostnames) {
|
||||
public async Task<Result> 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<Result<Guid?>> 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<Guid?>(_ => null);
|
||||
|
||||
var challengesResult = await NewOrderAsync(sessionId, hostnames, challengeType);
|
||||
if (!challengesResult.IsSuccess)
|
||||
return challengesResult.ToResultOfType<Guid?>(_ => null);
|
||||
|
||||
if (challengesResult.Value?.Count > 0) {
|
||||
var challengeResult = await CompleteChallengesAsync(sessionId);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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<Guid?>.Ok(initResult.Value);
|
||||
}
|
||||
|
||||
public async Task<IDomainResult> 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<Result> 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<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) =>
|
||||
ConfigureClientAsync(requestData.IsStaging);
|
||||
|
||||
public Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) =>
|
||||
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);
|
||||
|
||||
public async Task<Result<Dictionary<string, string>>> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) =>
|
||||
await ApplyCertificatesAsync(sessionId, requestData.Hostnames);
|
||||
public async Task<Result> RevokeCertificatesAsync(Guid sessionId, RevokeCertificatesRequest requestData) =>
|
||||
await RevokeCertificatesAsync(sessionId, requestData.Hostnames);
|
||||
#endregion
|
||||
|
||||
#region Acme Challenge REST methods
|
||||
|
||||
public (string?, IDomainResult) AcmeChallenge(string fileName) {
|
||||
public Result<string?> AcmeChallenge(string fileName) {
|
||||
DeleteExporedChallenges();
|
||||
|
||||
var challengePath = Path.Combine(_acmePath, fileName);
|
||||
if(!File.Exists(challengePath))
|
||||
return IDomainResult.NotFound<string?>();
|
||||
|
||||
return Result<string?>.NotFound(null);
|
||||
var fileContent = File.ReadAllText(Path.Combine(_acmePath, fileName));
|
||||
return IDomainResult.Success(fileContent);
|
||||
return Result<string?>.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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user