From 4000026b7a5cc1be5bda3a124f101ff9e7712314 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 23 Jun 2024 14:22:46 +0200 Subject: [PATCH] (refactor): webapi cache controller refactoring --- src/ClientApp/.vscode/settings.json | 2 +- src/ClientApp/ApiRoutes.tsx | 10 +- src/ClientApp/app/page.tsx | 28 +- src/ClientApp/entities/CacheAccount.ts | 1 + .../requests/PatchAccountRequest.ts | 0 .../requests/PatchContactsRequest.ts | 0 .../requests/PostContactsRequest.ts | 0 .../requests/PutAccountRequest.ts | 0 .../requests/PutContactsRequest.ts | 0 .../responses/GetAccountResponse.ts | 4 +- .../responses/GetContactsResponse.ts | 0 .../responses/GetHostnamesResponse.ts | 0 .../responses/HostnameResponse.ts | 0 .../Entities/LetsEncrypt/RegistrationCache.cs | 5 +- .../Services/LetsEncryptService.cs | 9 +- .../BackgroundServices/AutoRenewal.cs | 8 +- .../Controllers/AccountController.cs | 131 ++++--- .../Controllers/AccountsController.cs | 29 -- src/LetsEncryptServer/Program.cs | 3 +- .../Services/AccoutService.cs | 337 ++++++++++++++++++ .../Services/CacheService.cs | 267 +------------- .../Requests/PatchAccountRequest.cs | 2 +- .../Requests/PatchContactsRequest - Copy.cs} | 2 +- .../Account/Requests/PatchHostnamesRequest.cs | 12 + .../Requests/PostAccountRequest.cs | 7 +- .../Requests/PostContactsRequest.cs | 2 +- .../Account/Requests/PostHostnamesRequest.cs | 17 + .../Requests/PutAccountRequest.cs | 2 +- .../Requests/PutContactsRequest.cs | 2 +- .../Account/Requests/PutHostnamesRequest.cs | 17 + .../Responses/GetAccountResponse.cs | 6 +- .../Responses/GetContactsResponse.cs | 2 +- .../Responses/GetHostnamesResponse.cs | 2 +- .../Responses/HostnameResponse.cs | 2 +- .../b2a279ae-0306-4d77-9408-66a078f34fa6.json | 2 +- 35 files changed, 542 insertions(+), 369 deletions(-) rename src/ClientApp/models/letsEncryptServer/{cache => account}/requests/PatchAccountRequest.ts (100%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/requests/PatchContactsRequest.ts (100%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/requests/PostContactsRequest.ts (100%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/requests/PutAccountRequest.ts (100%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/requests/PutContactsRequest.ts (100%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/responses/GetAccountResponse.ts (62%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/responses/GetContactsResponse.ts (100%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/responses/GetHostnamesResponse.ts (100%) rename src/ClientApp/models/letsEncryptServer/{cache => account}/responses/HostnameResponse.ts (100%) delete mode 100644 src/LetsEncryptServer/Controllers/AccountsController.cs create mode 100644 src/LetsEncryptServer/Services/AccoutService.cs rename src/Models/LetsEncryptServer/{Cache => Account}/Requests/PatchAccountRequest.cs (82%) rename src/Models/LetsEncryptServer/{Cache/Requests/PatchContactsRequest.cs => Account/Requests/PatchContactsRequest - Copy.cs} (78%) create mode 100644 src/Models/LetsEncryptServer/Account/Requests/PatchHostnamesRequest.cs rename src/Models/LetsEncryptServer/{Cache => Account}/Requests/PostAccountRequest.cs (72%) rename src/Models/LetsEncryptServer/{Cache => Account}/Requests/PostContactsRequest.cs (89%) create mode 100644 src/Models/LetsEncryptServer/Account/Requests/PostHostnamesRequest.cs rename src/Models/LetsEncryptServer/{Cache => Account}/Requests/PutAccountRequest.cs (92%) rename src/Models/LetsEncryptServer/{Cache => Account}/Requests/PutContactsRequest.cs (87%) create mode 100644 src/Models/LetsEncryptServer/Account/Requests/PutHostnamesRequest.cs rename src/Models/LetsEncryptServer/{Cache => Account}/Responses/GetAccountResponse.cs (65%) rename src/Models/LetsEncryptServer/{Cache => Account}/Responses/GetContactsResponse.cs (76%) rename src/Models/LetsEncryptServer/{Cache => Account}/Responses/GetHostnamesResponse.cs (78%) rename src/Models/LetsEncryptServer/{Cache => Account}/Responses/HostnameResponse.cs (82%) diff --git a/src/ClientApp/.vscode/settings.json b/src/ClientApp/.vscode/settings.json index e35422a..2ab4571 100644 --- a/src/ClientApp/.vscode/settings.json +++ b/src/ClientApp/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.tabSize": 2, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/src/ClientApp/ApiRoutes.tsx b/src/ClientApp/ApiRoutes.tsx index 2d8ec84..41e549e 100644 --- a/src/ClientApp/ApiRoutes.tsx +++ b/src/ClientApp/ApiRoutes.tsx @@ -1,9 +1,9 @@ enum ApiRoutes { - CACHE_ACCOUNTS = 'api/cache/accounts', - CACHE_ACCOUNT = 'api/cache/account/{accountId}', - CACHE_ACCOUNT_CONTACTS = 'api/cache/account/{accountId}/contacts', - CACHE_ACCOUNT_CONTACT = 'api/cache/account/{accountId}/contact/{index}', - CACHE_ACCOUNT_HOSTNAMES = 'api/cache/account/{accountId}/hostnames' + ACCOUNTS = 'api/accounts', + ACCOUNT = 'api/account/{accountId}', + ACCOUNT_CONTACTS = 'api/account/{accountId}/contacts', + ACCOUNT_CONTACT = 'api/account/{accountId}/contact/{index}', + ACCOUNT_HOSTNAMES = 'api/account/{accountId}/hostnames' // CERTS_FLOW_CONFIGURE_CLIENT = `api/CertsFlow/ConfigureClient`, // CERTS_FLOW_TERMS_OF_SERVICE = `api/CertsFlow/TermsOfService/{sessionId}`, diff --git a/src/ClientApp/app/page.tsx b/src/ClientApp/app/page.tsx index 218fb1e..9a8b8e8 100644 --- a/src/ClientApp/app/page.tsx +++ b/src/ClientApp/app/page.tsx @@ -10,7 +10,7 @@ import { } from '@/hooks/useValidation' import { CustomButton, CustomInput } from '@/controls' import { TrashIcon, PlusIcon } from '@heroicons/react/24/solid' -import { GetAccountResponse } from '@/models/letsEncryptServer/cache/responses/GetAccountResponse' +import { GetAccountResponse } from '@/models/letsEncryptServer/account/responses/GetAccountResponse' import { deepCopy } from './functions' import { CacheAccount } from '@/entities/CacheAccount' @@ -50,18 +50,21 @@ export default function Page() { const fetchAccounts = async () => { const newAccounts: CacheAccount[] = [] const accounts = await httpService.get( - GetApiRoute(ApiRoutes.CACHE_ACCOUNTS) + GetApiRoute(ApiRoutes.ACCOUNTS) ) accounts?.forEach((account) => { newAccounts.push({ accountId: account.accountId, + description: account.description, contacts: account.contacts, - hostnames: account.hostnames.map((h) => ({ - hostname: h.hostname, - expires: new Date(h.expires), - isUpcomingExpire: h.isUpcomingExpire - })), + challengeType: account.challengeType, + hostnames: + account.hostnames?.map((h) => ({ + hostname: h.hostname, + expires: new Date(h.expires), + isUpcomingExpire: h.isUpcomingExpire + })) ?? [], isEditMode: false }) }) @@ -97,7 +100,7 @@ export default function Page() { // TODO: Remove from cache httpService.delete( - GetApiRoute(ApiRoutes.CACHE_ACCOUNT_CONTACT, accountId, contact) + GetApiRoute(ApiRoutes.ACCOUNT_CONTACT, accountId, contact) ) setAccounts( @@ -387,7 +390,9 @@ export default function Page() { ) : ( <>
-

Description:

+

+ Description: {account.description} +

Contacts:

@@ -399,6 +404,11 @@ export default function Page() { ))}
+
+

+ Challenge type: {account.challengeType} +

+

Hostnames:

    diff --git a/src/ClientApp/entities/CacheAccount.ts b/src/ClientApp/entities/CacheAccount.ts index 37f3c75..01dbda6 100644 --- a/src/ClientApp/entities/CacheAccount.ts +++ b/src/ClientApp/entities/CacheAccount.ts @@ -4,6 +4,7 @@ export interface CacheAccount { accountId: string description?: string contacts: string[] + challengeType?: string hostnames: CacheAccountHostname[] isEditMode: boolean } diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PatchAccountRequest.ts b/src/ClientApp/models/letsEncryptServer/account/requests/PatchAccountRequest.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/requests/PatchAccountRequest.ts rename to src/ClientApp/models/letsEncryptServer/account/requests/PatchAccountRequest.ts diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PatchContactsRequest.ts b/src/ClientApp/models/letsEncryptServer/account/requests/PatchContactsRequest.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/requests/PatchContactsRequest.ts rename to src/ClientApp/models/letsEncryptServer/account/requests/PatchContactsRequest.ts diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PostContactsRequest.ts b/src/ClientApp/models/letsEncryptServer/account/requests/PostContactsRequest.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/requests/PostContactsRequest.ts rename to src/ClientApp/models/letsEncryptServer/account/requests/PostContactsRequest.ts diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PutAccountRequest.ts b/src/ClientApp/models/letsEncryptServer/account/requests/PutAccountRequest.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/requests/PutAccountRequest.ts rename to src/ClientApp/models/letsEncryptServer/account/requests/PutAccountRequest.ts diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PutContactsRequest.ts b/src/ClientApp/models/letsEncryptServer/account/requests/PutContactsRequest.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/requests/PutContactsRequest.ts rename to src/ClientApp/models/letsEncryptServer/account/requests/PutContactsRequest.ts diff --git a/src/ClientApp/models/letsEncryptServer/cache/responses/GetAccountResponse.ts b/src/ClientApp/models/letsEncryptServer/account/responses/GetAccountResponse.ts similarity index 62% rename from src/ClientApp/models/letsEncryptServer/cache/responses/GetAccountResponse.ts rename to src/ClientApp/models/letsEncryptServer/account/responses/GetAccountResponse.ts index 90caf48..eee38e2 100644 --- a/src/ClientApp/models/letsEncryptServer/cache/responses/GetAccountResponse.ts +++ b/src/ClientApp/models/letsEncryptServer/account/responses/GetAccountResponse.ts @@ -2,6 +2,8 @@ import { HostnameResponse } from './HostnameResponse' export interface GetAccountResponse { accountId: string + description?: string contacts: string[] - hostnames: HostnameResponse[] + challengeType?: string + hostnames?: HostnameResponse[] } diff --git a/src/ClientApp/models/letsEncryptServer/cache/responses/GetContactsResponse.ts b/src/ClientApp/models/letsEncryptServer/account/responses/GetContactsResponse.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/responses/GetContactsResponse.ts rename to src/ClientApp/models/letsEncryptServer/account/responses/GetContactsResponse.ts diff --git a/src/ClientApp/models/letsEncryptServer/cache/responses/GetHostnamesResponse.ts b/src/ClientApp/models/letsEncryptServer/account/responses/GetHostnamesResponse.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/responses/GetHostnamesResponse.ts rename to src/ClientApp/models/letsEncryptServer/account/responses/GetHostnamesResponse.ts diff --git a/src/ClientApp/models/letsEncryptServer/cache/responses/HostnameResponse.ts b/src/ClientApp/models/letsEncryptServer/account/responses/HostnameResponse.ts similarity index 100% rename from src/ClientApp/models/letsEncryptServer/cache/responses/HostnameResponse.ts rename to src/ClientApp/models/letsEncryptServer/account/responses/HostnameResponse.ts diff --git a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs index 1784e11..0fc51c0 100644 --- a/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs +++ b/src/LetsEncrypt/Entities/LetsEncrypt/RegistrationCache.cs @@ -29,9 +29,10 @@ public class RegistrationCache { /// /// Field used to identify cache by account id /// - public Guid AccountId { get; set; } + public required Guid AccountId { get; set; } public string? Description { get; set; } - public string[]? Contacts { get; set; } + public required string[] Contacts { get; set; } + public string? ChallengeType { get; set; } #endregion diff --git a/src/LetsEncrypt/Services/LetsEncryptService.cs b/src/LetsEncrypt/Services/LetsEncryptService.cs index 22f054d..fc55ca9 100644 --- a/src/LetsEncrypt/Services/LetsEncryptService.cs +++ b/src/LetsEncrypt/Services/LetsEncryptService.cs @@ -1,8 +1,13 @@ 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; @@ -10,9 +15,6 @@ using MaksIT.LetsEncrypt.Models.Responses; using MaksIT.LetsEncrypt.Models.Interfaces; using MaksIT.LetsEncrypt.Models.Requests; using MaksIT.LetsEncrypt.Entities.Jws; -using DomainResults.Common; -using System.Net.Http.Headers; -using System.Security.Principal; namespace MaksIT.LetsEncrypt.Services; @@ -230,6 +232,7 @@ public class LetsEncryptService : ILetsEncryptService { var challenge = challengeResponse.Result.Challenges.First(x => x.Type == challengeType); state.Challenges.Add(challenge); + state.Cache.ChallengeType = challengeType; var keyToken = state.JwsService.GetKeyAuthorization(challenge.Token); diff --git a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs index ebfc809..2fd9f4c 100644 --- a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs +++ b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs @@ -12,7 +12,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { private readonly IOptions _appSettings; private readonly ILogger _logger; - private readonly ICacheInternalService _cacheService; + private readonly ICacheService _cacheService; private readonly ICertsInternalService _certsFlowService; public AutoRenewal( @@ -61,7 +61,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { return IDomainResult.Success(); } - var renewResult = await RenewCertificatesForHostnames(cache.AccountId, cache.Description, cache.Contacts, hostnames); + var renewResult = await RenewCertificatesForHostnames(cache.AccountId, cache.Description, cache.Contacts, hostnames, cache.ChallengeType); if (!renewResult.IsSuccess) return renewResult; @@ -70,7 +70,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { return IDomainResult.Success(); } - private async Task RenewCertificatesForHostnames(Guid accountId, string description, string[] contacts, string[] hostnames) { + private async Task RenewCertificatesForHostnames(Guid accountId, string description, string[] contacts, string[] hostnames, string challengeType) { var (sessionId, configureClientResult) = await _certsFlowService.ConfigureClientAsync(); if (!configureClientResult.IsSuccess || sessionId == null) { LogErrors(configureClientResult.Errors); @@ -85,7 +85,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { return initResult; } - var (_, newOrderResult) = await _certsFlowService.NewOrderAsync(sessionIdValue, hostnames, "http-01"); + var (_, newOrderResult) = await _certsFlowService.NewOrderAsync(sessionIdValue, hostnames, challengeType); if (!newOrderResult.IsSuccess) { LogErrors(newOrderResult.Errors); return newOrderResult; diff --git a/src/LetsEncryptServer/Controllers/AccountController.cs b/src/LetsEncryptServer/Controllers/AccountController.cs index 753dc86..890789d 100644 --- a/src/LetsEncryptServer/Controllers/AccountController.cs +++ b/src/LetsEncryptServer/Controllers/AccountController.cs @@ -3,89 +3,128 @@ using DomainResults.Mvc; using MaksIT.LetsEncryptServer.Services; -using MaksIT.Models.LetsEncryptServer.Cache.Requests; +using MaksIT.Models.LetsEncryptServer.Account.Requests; namespace MaksIT.LetsEncryptServer.Controllers; [ApiController] -[Route("api/account")] +[Route("api")] public class AccountController : ControllerBase { - private readonly ICacheRestService _cacheService; - private readonly ICertsFlowService _certsFlowService; + private readonly IAccountRestService _accountService; public AccountController( - ICacheService cacheService, - ICertsFlowService certsFlowService + IAccountService accountService ) { - _cacheService = cacheService; - _certsFlowService = certsFlowService; + _accountService = accountService; } + #region Accounts - [HttpPost] + [HttpGet("accounts")] + public async Task GetAccounts() { + var result = await _accountService.GetAccountsAsync(); + return result.ToActionResult(); + } + + #endregion + + #region Account + + [HttpPost("account")] public async Task PostAccount([FromBody] PostAccountRequest requestData) { - //var result = await _cacheService.PostAccountAsync(requestData); + var result = await _accountService.PostAccountAsync(requestData); + return result.ToActionResult(); + } + + [HttpPut("account/{accountId:guid}")] + public async Task PutAccount(Guid accountId, [FromBody] PutAccountRequest requestData) { + var result = await _accountService.PutAccountAsync(accountId, requestData); + return result.ToActionResult(); + } + + [HttpPatch("account/{accountId:guid}")] + public async Task PatchAccount(Guid accountId, [FromBody] PatchAccountRequest requestData) { + var result = await _accountService.PatchAccountAsync(accountId, requestData); + return result.ToActionResult(); + } + + [HttpDelete("account/{accountd:guid}")] + public async Task DeleteAccount(Guid accountId) { + var result = await _accountService.DeleteAccountAsync(accountId); + return result.ToActionResult(); + } + + #endregion + + #region Account Contacts + + [HttpGet("account/{accountId:guid}/contacts")] + public async Task GetContacts(Guid accountId) { + var result = await _accountService.GetContactsAsync(accountId); + return result.ToActionResult(); + } + + [HttpPost("account/{accountId:guid}/contacts")] + public async Task PostContacts(Guid accountId, [FromBody] PostContactsRequest requestData) { + //var result = await _accountService.PostContactsAsync(accountId, requestData); //return result.ToActionResult(); return BadRequest("Not implemented"); } - [HttpPut("{accountId:guid}")] - public async Task PutAccount(Guid accountId, [FromBody] PutAccountRequest requestData) { - var result = await _cacheService.PutAccountAsync(accountId, requestData); - return result.ToActionResult(); - } - - [HttpPatch("{accountId:guid}")] - public async Task PatchAccount(Guid accountId, [FromBody] PatchAccountRequest requestData) { - var result = await _cacheService.PatchAccountAsync(accountId, requestData); - return result.ToActionResult(); - } - - [HttpDelete("{accountd:guid}")] - public async Task DeleteAccount(Guid accountId) { - var result = await _cacheService.DeleteAccountAsync(accountId); - return result.ToActionResult(); - } - - #region Contacts - - [HttpGet("{accountId:guid}/contacts")] - public async Task GetContacts(Guid accountId) { - var result = await _cacheService.GetContactsAsync(accountId); - return result.ToActionResult(); - } - - [HttpPut("{accountId:guid}/contacts")] + [HttpPut("account/{accountId:guid}/contacts")] public async Task PutContacts(Guid accountId, [FromBody] PutContactsRequest requestData) { - var result = await _cacheService.PutContactsAsync(accountId, requestData); + var result = await _accountService.PutContactsAsync(accountId, requestData); return result.ToActionResult(); } - [HttpPatch("{accountId:guid}/contacts")] + [HttpPatch("account/{accountId:guid}/contacts")] public async Task PatchContacts(Guid accountId, [FromBody] PatchContactsRequest requestData) { - var result = await _cacheService.PatchContactsAsync(accountId, requestData); + var result = await _accountService.PatchContactsAsync(accountId, requestData); return result.ToActionResult(); } - [HttpDelete("{accountId:guid}/contact/{index:int}")] + [HttpDelete("account/{accountId:guid}/contact/{index:int}")] public async Task DeleteContact(Guid accountId, int index) { - var result = await _cacheService.DeleteContactAsync(accountId, index); + var result = await _accountService.DeleteContactAsync(accountId, index); return result.ToActionResult(); } #endregion - #region Hostnames + #region Account Hostnames [HttpGet("{accountId:guid}/hostnames")] public async Task GetHostnames(Guid accountId) { - var result = await _cacheService.GetHostnames(accountId); + var result = await _accountService.GetHostnames(accountId); return result.ToActionResult(); } - [HttpPost("{accountId:guid}")] + [HttpPost("account/{accountId:guid}/hostnames")] + public async Task PostHostname(Guid accountId, [FromBody] PostHostnamesRequest requestData) { + //var result = await _cacheService.PostHostnameAsync(accountId, requestData); + //return result.ToActionResult(); - [HttpDelete("{accountId:guid}/hostname/{index:int}")] + return BadRequest("Not implemented"); + } + + [HttpPut("account/{accountId:guid}/hostnames")] + public async Task PutHostname(Guid accountId, [FromBody] PutHostnamesRequest requestData) { + //var result = await _cacheService.PutHostnameAsync(accountId, requestData); + //return result.ToActionResult(); + + return BadRequest("Not implemented"); + } + + [HttpPatch("account/{accountId:guid}/hostnames")] + public async Task PatchHostname(Guid accountId, [FromBody] PatchHostnamesRequest requestData) { + //var result = await _cacheService.PatchHostnameAsync(accountId, requestData); + //return result.ToActionResult(); + + return BadRequest("Not implemented"); + } + + + [HttpDelete("account/{accountId:guid}/hostname/{index:int}")] public async Task DeleteHostname(Guid accountId, int index) { //var result = await _cacheService.DeleteHostnameAsync(accountId, index); //return result.ToActionResult(); diff --git a/src/LetsEncryptServer/Controllers/AccountsController.cs b/src/LetsEncryptServer/Controllers/AccountsController.cs deleted file mode 100644 index 323990a..0000000 --- a/src/LetsEncryptServer/Controllers/AccountsController.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -using DomainResults.Mvc; - -using MaksIT.LetsEncryptServer.Services; -using MaksIT.Models.LetsEncryptServer.Cache.Requests; - -namespace MaksIT.LetsEncryptServer.Controllers; - -[ApiController] -[Route("api/accounts")] -public class AccountsController : ControllerBase { - private readonly ICacheRestService _cacheService; - private readonly ICertsFlowService _certsFlowService; - - public AccountsController( - ICacheService cacheService, - ICertsFlowService certsFlowService - ) { - _cacheService = cacheService; - _certsFlowService = certsFlowService; - } - - [HttpGet] - public async Task GetAccounts() { - var result = await _cacheService.GetAccountsAsync(); - return result.ToActionResult(); - } -} diff --git a/src/LetsEncryptServer/Program.cs b/src/LetsEncryptServer/Program.cs index a51d570..4f9b804 100644 --- a/src/LetsEncryptServer/Program.cs +++ b/src/LetsEncryptServer/Program.cs @@ -29,8 +29,9 @@ builder.Services.AddCors(); builder.Services.AddMemoryCache(); builder.Services.AddHttpClient(); -builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddHttpClient(); builder.Services.AddHostedService(); diff --git a/src/LetsEncryptServer/Services/AccoutService.cs b/src/LetsEncryptServer/Services/AccoutService.cs new file mode 100644 index 0000000..6633246 --- /dev/null +++ b/src/LetsEncryptServer/Services/AccoutService.cs @@ -0,0 +1,337 @@ +using System.Text.Json; + +using DomainResults.Common; + +using MaksIT.Core.Extensions; +using MaksIT.LetsEncrypt.Entities; +using MaksIT.LetsEncrypt.Models.Responses; +using MaksIT.Models; +using MaksIT.Models.LetsEncryptServer.Account.Requests; +using MaksIT.Models.LetsEncryptServer.Account.Responses; + +namespace MaksIT.LetsEncryptServer.Services; + + +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)> PutAccountAsync(Guid accountId, PutAccountRequest requestData); + Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData); + Task DeleteAccountAsync(Guid accountId); + Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId); + Task<(GetContactsResponse?, IDomainResult)> PostContactsAsync(Guid accountId, PostContactsRequest requestData); + Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData); + Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData); + Task DeleteContactAsync(Guid accountId, int index); + Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId); +} + +public interface IAccountService : IAccountInternalService, IAccountRestService { } + +public class AccountService : IAccountService { + + private readonly ILogger _logger; + private readonly ICacheService _cacheService; + private readonly ICertsInternalService _certsFlowService; + + public AccountService( + ILogger logger, + ICacheService cacheService, + ICertsFlowService certsFlowService + ) { + _logger = logger; + _cacheService = cacheService; + _certsFlowService = certsFlowService; + } + + #region Accounts + + public async Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync() { + + var (caches, result) = await _cacheService.LoadAccountsFromCacheAsync(); + if (!result.IsSuccess || caches == null) { + return (null, result); + } + + var accounts = caches.Select(cache => new GetAccountResponse { + AccountId = cache.AccountId, + Description = cache.Description, + Contacts = cache.Contacts, + ChallengeType = cache.ChallengeType, + Hostnames = GetHostnamesFromCache(cache).ToArray() + }); + + return IDomainResult.Success(accounts.ToArray()); + } + + public async Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId) { + var (cache, result) = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!result.IsSuccess || cache == null) { + return (null, result); + } + + var response = new GetAccountResponse { + AccountId = accountId, + Description = cache.Description, + Contacts = cache.Contacts, + Hostnames = GetHostnamesFromCache(cache).ToArray() + }; + + return IDomainResult.Success(response); + } + + public async Task<(GetAccountResponse?, IDomainResult)> PostAccountAsync(PostAccountRequest requestData) { + var (sessionId, configureClientResult) = await _certsFlowService.ConfigureClientAsync(); + if (!configureClientResult.IsSuccess || sessionId == null) { + //LogErrors(configureClientResult.Errors); + return (null, configureClientResult); + } + var sessionIdValue = sessionId.Value; + + var (_, initResult) = await _certsFlowService.InitAsync(sessionIdValue, null, requestData.Description, requestData.Contacts); + if (!initResult.IsSuccess) { + //LogErrors(initResult.Errors); + return (null, initResult); + } + + var (_, newOrderResult) = await _certsFlowService.NewOrderAsync(sessionIdValue, requestData.Hostnames, requestData.ChallengeType); + if (!newOrderResult.IsSuccess) { + //LogErrors(newOrderResult.Errors); + return (null, newOrderResult); + } + + var challengeResult = await _certsFlowService.CompleteChallengesAsync(sessionIdValue); + if (!challengeResult.IsSuccess) { + //LogErrors(challengeResult.Errors); + return (null, challengeResult); + } + + var getOrderResult = await _certsFlowService.GetOrderAsync(sessionIdValue, requestData.Hostnames); + if (!getOrderResult.IsSuccess) { + //LogErrors(getOrderResult.Errors); + return (null, getOrderResult); + } + + var certs = await _certsFlowService.GetCertificatesAsync(sessionIdValue, requestData.Hostnames); + if (!certs.IsSuccess) { + //LogErrors(certs.Errors); + return (null, certs); + } + + var (_, applyCertsResult) = await _certsFlowService.ApplyCertificatesAsync(sessionIdValue, requestData.Hostnames); + if (!applyCertsResult.IsSuccess) { + //LogErrors(applyCertsResult.Errors); + return (null, applyCertsResult); + } + + return IDomainResult.Success(null); + } + + public async Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData) { + var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadResult.IsSuccess || cache == null) { + return (null, loadResult); + } + + cache.Description = requestData.Description; + cache.Contacts = requestData.Contacts; + + var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache); + if (!saveResult.IsSuccess) { + return (null, saveResult); + } + + return 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); + } + + if (requestData.Description != null) { + switch (requestData.Description.Op) { + case PatchOperation.Replace: + cache.Description = requestData.Description.Value; + break; + } + } + + if (requestData.Contacts != null && requestData.Contacts.Any()) { + var contacts = cache.Contacts?.ToList() ?? new List(); + foreach (var action in requestData.Contacts) { + switch (action.Op) + { + case PatchOperation.Add: + if (action.Value != null) contacts.Add(action.Value); + break; + case PatchOperation.Replace: + if (action.Index != null && action.Index >= 0 && action.Index < contacts.Count) + contacts[action.Index.Value] = action.Value; + break; + case PatchOperation.Remove: + if (action.Index != null && action.Index >= 0 && action.Index < contacts.Count) + contacts.RemoveAt(action.Index.Value); + break; + } + } + cache.Contacts = contacts.ToArray(); + } + + var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache); + if (!saveResult.IsSuccess) { + return (null, saveResult); + } + + return CreateGetAccountResponse(accountId, cache); + } + + public async Task DeleteAccountAsync(Guid accountId) { + return await _cacheService.DeleteFromCacheAsync(accountId); + } + #endregion + + #region Contacts Operations + + public async Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId) { + var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadResult.IsSuccess || cache == null) { + return (null, loadResult); + } + + return IDomainResult.Success(new GetContactsResponse { + Contacts = cache.Contacts ?? Array.Empty() + }); + } + + public async Task<(GetContactsResponse?, IDomainResult)> PostContactsAsync(Guid accountId, PostContactsRequest requestData) { + return IDomainResult.Failed("Not implemented"); + } + + public async Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData) { + var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadResult.IsSuccess || cache == null) { + return (null, loadResult); + } + + cache.Contacts = requestData.Contacts; + var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache); + if (!saveResult.IsSuccess) { + return (null, saveResult); + } + + return CreateGetAccountResponse(accountId, cache); + } + + public async Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData) { + var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadResult.IsSuccess || cache == null) { + return (null, loadResult); + } + + var contacts = cache.Contacts?.ToList() ?? new List(); + + foreach (var contact in requestData.Contacts) { + switch (contact.Op) { + case PatchOperation.Add: + if (contact.Value != null) { + contacts.Add(contact.Value); + } + break; + case PatchOperation.Replace: + if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count && contact.Value != null) { + contacts[contact.Index.Value] = contact.Value; + } + break; + case PatchOperation.Remove: + if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count) { + contacts.RemoveAt(contact.Index.Value); + } + break; + default: + return (null, IDomainResult.Failed("Invalid patch operation.")); + } + } + + cache.Contacts = contacts.ToArray(); + var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache); + if (!saveResult.IsSuccess) { + return (null, saveResult); + } + + return CreateGetAccountResponse(accountId, cache); + } + + public async Task DeleteContactAsync(Guid accountId, int index) { + var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadResult.IsSuccess || cache == null) { + return loadResult; + } + + var contacts = cache.Contacts?.ToList() ?? new List(); + + if (index >= 0 && index < contacts.Count) { + contacts.RemoveAt(index); + } + + cache.Contacts = contacts.ToArray(); + var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache); + if (!saveResult.IsSuccess) { + return saveResult; + } + + return IDomainResult.Success(); + } + + #endregion + + #region Hostnames Operations + + public async Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId) { + var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId); + if (!loadResult.IsSuccess || cache?.CachedCerts == null) { + return (null, loadResult); + } + + var hostnames = GetHostnamesFromCache(cache); + + return IDomainResult.Success(new GetHostnamesResponse { + Hostnames = hostnames + }); + } + + private List GetHostnamesFromCache(RegistrationCache cache) { + var hosts = cache.GetHosts().Select(x => new HostnameResponse { + Hostname = x.Hostname, + Expires = x.Expires, + IsUpcomingExpire = x.IsUpcomingExpire + }).ToList(); + + return hosts; + } + + #endregion + + #region Helper Methods + + private (GetAccountResponse?, IDomainResult) CreateGetAccountResponse(Guid accountId, RegistrationCache cache) { + var hostnames = GetHostnamesFromCache(cache) ?? new List(); + + return (new GetAccountResponse { + AccountId = accountId, + Description = cache.Description, + Contacts = cache.Contacts, + Hostnames = hostnames.ToArray() + }, IDomainResult.Success()); + } + + + #endregion +} diff --git a/src/LetsEncryptServer/Services/CacheService.cs b/src/LetsEncryptServer/Services/CacheService.cs index 2bbf835..2981225 100644 --- a/src/LetsEncryptServer/Services/CacheService.cs +++ b/src/LetsEncryptServer/Services/CacheService.cs @@ -5,33 +5,16 @@ using DomainResults.Common; using MaksIT.Core.Extensions; using MaksIT.LetsEncrypt.Entities; using MaksIT.Models; -using MaksIT.Models.LetsEncryptServer.Cache.Requests; -using MaksIT.Models.LetsEncryptServer.Cache.Responses; namespace MaksIT.LetsEncryptServer.Services; -public interface ICacheInternalService { +public interface ICacheService { Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync(); Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId); Task SaveToCacheAsync(Guid accountId, RegistrationCache cache); Task DeleteFromCacheAsync(Guid accountId); } -public interface ICacheRestService { - Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync(); - Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId); - Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData); - Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData); - Task DeleteAccountAsync(Guid accountId); - Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId); - Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData); - Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData); - Task DeleteContactAsync(Guid accountId, int index); - Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId); -} - -public interface ICacheService : ICacheInternalService, ICacheRestService {} - public class CacheService : ICacheService, IDisposable { private readonly ILogger _logger; private readonly string _cacheDirectory; @@ -86,9 +69,7 @@ public class CacheService : ICacheService, IDisposable { } - public Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId) { - return _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId)); - } + private async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheInternalAsync(Guid accountId) { var cacheFilePath = GetCacheFilePath(accountId); @@ -110,9 +91,7 @@ public class CacheService : ICacheService, IDisposable { return IDomainResult.Success(cache); } - public Task SaveToCacheAsync(Guid accountId, RegistrationCache cache) { - return _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache)); - } + private async Task SaveToCacheInternalAsync(Guid accountId, RegistrationCache cache) { var cacheFilePath = GetCacheFilePath(accountId); @@ -122,9 +101,7 @@ public class CacheService : ICacheService, IDisposable { return DomainResult.Success(); } - public Task DeleteFromCacheAsync(Guid accountId) { - return _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId)); - } + private IDomainResult DeleteFromCacheInternal(Guid accountId) { var cacheFilePath = GetCacheFilePath(accountId); @@ -137,245 +114,23 @@ public class CacheService : ICacheService, IDisposable { } return DomainResult.Success(); } - - #endregion - - #region Account Operations - - public async Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync() { - return await _lockManager.ExecuteWithLockAsync(async () => { - - var accountIds = GetCachedAccounts(); - var accounts = new List(); - - foreach (var accountId in accountIds) { - var (account, result) = await GetAccountAsync(accountId); - if (!result.IsSuccess || account == null) { - return (null, result); - } - accounts.Add(account); - } - - return IDomainResult.Success(accounts.ToArray()); - }); - } - - public async Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId) { - return await _lockManager.ExecuteWithLockAsync(async () => { - var (cache, result) = await LoadAccountFromCacheAsync(accountId); - if (!result.IsSuccess || cache == null) { - return (null, result); - } - - var response = new GetAccountResponse { - AccountId = accountId, - Description = cache.Description, - Contacts = cache.Contacts, - Hostnames = GetHostnamesFromCache(cache).ToArray() - }; - - return IDomainResult.Success(response); - }); - } - - public async Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData) { - var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); - } - - cache.Description = requestData.Description; - cache.Contacts = requestData.Contacts; - - var saveResult = await SaveToCacheAsync(accountId, cache); - if (!saveResult.IsSuccess) { - return (null, saveResult); - } - - return CreateGetAccountResponse(accountId, cache); - } - - public async Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) { - var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); - } - - if (requestData.Description != null) { - switch (requestData.Description.Op) { - case PatchOperation.Replace: - cache.Description = requestData.Description.Value; - break; - } - } - - if (requestData.Contacts != null && requestData.Contacts.Any()) { - var contacts = cache.Contacts?.ToList() ?? new List(); - foreach (var action in requestData.Contacts) { - switch (action.Op) - { - case PatchOperation.Add: - if (action.Value != null) contacts.Add(action.Value); - break; - case PatchOperation.Replace: - if (action.Index != null && action.Index >= 0 && action.Index < contacts.Count) - contacts[action.Index.Value] = action.Value; - break; - case PatchOperation.Remove: - if (action.Index != null && action.Index >= 0 && action.Index < contacts.Count) - contacts.RemoveAt(action.Index.Value); - break; - } - } - cache.Contacts = contacts.ToArray(); - } - - var saveResult = await SaveToCacheAsync(accountId, cache); - if (!saveResult.IsSuccess) { - return (null, saveResult); - } - - return CreateGetAccountResponse(accountId, cache); - } - - public async Task DeleteAccountAsync(Guid accountId) { - return await DeleteFromCacheAsync(accountId); - } - #endregion - - #region Contacts Operations - - public async Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId) { - var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); - } - - return IDomainResult.Success(new GetContactsResponse { - Contacts = cache.Contacts ?? Array.Empty() - }); - } - - public async Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData) { - var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); - } - - cache.Contacts = requestData.Contacts; - var saveResult = await SaveToCacheAsync(accountId, cache); - if (!saveResult.IsSuccess) { - return (null, saveResult); - } - - return CreateGetAccountResponse(accountId, cache); - } - - public async Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData) { - var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return (null, loadResult); - } - - var contacts = cache.Contacts?.ToList() ?? new List(); - - foreach (var contact in requestData.Contacts) { - switch (contact.Op) { - case PatchOperation.Add: - if (contact.Value != null) { - contacts.Add(contact.Value); - } - break; - case PatchOperation.Replace: - if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count && contact.Value != null) { - contacts[contact.Index.Value] = contact.Value; - } - break; - case PatchOperation.Remove: - if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count) { - contacts.RemoveAt(contact.Index.Value); - } - break; - default: - return (null, IDomainResult.Failed("Invalid patch operation.")); - } - } - - cache.Contacts = contacts.ToArray(); - var saveResult = await SaveToCacheAsync(accountId, cache); - if (!saveResult.IsSuccess) { - return (null, saveResult); - } - - return CreateGetAccountResponse(accountId, cache); - } - - public async Task DeleteContactAsync(Guid accountId, int index) { - var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache == null) { - return loadResult; - } - - var contacts = cache.Contacts?.ToList() ?? new List(); - - if (index >= 0 && index < contacts.Count) { - contacts.RemoveAt(index); - } - - cache.Contacts = contacts.ToArray(); - var saveResult = await SaveToCacheAsync(accountId, cache); - if (!saveResult.IsSuccess) { - return saveResult; - } - - return IDomainResult.Success(); - } #endregion - #region Hostnames Operations - public async Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId) { - var (cache, loadResult) = await LoadAccountFromCacheAsync(accountId); - if (!loadResult.IsSuccess || cache?.CachedCerts == null) { - return (null, loadResult); - } - - var hostnames = GetHostnamesFromCache(cache); - - return IDomainResult.Success(new GetHostnamesResponse { - Hostnames = hostnames - }); + public Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId) { + return _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId)); } - private List GetHostnamesFromCache(RegistrationCache cache) { - var hosts = cache.GetHosts().Select(x => new HostnameResponse { - Hostname = x.Hostname, - Expires = x.Expires, - IsUpcomingExpire = x.IsUpcomingExpire - }).ToList(); - - return hosts; + public Task SaveToCacheAsync(Guid accountId, RegistrationCache cache) { + return _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache)); } - #endregion - - #region Helper Methods - - private (GetAccountResponse?, IDomainResult) CreateGetAccountResponse(Guid accountId, RegistrationCache cache) { - var hostnames = GetHostnamesFromCache(cache) ?? new List(); - - return (new GetAccountResponse { - AccountId = accountId, - Description = cache.Description, - Contacts = cache.Contacts, - Hostnames = hostnames.ToArray() - }, IDomainResult.Success()); + public Task DeleteFromCacheAsync(Guid accountId) { + return _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId)); } public void Dispose() { - _lockManager?.Dispose(); + _lockManager.Dispose(); } - - #endregion } diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PatchAccountRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PatchAccountRequest.cs similarity index 82% rename from src/Models/LetsEncryptServer/Cache/Requests/PatchAccountRequest.cs rename to src/Models/LetsEncryptServer/Account/Requests/PatchAccountRequest.cs index 22d2bc7..1eaef45 100644 --- a/src/Models/LetsEncryptServer/Cache/Requests/PatchAccountRequest.cs +++ b/src/Models/LetsEncryptServer/Account/Requests/PatchAccountRequest.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { public class PatchAccountRequest { public PatchAction? Description { get; set; } diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PatchContactsRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PatchContactsRequest - Copy.cs similarity index 78% rename from src/Models/LetsEncryptServer/Cache/Requests/PatchContactsRequest.cs rename to src/Models/LetsEncryptServer/Account/Requests/PatchContactsRequest - Copy.cs index 92f2883..eb76f4e 100644 --- a/src/Models/LetsEncryptServer/Cache/Requests/PatchContactsRequest.cs +++ b/src/Models/LetsEncryptServer/Account/Requests/PatchContactsRequest - Copy.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { public class PatchContactsRequest { public List> Contacts { get; set; } diff --git a/src/Models/LetsEncryptServer/Account/Requests/PatchHostnamesRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PatchHostnamesRequest.cs new file mode 100644 index 0000000..03a350d --- /dev/null +++ b/src/Models/LetsEncryptServer/Account/Requests/PatchHostnamesRequest.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { + + public class PatchHostnamesRequest { + public List> Hostnames { get; set; } + } +} diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PostAccountRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PostAccountRequest.cs similarity index 72% rename from src/Models/LetsEncryptServer/Cache/Requests/PostAccountRequest.cs rename to src/Models/LetsEncryptServer/Account/Requests/PostAccountRequest.cs index fc6acc1..150abb4 100644 --- a/src/Models/LetsEncryptServer/Cache/Requests/PostAccountRequest.cs +++ b/src/Models/LetsEncryptServer/Account/Requests/PostAccountRequest.cs @@ -1,11 +1,13 @@ using System.ComponentModel.DataAnnotations; -namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { public class PostAccountRequest : IValidatableObject { public required string Description { get; set; } public required string[] Contacts { get; set; } public required string[] Hostnames { get; set; } + public required string ChallengeType { get; set; } + public IEnumerable Validate(ValidationContext validationContext) { if (string.IsNullOrWhiteSpace(Description)) yield return new ValidationResult("Description is required", new[] { nameof(Description) }); @@ -15,6 +17,9 @@ namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { if (Hostnames == null || Hostnames.Length == 0) yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) }); + + if (string.IsNullOrWhiteSpace(ChallengeType) && ChallengeType != "http-01") + yield return new ValidationResult("ChallengeType is required", new[] { nameof(ChallengeType) }); } } } diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PostContactsRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PostContactsRequest.cs similarity index 89% rename from src/Models/LetsEncryptServer/Cache/Requests/PostContactsRequest.cs rename to src/Models/LetsEncryptServer/Account/Requests/PostContactsRequest.cs index 2e64e95..837cece 100644 --- a/src/Models/LetsEncryptServer/Cache/Requests/PostContactsRequest.cs +++ b/src/Models/LetsEncryptServer/Account/Requests/PostContactsRequest.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { public class PostContactsRequest : IValidatableObject { public required string[] Contacts { get; set; } diff --git a/src/Models/LetsEncryptServer/Account/Requests/PostHostnamesRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PostHostnamesRequest.cs new file mode 100644 index 0000000..0bfdcdc --- /dev/null +++ b/src/Models/LetsEncryptServer/Account/Requests/PostHostnamesRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { + public class PostHostnamesRequest : IValidatableObject { + public required string[] Hostnames { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) { + if (Hostnames == null || Hostnames.Length == 0) + yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) }); + } + } +} diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PutAccountRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PutAccountRequest.cs similarity index 92% rename from src/Models/LetsEncryptServer/Cache/Requests/PutAccountRequest.cs rename to src/Models/LetsEncryptServer/Account/Requests/PutAccountRequest.cs index 858cc29..f86800e 100644 --- a/src/Models/LetsEncryptServer/Cache/Requests/PutAccountRequest.cs +++ b/src/Models/LetsEncryptServer/Account/Requests/PutAccountRequest.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { public class PutAccountRequest : IValidatableObject { public required string Description { get; set; } public required string[] Contacts { get; set; } diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PutContactsRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PutContactsRequest.cs similarity index 87% rename from src/Models/LetsEncryptServer/Cache/Requests/PutContactsRequest.cs rename to src/Models/LetsEncryptServer/Account/Requests/PutContactsRequest.cs index 94ce291..055d240 100644 --- a/src/Models/LetsEncryptServer/Cache/Requests/PutContactsRequest.cs +++ b/src/Models/LetsEncryptServer/Account/Requests/PutContactsRequest.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; -namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { public class PutContactsRequest : IValidatableObject { public required string[] Contacts { get; set; } diff --git a/src/Models/LetsEncryptServer/Account/Requests/PutHostnamesRequest.cs b/src/Models/LetsEncryptServer/Account/Requests/PutHostnamesRequest.cs new file mode 100644 index 0000000..7eb7b58 --- /dev/null +++ b/src/Models/LetsEncryptServer/Account/Requests/PutHostnamesRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.Models.LetsEncryptServer.Account.Requests { + public class PutHostnamesRequest : IValidatableObject { + public string[] Hostnames { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) { + if (Hostnames == null || Hostnames.Length == 0) + yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) }); + } + } +} diff --git a/src/Models/LetsEncryptServer/Cache/Responses/GetAccountResponse.cs b/src/Models/LetsEncryptServer/Account/Responses/GetAccountResponse.cs similarity index 65% rename from src/Models/LetsEncryptServer/Cache/Responses/GetAccountResponse.cs rename to src/Models/LetsEncryptServer/Account/Responses/GetAccountResponse.cs index 144618f..0ae4663 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/GetAccountResponse.cs +++ b/src/Models/LetsEncryptServer/Account/Responses/GetAccountResponse.cs @@ -4,13 +4,15 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Account.Responses { public class GetAccountResponse { public Guid AccountId { get; set; } public string? Description { get; set; } - public string []? Contacts { get; set; } + public required string [] Contacts { get; set; } + + public string? ChallengeType { get; set; } public HostnameResponse[]? Hostnames { get; set; } } diff --git a/src/Models/LetsEncryptServer/Cache/Responses/GetContactsResponse.cs b/src/Models/LetsEncryptServer/Account/Responses/GetContactsResponse.cs similarity index 76% rename from src/Models/LetsEncryptServer/Cache/Responses/GetContactsResponse.cs rename to src/Models/LetsEncryptServer/Account/Responses/GetContactsResponse.cs index 1e2e876..ae55445 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/GetContactsResponse.cs +++ b/src/Models/LetsEncryptServer/Account/Responses/GetContactsResponse.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Account.Responses { public class GetContactsResponse { public string[] Contacts { get; set; } } diff --git a/src/Models/LetsEncryptServer/Cache/Responses/GetHostnamesResponse.cs b/src/Models/LetsEncryptServer/Account/Responses/GetHostnamesResponse.cs similarity index 78% rename from src/Models/LetsEncryptServer/Cache/Responses/GetHostnamesResponse.cs rename to src/Models/LetsEncryptServer/Account/Responses/GetHostnamesResponse.cs index 207ce2b..f9758ba 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/GetHostnamesResponse.cs +++ b/src/Models/LetsEncryptServer/Account/Responses/GetHostnamesResponse.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Account.Responses { public class GetHostnamesResponse { public List Hostnames { get; set; } diff --git a/src/Models/LetsEncryptServer/Cache/Responses/HostnameResponse.cs b/src/Models/LetsEncryptServer/Account/Responses/HostnameResponse.cs similarity index 82% rename from src/Models/LetsEncryptServer/Cache/Responses/HostnameResponse.cs rename to src/Models/LetsEncryptServer/Account/Responses/HostnameResponse.cs index 79ab200..81a18ca 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/HostnameResponse.cs +++ b/src/Models/LetsEncryptServer/Account/Responses/HostnameResponse.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Account.Responses { public class HostnameResponse { public string Hostname { get; set; } public DateTime Expires { get; set; } diff --git a/src/docker-compose/LetsEncryptServer/cache/b2a279ae-0306-4d77-9408-66a078f34fa6.json b/src/docker-compose/LetsEncryptServer/cache/b2a279ae-0306-4d77-9408-66a078f34fa6.json index ce82b10..26243e1 100644 --- a/src/docker-compose/LetsEncryptServer/cache/b2a279ae-0306-4d77-9408-66a078f34fa6.json +++ b/src/docker-compose/LetsEncryptServer/cache/b2a279ae-0306-4d77-9408-66a078f34fa6.json @@ -1 +1 @@ -{"AccountId":"b2a279ae-0306-4d77-9408-66a078f34fa6","Contacts":["maksym.sadovnychyy@gmail.com"],"CachedCerts":{"maks-it.com":{"Cert":"-----BEGIN CERTIFICATE-----\nMIIF9zCCBN\u002BgAwIBAgISA\u002BeXxGMTkVzdcOun4cTxIFBuMA0GCSqGSIb3DQEBCwUA\nMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD\nEwNSMTEwHhcNMjQwNjA4MTkzMjQ4WhcNMjQwOTA2MTkzMjQ3WjAWMRQwEgYDVQQD\nEwttYWtzLWl0LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIZ5\nBv\u002BhJjZfolnnM23Fk8a6l9l3ezMVTxMSZM62sZ8toC\u002BWSBcx0mGJ6qLhTF348xlF\nGQzzZ7OLHv\u002BZq/wn0in8Qlu/n7OWlFHBUTBCy5bp2MXxrstJaY/pfDz2WYVgBDvh\nsPiRdfA/UY1f05qlUFamENuTNzDSflIKHIG0HCu31B4mA7v423Ai8vrTm3yav8KH\n10SnwSz0lCXMrB7V94BxCpqFYLUZLS8oJjIn26xEg4rBWCJlxKyBQq1T/KhuTfcg\nOMNjyR1bZU1\u002Bj31XRy0qcNsOfCsHAk/Ojae6q9Oy7ACFd31pTAmmf9LQlEtlD/fx\nszQLfTAKwY8dIWDJ5Va\u002BWhYlJSBF95cU3iJ/vFyQn1FaylAQ2P\u002B0/esXPSwJxQrE\nNs5lRunzwiHANYXQN\u002B9I9o4cL6n5HO1/orCSg/4FDJUNq7gnLlZNDewiaRGk0497\nONnsnt\u002BOLvaa7Jhdgx5GYXTaB3x9IBgNqVYm9CY5\u002ByQnbhXHPPVHnpCnWgahmCk6\nUzxREKCpy5Z4fU/6/YQz5/DOfeEdFemOZcgTIJguiI7Q\u002BHQyOIsiKbMlOcIPr4Kg\nUoStj6gZrWnHJirpW8TzeMnCiX3P8bICFj4mu4LKbc/5\u002BJBfBuq4Y53xGvZHTS2s\nI/J/2bwp2bdKDKkGhC\u002Byam015GyUZiYvEyyvVK6VAgMBAAGjggIgMIICHDAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud\nEwEB/wQCMAAwHQYDVR0OBBYEFBkWLP9fRmxkZlBHOyXzIYlCfTP4MB8GA1UdIwQY\nMBaAFMXPRqTq9MPAemyVxC2wXpIvJuO5MFcGCCsGAQUFBwEBBEswSTAiBggrBgEF\nBQcwAYYWaHR0cDovL3IxMS5vLmxlbmNyLm9yZzAjBggrBgEFBQcwAoYXaHR0cDov\nL3IxMS5pLmxlbmNyLm9yZy8wKAYDVR0RBCEwH4IQYXV0aC5tYWtzLWl0LmNvbYIL\nbWFrcy1pdC5jb20wEwYDVR0gBAwwCjAIBgZngQwBAgEwggEDBgorBgEEAdZ5AgQC\nBIH0BIHxAO8AdQBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZu7\u002BrOdiEcwAAAY/5\njcSDAAAEAwBGMEQCIEiozrVWl8AuGtl4y37/Fap70in576K355cVKOW3YTzOAiAq\nxqqrNaptO1WjAk09UgdJdcp\u002BixLgU6zuxi713gFxDQB2AN/hVuuqBa\u002B1nA\u002BGcY2o\nwDJOrlbZbqf1pWoB0cE7vlJcAAABj/mNxVEAAAQDAEcwRQIhAO2RqHLAAo3Dj8j\u002B\nRUReVWpVVjmuIVIm5RJjA8Gd68tDAiAonKRBChW6nbCYWK/MrC4JCm\u002BnCk0AQK7S\nTwSQ8EyXQjANBgkqhkiG9w0BAQsFAAOCAQEAqwS1KQtJFWuktlgBcVxP4jfKTzu8\nO620Y8RnEgrPXkoMB5juJkeISnxW11DZvTcRGqvdfT71nPNyqJWsKn/kWinC9Yi9\nXDqSb0/AdKXA81PCBGuZIVUYNf95zC61TvLnJ36qlrG8X/\u002BGDcJU8Afs3WtxuQks\nZVmFSkueuVfFXAK4yQceuhR2IEXponz\u002BgHNh1dDMyonfsdF4dmY3YFDUjUoEVz0K\nCTT0HTYR7c3pLFm184o5ieqnmTPvaN76OTDJt5It50l/Z93wFRXh4RuI1CixuoVo\n2f1SyyXasCilLttQAgabqFTFytvL/W\u002BsLqWBDaX0/QAldWg5i4N4Azvb7g==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIFBjCCAu6gAwIBAgIRAIp9PhPWLzDvI4a9KQdrNPgwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\nWhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\nRW5jcnlwdDEMMAoGA1UEAxMDUjExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEAuoe8XBsAOcvKCs3UZxD5ATylTqVhyybKUvsVAbe5KPUoHu0nsyQYOWcJ\nDAjs4DqwO3cOvfPlOVRBDE6uQdaZdN5R2\u002B97/1i9qLcT9t4x1fJyyXJqC4N0lZxG\nAGQUmfOx2SLZzaiSqhwmej/\u002B71gFewiVgdtxD4774zEJuwm\u002BUE1fj5F2PVqdnoPy\n6cRms\u002BEGZkNIGIBloDcYmpuEMpexsr3E\u002BBUAnSeI\u002B\u002BJjF5ZsmydnS8TbKF5pwnnw\nSVzgJFDhxLyhBax7QG0AtMJBP6dYuC/FXJuluwme8f7rsIU5/agK70XEeOtlKsLP\nXzze41xNG/cLJyuqC0J3U095ah2H2QIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB\nhjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB\n/wIBADAdBgNVHQ4EFgQUxc9GpOr0w8B6bJXELbBeki8m47kwHwYDVR0jBBgwFoAU\nebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC\nhhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG\nA1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN\nAQELBQADggIBAE7iiV0KAxyQOND1H/lxXPjDj7I3iHpvsCUf7b632IYGjukJhM1y\nv4Hz/MrPU0jtvfZpQtSlET41yBOykh0FX\u002Bou1Nj4ScOt9ZmWnO8m2OG0JAtIIE38\n01S0qcYhyOE2G/93ZCkXufBL713qzXnQv5C/viOykNpKqUgxdKlEC\u002BHi9i2DcaR1\ne9KUwQUZRhy5j/PEdEglKg3l9dtD4tuTm7kZtB8v32oOjzHTYw\u002B7KdzdZiw/sBtn\nUfhBPORNuay4pJxmY/WrhSMdzFO2q3Gu3MUBcdo27goYKjL9CTF8j/Zz55yctUoV\naneCWs/ajUX\u002BHypkBTA\u002Bc8LGDLnWO2NKq0YD/pnARkAnYGPfUDoHR9gVSp/qRx\u002BZ\nWghiDLZsMwhN1zjtSC0uBWiugF3vTNzYIEFfaPG7Ws3jDrAMMYebQ95JQ\u002BHIBD/R\nPBuHRTBpqKlyDnkSHDHYPiNX3adPoPAcgdF3H2/W0rmoswMWgTlLn1Wu0mrks7/q\npdWfS6PJ1jty80r2VKsM/Dj3YIDfbjXKdaFU5C\u002B8bhfJGqU3taKauuz0wHVGT3eo\n6FlWkWYtbt4pgdamlwVeZEW\u002BLM7qZEJEsMNPrfC03APKmZsJgpWCDWOKZvkZcvjV\nuYkQ4omYCTX5ohy\u002BknMjdOmdH9c7SpqEWBDC86fiNex\u002BO0XOMEZSa8DA\n-----END CERTIFICATE-----\n","Private":"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"},"auth.maks-it.com":{"Cert":"-----BEGIN CERTIFICATE-----\nMIIF/TCCBOWgAwIBAgISA\u002BHfMDeeJZZXZPh6AIy1PyhAMA0GCSqGSIb3DQEBCwUA\nMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD\nEwNSMTAwHhcNMjQwNjA4MTkzMjU1WhcNMjQwOTA2MTkzMjU0WjAbMRkwFwYDVQQD\nExBhdXRoLm1ha3MtaXQuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC\nAgEA3iP5fkBDs3ZQyGTBdAbcdAbpco9O5WtzAEFlSG5Qzy68pdAGi4IZNS2rnIOd\nCyR0Eo7JAKha6Xj8/dKjVu9rREL/0Mea/c\u002B20YDe/ouTeSZng\u002Bk5yscfWCYowxS6\n0kx2LDJvfi8FMR1PxPV0WzbRnFid4XN668UDUoEAsNvOHUEtdHc0HC4Fn5AxGmXs\ndYan2PzGUTyXDud8akCyOHK4LYjIQnZtPXTDh77bCSDYC4/l5H6X4tdGc3xDJW02\nZh8aYI9qagVZhZX9YFRr49\u002BOiJlRsui2g4oRPy4hPNDD0wQeOlis9Gsl6VDx6JCI\n3HOhmZ3w7nL/HtPx6zamAO0CkhKTjasifV8aQs/hFNatcfC2SFXfKeVzd7lyfdOB\nTSkeGqHjKuu/djc/Jo3Os\u002BlBiLceiHYzYDqYenO25lQJR7exkIPVGY\u002B9vfWdOU\u002BH\nbUVZKBwjy4Dc29f1bSG5Pqlhn\u002BvLqZuBittWhAjy2lF4nZMVPZBpmC\u002BGD0kLWX4x\nvEjEOO6QNDoB76fDddcvnu4pSjwWE/EvcjZsldtnjgrxnfGBcFmY4rQaudNfAl/0\nAuPQgI7VyoxzGxnzv0d\u002BEwR9Ek92p0z\u002BFx1eqvzr8T7U0NqiKWdWgZs4SgycTPSh\na7z9mZ0RRxW4FFVBIA30Ay9SRmN8sMRf9oiqctBnDVvjplECAwEAAaOCAiEwggId\nMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\nDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUBAYb7yYdAxSEQk6HowroXIlXac8wHwYD\nVR0jBBgwFoAUu7zDR6XkvKnGw6RyDBCNojXhyOgwVwYIKwYBBQUHAQEESzBJMCIG\nCCsGAQUFBzABhhZodHRwOi8vcjEwLm8ubGVuY3Iub3JnMCMGCCsGAQUFBzAChhdo\ndHRwOi8vcjEwLmkubGVuY3Iub3JnLzAoBgNVHREEITAfghBhdXRoLm1ha3MtaXQu\nY29tggttYWtzLWl0LmNvbTATBgNVHSAEDDAKMAgGBmeBDAECATCCAQQGCisGAQQB\n1nkCBAIEgfUEgfIA8AB2AEiw42vapkc0D\u002BVqAvqdMOscUgHLVt0sgdm7v6s52IRz\nAAABj/mN4NwAAAQDAEcwRQIgNFpxl\u002Bhm49jYxsaTbJpYj6o1SA0RRGs4spNSqtp3\ntb8CIQC8zl8Qv3eyJLOxXoE7F4yYZtXI9xTMrqg1hHsoJLqfAwB2AO7N0GTV2xrO\nxVy3nbTNE6Iyh0Z8vOzew1FIWUZxH7WbAAABj/mN4N8AAAQDAEcwRQIgVKnCn3gB\nPv1aqt5vDDd/44QLiA/56h82qRHOGptkaSkCIQDEcHea400qS5C63zghYSFKF21Z\nvwGqovbi3fj9sfRueTANBgkqhkiG9w0BAQsFAAOCAQEAPO/6RmCl2y73Vp4bBIWB\nSuhhsZ0BYg4owFMoryWoOFojcG2mgxR\u002B5kokJUWVWxAg0r8OPPzucjFqDNtxmqpY\nA/cHjctO6iXsKK1wR2rEyXUUcqs41uFvSpTJ8Vqpa9QjzwY4w3AdOrTQTPDodQKc\ngPNwrAvlmlmsopYyjo7LsyPDKBwBzilf3W3WJeRTW0ls8micPkX\u002B31s0cH4xj7FQ\nhnHJ1iKQ1elkM0kGscTjA1gwVQRap8\u002BQpZZGj20h9OPpoJYYdPwMu5tePL0bVBzJ\nsuA5nNn1GR/8E\u002BdDNM9Gnv4NGUDi8SQmA4hhlr6lcaYgKZL\u002B4OsXvwnf3si8QKrp\nlA==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIFBTCCAu2gAwIBAgIQS6hSk/eaL6JzBkuoBI110DANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy\nY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNDAzMTMwMDAwMDBa\nFw0yNzAzMTIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBF\nbmNyeXB0MQwwCgYDVQQDEwNSMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQDPV\u002BXmxFQS7bRH/sknWHZGUCiMHT6I3wWd1bUYKb3dtVq/\u002BvbOo76vACFL\nYlpaPAEvxVgD9on/jhFD68G14BQHlo9vH9fnuoE5CXVlt8KvGFs3Jijno/QHK20a\n/6tYvJWuQP/py1fEtVt/eA0YYbwX51TGu0mRzW4Y0YCF7qZlNrx06rxQTOr8IfM4\nFpOUurDTazgGzRYSespSdcitdrLCnF2YRVxvYXvGLe48E1KGAdlX5jgc3421H5KR\nmudKHMxFqHJV8LDmowfs/acbZp4/SItxhHFYyTr6717yW0QrPHTnj7JHwQdqzZq3\nDZb3EoEmUVQK7GH29/Xi8orIlQ2NAgMBAAGjgfgwgfUwDgYDVR0PAQH/BAQDAgGG\nMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATASBgNVHRMBAf8ECDAGAQH/\nAgEAMB0GA1UdDgQWBBS7vMNHpeS8qcbDpHIMEI2iNeHI6DAfBgNVHSMEGDAWgBR5\ntFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAKG\nFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0gBAwwCjAIBgZngQwBAgEwJwYD\nVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVuY3Iub3JnLzANBgkqhkiG9w0B\nAQsFAAOCAgEAkrHnQTfreZ2B5s3iJeE6IOmQRJWjgVzPw139vaBw1bGWKCIL0vIo\nzwzn1OZDjCQiHcFCktEJr59L9MhwTyAWsVrdAfYf\u002BB9haxQnsHKNY67u4s5Lzzfd\nu6PUzeetUK29v\u002BPsPmI2cJkxp\u002BiN3epi4hKu9ZzUPSwMqtCceb7qPVxEbpYxY1p9\n1n5PJKBLBX9eb9LU6l8zSxPWV7bK3lG4XaMJgnT9x3ies7msFtpKK5bDtotij/l0\nGaKeA97pb5uwD9KgWvaFXMIEt8jVTjLEvwRdvCn294GPDF08U8lAkIv7tghluaQh\n1QnlE4SEN4LOECj8dsIGJXpGUk3aU3KkJz9icKy\u002BaUgA\u002B2cP21uh6NcDIS3XyfaZ\nQjmDQ993ChII8SXWupQZVBiIpcWO4RqZk3lr7Bz5MUCwzDIA359e57SSq5CCkY0N\n4B6Vulk7LktfwrdGNVI5BsC9qqxSwSKgRJeZ9wygIaehbHFHFhcBaMDKpiZlBHyz\nrsnnlFXCb5s8HKn5LsUgGvB24L7sGNZP2CX7dhHov\u002BYhD\u002BjozLW2p9W4959Bz2Ei\nRmqDtmiXLnzqTpXbI\u002BsuyCsohKRg6Un0RC47\u002BcpiVwHiXZAW\u002Bcn8eiNIjqbVgXLx\nKPpdzvvtTnOPlC7SQZSYmdunr3Bf9b77AiC/ZidstK36dRILKz7OA54=\n-----END CERTIFICATE-----\n","Private":"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"}},"AccountKey":"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","Id":null,"Key":{"kty":"RSA","kid":null,"use":null,"n":"rnPtd1dxav2zok86-2oqfXis9DyGdaYyAfB9Qk5gmzNztit8xoOCcpw68STS3-L8q91KOfV5rHWfR9tnhBa3vwif5OVwr942a1YDUsxx7W-fToj5CR79gF26jQfhOvbLJ2Os-WhBLWQmDumWAf5-ySwI-LFHpL32JB_JZdyR5hGQI33VqJiHe14_dQnmF9rVxoUp2Q-Shtaz3HPGZdQlhkfxJKsCmIoNtU7MRhlWHRWY5-ZUxHNkVYz_Hrz9BIp2a4FBY2S45aiSYWUxSSVcE1cymHJCCFBYWGXpu5in1BhN-Sx-F6PK4otQcwmegpl9SE7cxBywGAogqAesRI0A2YorloxyR8Bm6piF4pksbNtzulugoPA-9wk1M3rVARt5m_g7QEftDch5eMq9doaanCJCFvjT7ow4RBGMw9LevRyYNfasi1DHKb9d0rv--TrxGvunfpt3olVoSiVE0qDqbmcoAnX8_xXp2j9LSqUNuT4mE4eZTrqCePdD4fQULU2wDnfrp7-rQILA6MMtuyj662KhpJIibl_EqWjBxvMuB8jfAsyu_RuUOuBLWIkbbKqcCWq5VeBAavtJG4R7VVqS1WEOGsuq8igmTJHy7Nwry8Vsg47hIdRBdRIUnCa2hEoKsafsaRH7S_fvTD31s5lJyp2dIT8ZTdVFQcIlFBHHsW8","e":"AQAB","d":null,"p":null,"q":null,"dp":null,"dq":null,"qi":null,"oth":null,"alg":null},"Location":"https://acme-v02.api.letsencrypt.org/acme/acct/1771419987"} \ No newline at end of file +{"AccountId":"b2a279ae-0306-4d77-9408-66a078f34fa6","Description":"Main Prod","ChallengeType":"http-01","Contacts":["maksym.sadovnychyy@gmail.com"],"CachedCerts":{"maks-it.com":{"Cert":"-----BEGIN CERTIFICATE-----\nMIIF9zCCBN\u002BgAwIBAgISA\u002BeXxGMTkVzdcOun4cTxIFBuMA0GCSqGSIb3DQEBCwUA\nMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD\nEwNSMTEwHhcNMjQwNjA4MTkzMjQ4WhcNMjQwOTA2MTkzMjQ3WjAWMRQwEgYDVQQD\nEwttYWtzLWl0LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIZ5\nBv\u002BhJjZfolnnM23Fk8a6l9l3ezMVTxMSZM62sZ8toC\u002BWSBcx0mGJ6qLhTF348xlF\nGQzzZ7OLHv\u002BZq/wn0in8Qlu/n7OWlFHBUTBCy5bp2MXxrstJaY/pfDz2WYVgBDvh\nsPiRdfA/UY1f05qlUFamENuTNzDSflIKHIG0HCu31B4mA7v423Ai8vrTm3yav8KH\n10SnwSz0lCXMrB7V94BxCpqFYLUZLS8oJjIn26xEg4rBWCJlxKyBQq1T/KhuTfcg\nOMNjyR1bZU1\u002Bj31XRy0qcNsOfCsHAk/Ojae6q9Oy7ACFd31pTAmmf9LQlEtlD/fx\nszQLfTAKwY8dIWDJ5Va\u002BWhYlJSBF95cU3iJ/vFyQn1FaylAQ2P\u002B0/esXPSwJxQrE\nNs5lRunzwiHANYXQN\u002B9I9o4cL6n5HO1/orCSg/4FDJUNq7gnLlZNDewiaRGk0497\nONnsnt\u002BOLvaa7Jhdgx5GYXTaB3x9IBgNqVYm9CY5\u002ByQnbhXHPPVHnpCnWgahmCk6\nUzxREKCpy5Z4fU/6/YQz5/DOfeEdFemOZcgTIJguiI7Q\u002BHQyOIsiKbMlOcIPr4Kg\nUoStj6gZrWnHJirpW8TzeMnCiX3P8bICFj4mu4LKbc/5\u002BJBfBuq4Y53xGvZHTS2s\nI/J/2bwp2bdKDKkGhC\u002Byam015GyUZiYvEyyvVK6VAgMBAAGjggIgMIICHDAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud\nEwEB/wQCMAAwHQYDVR0OBBYEFBkWLP9fRmxkZlBHOyXzIYlCfTP4MB8GA1UdIwQY\nMBaAFMXPRqTq9MPAemyVxC2wXpIvJuO5MFcGCCsGAQUFBwEBBEswSTAiBggrBgEF\nBQcwAYYWaHR0cDovL3IxMS5vLmxlbmNyLm9yZzAjBggrBgEFBQcwAoYXaHR0cDov\nL3IxMS5pLmxlbmNyLm9yZy8wKAYDVR0RBCEwH4IQYXV0aC5tYWtzLWl0LmNvbYIL\nbWFrcy1pdC5jb20wEwYDVR0gBAwwCjAIBgZngQwBAgEwggEDBgorBgEEAdZ5AgQC\nBIH0BIHxAO8AdQBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZu7\u002BrOdiEcwAAAY/5\njcSDAAAEAwBGMEQCIEiozrVWl8AuGtl4y37/Fap70in576K355cVKOW3YTzOAiAq\nxqqrNaptO1WjAk09UgdJdcp\u002BixLgU6zuxi713gFxDQB2AN/hVuuqBa\u002B1nA\u002BGcY2o\nwDJOrlbZbqf1pWoB0cE7vlJcAAABj/mNxVEAAAQDAEcwRQIhAO2RqHLAAo3Dj8j\u002B\nRUReVWpVVjmuIVIm5RJjA8Gd68tDAiAonKRBChW6nbCYWK/MrC4JCm\u002BnCk0AQK7S\nTwSQ8EyXQjANBgkqhkiG9w0BAQsFAAOCAQEAqwS1KQtJFWuktlgBcVxP4jfKTzu8\nO620Y8RnEgrPXkoMB5juJkeISnxW11DZvTcRGqvdfT71nPNyqJWsKn/kWinC9Yi9\nXDqSb0/AdKXA81PCBGuZIVUYNf95zC61TvLnJ36qlrG8X/\u002BGDcJU8Afs3WtxuQks\nZVmFSkueuVfFXAK4yQceuhR2IEXponz\u002BgHNh1dDMyonfsdF4dmY3YFDUjUoEVz0K\nCTT0HTYR7c3pLFm184o5ieqnmTPvaN76OTDJt5It50l/Z93wFRXh4RuI1CixuoVo\n2f1SyyXasCilLttQAgabqFTFytvL/W\u002BsLqWBDaX0/QAldWg5i4N4Azvb7g==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIFBjCCAu6gAwIBAgIRAIp9PhPWLzDvI4a9KQdrNPgwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\nWhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\nRW5jcnlwdDEMMAoGA1UEAxMDUjExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEAuoe8XBsAOcvKCs3UZxD5ATylTqVhyybKUvsVAbe5KPUoHu0nsyQYOWcJ\nDAjs4DqwO3cOvfPlOVRBDE6uQdaZdN5R2\u002B97/1i9qLcT9t4x1fJyyXJqC4N0lZxG\nAGQUmfOx2SLZzaiSqhwmej/\u002B71gFewiVgdtxD4774zEJuwm\u002BUE1fj5F2PVqdnoPy\n6cRms\u002BEGZkNIGIBloDcYmpuEMpexsr3E\u002BBUAnSeI\u002B\u002BJjF5ZsmydnS8TbKF5pwnnw\nSVzgJFDhxLyhBax7QG0AtMJBP6dYuC/FXJuluwme8f7rsIU5/agK70XEeOtlKsLP\nXzze41xNG/cLJyuqC0J3U095ah2H2QIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB\nhjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB\n/wIBADAdBgNVHQ4EFgQUxc9GpOr0w8B6bJXELbBeki8m47kwHwYDVR0jBBgwFoAU\nebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC\nhhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG\nA1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN\nAQELBQADggIBAE7iiV0KAxyQOND1H/lxXPjDj7I3iHpvsCUf7b632IYGjukJhM1y\nv4Hz/MrPU0jtvfZpQtSlET41yBOykh0FX\u002Bou1Nj4ScOt9ZmWnO8m2OG0JAtIIE38\n01S0qcYhyOE2G/93ZCkXufBL713qzXnQv5C/viOykNpKqUgxdKlEC\u002BHi9i2DcaR1\ne9KUwQUZRhy5j/PEdEglKg3l9dtD4tuTm7kZtB8v32oOjzHTYw\u002B7KdzdZiw/sBtn\nUfhBPORNuay4pJxmY/WrhSMdzFO2q3Gu3MUBcdo27goYKjL9CTF8j/Zz55yctUoV\naneCWs/ajUX\u002BHypkBTA\u002Bc8LGDLnWO2NKq0YD/pnARkAnYGPfUDoHR9gVSp/qRx\u002BZ\nWghiDLZsMwhN1zjtSC0uBWiugF3vTNzYIEFfaPG7Ws3jDrAMMYebQ95JQ\u002BHIBD/R\nPBuHRTBpqKlyDnkSHDHYPiNX3adPoPAcgdF3H2/W0rmoswMWgTlLn1Wu0mrks7/q\npdWfS6PJ1jty80r2VKsM/Dj3YIDfbjXKdaFU5C\u002B8bhfJGqU3taKauuz0wHVGT3eo\n6FlWkWYtbt4pgdamlwVeZEW\u002BLM7qZEJEsMNPrfC03APKmZsJgpWCDWOKZvkZcvjV\nuYkQ4omYCTX5ohy\u002BknMjdOmdH9c7SpqEWBDC86fiNex\u002BO0XOMEZSa8DA\n-----END CERTIFICATE-----\n","Private":"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"},"auth.maks-it.com":{"Cert":"-----BEGIN CERTIFICATE-----\nMIIF/TCCBOWgAwIBAgISA\u002BHfMDeeJZZXZPh6AIy1PyhAMA0GCSqGSIb3DQEBCwUA\nMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD\nEwNSMTAwHhcNMjQwNjA4MTkzMjU1WhcNMjQwOTA2MTkzMjU0WjAbMRkwFwYDVQQD\nExBhdXRoLm1ha3MtaXQuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC\nAgEA3iP5fkBDs3ZQyGTBdAbcdAbpco9O5WtzAEFlSG5Qzy68pdAGi4IZNS2rnIOd\nCyR0Eo7JAKha6Xj8/dKjVu9rREL/0Mea/c\u002B20YDe/ouTeSZng\u002Bk5yscfWCYowxS6\n0kx2LDJvfi8FMR1PxPV0WzbRnFid4XN668UDUoEAsNvOHUEtdHc0HC4Fn5AxGmXs\ndYan2PzGUTyXDud8akCyOHK4LYjIQnZtPXTDh77bCSDYC4/l5H6X4tdGc3xDJW02\nZh8aYI9qagVZhZX9YFRr49\u002BOiJlRsui2g4oRPy4hPNDD0wQeOlis9Gsl6VDx6JCI\n3HOhmZ3w7nL/HtPx6zamAO0CkhKTjasifV8aQs/hFNatcfC2SFXfKeVzd7lyfdOB\nTSkeGqHjKuu/djc/Jo3Os\u002BlBiLceiHYzYDqYenO25lQJR7exkIPVGY\u002B9vfWdOU\u002BH\nbUVZKBwjy4Dc29f1bSG5Pqlhn\u002BvLqZuBittWhAjy2lF4nZMVPZBpmC\u002BGD0kLWX4x\nvEjEOO6QNDoB76fDddcvnu4pSjwWE/EvcjZsldtnjgrxnfGBcFmY4rQaudNfAl/0\nAuPQgI7VyoxzGxnzv0d\u002BEwR9Ek92p0z\u002BFx1eqvzr8T7U0NqiKWdWgZs4SgycTPSh\na7z9mZ0RRxW4FFVBIA30Ay9SRmN8sMRf9oiqctBnDVvjplECAwEAAaOCAiEwggId\nMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\nDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUBAYb7yYdAxSEQk6HowroXIlXac8wHwYD\nVR0jBBgwFoAUu7zDR6XkvKnGw6RyDBCNojXhyOgwVwYIKwYBBQUHAQEESzBJMCIG\nCCsGAQUFBzABhhZodHRwOi8vcjEwLm8ubGVuY3Iub3JnMCMGCCsGAQUFBzAChhdo\ndHRwOi8vcjEwLmkubGVuY3Iub3JnLzAoBgNVHREEITAfghBhdXRoLm1ha3MtaXQu\nY29tggttYWtzLWl0LmNvbTATBgNVHSAEDDAKMAgGBmeBDAECATCCAQQGCisGAQQB\n1nkCBAIEgfUEgfIA8AB2AEiw42vapkc0D\u002BVqAvqdMOscUgHLVt0sgdm7v6s52IRz\nAAABj/mN4NwAAAQDAEcwRQIgNFpxl\u002Bhm49jYxsaTbJpYj6o1SA0RRGs4spNSqtp3\ntb8CIQC8zl8Qv3eyJLOxXoE7F4yYZtXI9xTMrqg1hHsoJLqfAwB2AO7N0GTV2xrO\nxVy3nbTNE6Iyh0Z8vOzew1FIWUZxH7WbAAABj/mN4N8AAAQDAEcwRQIgVKnCn3gB\nPv1aqt5vDDd/44QLiA/56h82qRHOGptkaSkCIQDEcHea400qS5C63zghYSFKF21Z\nvwGqovbi3fj9sfRueTANBgkqhkiG9w0BAQsFAAOCAQEAPO/6RmCl2y73Vp4bBIWB\nSuhhsZ0BYg4owFMoryWoOFojcG2mgxR\u002B5kokJUWVWxAg0r8OPPzucjFqDNtxmqpY\nA/cHjctO6iXsKK1wR2rEyXUUcqs41uFvSpTJ8Vqpa9QjzwY4w3AdOrTQTPDodQKc\ngPNwrAvlmlmsopYyjo7LsyPDKBwBzilf3W3WJeRTW0ls8micPkX\u002B31s0cH4xj7FQ\nhnHJ1iKQ1elkM0kGscTjA1gwVQRap8\u002BQpZZGj20h9OPpoJYYdPwMu5tePL0bVBzJ\nsuA5nNn1GR/8E\u002BdDNM9Gnv4NGUDi8SQmA4hhlr6lcaYgKZL\u002B4OsXvwnf3si8QKrp\nlA==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIFBTCCAu2gAwIBAgIQS6hSk/eaL6JzBkuoBI110DANBgkqhkiG9w0BAQsFADBP\nMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy\nY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNDAzMTMwMDAwMDBa\nFw0yNzAzMTIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBF\nbmNyeXB0MQwwCgYDVQQDEwNSMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQDPV\u002BXmxFQS7bRH/sknWHZGUCiMHT6I3wWd1bUYKb3dtVq/\u002BvbOo76vACFL\nYlpaPAEvxVgD9on/jhFD68G14BQHlo9vH9fnuoE5CXVlt8KvGFs3Jijno/QHK20a\n/6tYvJWuQP/py1fEtVt/eA0YYbwX51TGu0mRzW4Y0YCF7qZlNrx06rxQTOr8IfM4\nFpOUurDTazgGzRYSespSdcitdrLCnF2YRVxvYXvGLe48E1KGAdlX5jgc3421H5KR\nmudKHMxFqHJV8LDmowfs/acbZp4/SItxhHFYyTr6717yW0QrPHTnj7JHwQdqzZq3\nDZb3EoEmUVQK7GH29/Xi8orIlQ2NAgMBAAGjgfgwgfUwDgYDVR0PAQH/BAQDAgGG\nMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATASBgNVHRMBAf8ECDAGAQH/\nAgEAMB0GA1UdDgQWBBS7vMNHpeS8qcbDpHIMEI2iNeHI6DAfBgNVHSMEGDAWgBR5\ntFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAKG\nFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0gBAwwCjAIBgZngQwBAgEwJwYD\nVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVuY3Iub3JnLzANBgkqhkiG9w0B\nAQsFAAOCAgEAkrHnQTfreZ2B5s3iJeE6IOmQRJWjgVzPw139vaBw1bGWKCIL0vIo\nzwzn1OZDjCQiHcFCktEJr59L9MhwTyAWsVrdAfYf\u002BB9haxQnsHKNY67u4s5Lzzfd\nu6PUzeetUK29v\u002BPsPmI2cJkxp\u002BiN3epi4hKu9ZzUPSwMqtCceb7qPVxEbpYxY1p9\n1n5PJKBLBX9eb9LU6l8zSxPWV7bK3lG4XaMJgnT9x3ies7msFtpKK5bDtotij/l0\nGaKeA97pb5uwD9KgWvaFXMIEt8jVTjLEvwRdvCn294GPDF08U8lAkIv7tghluaQh\n1QnlE4SEN4LOECj8dsIGJXpGUk3aU3KkJz9icKy\u002BaUgA\u002B2cP21uh6NcDIS3XyfaZ\nQjmDQ993ChII8SXWupQZVBiIpcWO4RqZk3lr7Bz5MUCwzDIA359e57SSq5CCkY0N\n4B6Vulk7LktfwrdGNVI5BsC9qqxSwSKgRJeZ9wygIaehbHFHFhcBaMDKpiZlBHyz\nrsnnlFXCb5s8HKn5LsUgGvB24L7sGNZP2CX7dhHov\u002BYhD\u002BjozLW2p9W4959Bz2Ei\nRmqDtmiXLnzqTpXbI\u002BsuyCsohKRg6Un0RC47\u002BcpiVwHiXZAW\u002Bcn8eiNIjqbVgXLx\nKPpdzvvtTnOPlC7SQZSYmdunr3Bf9b77AiC/ZidstK36dRILKz7OA54=\n-----END CERTIFICATE-----\n","Private":"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"}},"AccountKey":"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","Id":null,"Key":{"kty":"RSA","kid":null,"use":null,"n":"rnPtd1dxav2zok86-2oqfXis9DyGdaYyAfB9Qk5gmzNztit8xoOCcpw68STS3-L8q91KOfV5rHWfR9tnhBa3vwif5OVwr942a1YDUsxx7W-fToj5CR79gF26jQfhOvbLJ2Os-WhBLWQmDumWAf5-ySwI-LFHpL32JB_JZdyR5hGQI33VqJiHe14_dQnmF9rVxoUp2Q-Shtaz3HPGZdQlhkfxJKsCmIoNtU7MRhlWHRWY5-ZUxHNkVYz_Hrz9BIp2a4FBY2S45aiSYWUxSSVcE1cymHJCCFBYWGXpu5in1BhN-Sx-F6PK4otQcwmegpl9SE7cxBywGAogqAesRI0A2YorloxyR8Bm6piF4pksbNtzulugoPA-9wk1M3rVARt5m_g7QEftDch5eMq9doaanCJCFvjT7ow4RBGMw9LevRyYNfasi1DHKb9d0rv--TrxGvunfpt3olVoSiVE0qDqbmcoAnX8_xXp2j9LSqUNuT4mE4eZTrqCePdD4fQULU2wDnfrp7-rQILA6MMtuyj662KhpJIibl_EqWjBxvMuB8jfAsyu_RuUOuBLWIkbbKqcCWq5VeBAavtJG4R7VVqS1WEOGsuq8igmTJHy7Nwry8Vsg47hIdRBdRIUnCa2hEoKsafsaRH7S_fvTD31s5lJyp2dIT8ZTdVFQcIlFBHHsW8","e":"AQAB","d":null,"p":null,"q":null,"dp":null,"dq":null,"qi":null,"oth":null,"alg":null},"Location":"https://acme-v02.api.letsencrypt.org/acme/acct/1771419987"} \ No newline at end of file