diff --git a/src/ClientApp/.vscode/settings.json b/src/ClientApp/.vscode/settings.json index 3ba6995..e35422a 100644 --- a/src/ClientApp/.vscode/settings.json +++ b/src/ClientApp/.vscode/settings.json @@ -1,8 +1,8 @@ { "editor.tabSize": 2, - // "editor.codeActionsOnSave": { - // "source.fixAll.eslint": true - // }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, diff --git a/src/ClientApp/ApiRoutes.tsx b/src/ClientApp/ApiRoutes.tsx index 4c9c47e..2d8ec84 100644 --- a/src/ClientApp/ApiRoutes.tsx +++ b/src/ClientApp/ApiRoutes.tsx @@ -2,7 +2,7 @@ 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}/contacts/{index}', + CACHE_ACCOUNT_CONTACT = 'api/cache/account/{accountId}/contact/{index}', CACHE_ACCOUNT_HOSTNAMES = 'api/cache/account/{accountId}/hostnames' // CERTS_FLOW_CONFIGURE_CLIENT = `api/CertsFlow/ConfigureClient`, diff --git a/src/ClientApp/app/page.tsx b/src/ClientApp/app/page.tsx index 429a59e..218fb1e 100644 --- a/src/ClientApp/app/page.tsx +++ b/src/ClientApp/app/page.tsx @@ -21,17 +21,20 @@ export default function Page() { const { value: newContact, error: contactError, - handleChange: handleContactChange - } = useValidation({ + handleChange: handleContactChange, + reset: resetContact + } = useValidation({ initialValue: '', validateFn: isValidEmail, errorMessage: 'Invalid email format.' }) + const { value: newHostname, error: hostnameError, - handleChange: handleHostnameChange - } = useValidation({ + handleChange: handleHostnameChange, + reset: resetHostname + } = useValidation({ initialValue: '', validateFn: isValidHostname, errorMessage: 'Invalid hostname format.' @@ -110,25 +113,25 @@ export default function Page() { } const addContact = (accountId: string) => { - if (newContact.trim() === '' || contactError) { + if (newContact === '' || contactError) { return } if ( accounts .find((account) => account.accountId === accountId) - ?.contacts.includes(newContact.trim()) + ?.contacts.includes(newContact) ) return setAccounts( accounts.map((account) => account.accountId === accountId - ? { ...account, contacts: [...account.contacts, newContact.trim()] } + ? { ...account, contacts: [...account.contacts, newContact] } : account ) ) - handleContactChange('') + resetContact() } const deleteHostname = (accountId: string, hostname: string) => { @@ -153,14 +156,14 @@ export default function Page() { } const addHostname = (accountId: string) => { - if (newHostname.trim() === '' || hostnameError) { + if (newHostname === '' || hostnameError) { return } if ( accounts .find((account) => account.accountId === accountId) - ?.hostnames.some((h) => h.hostname === newHostname.trim()) + ?.hostnames.some((h) => h.hostname === newHostname) ) return @@ -172,7 +175,7 @@ export default function Page() { hostnames: [ ...account.hostnames, { - hostname: newHostname.trim(), + hostname: newHostname, expires: new Date(), isUpcomingExpire: false } @@ -181,7 +184,7 @@ export default function Page() { : account ) ) - handleHostnameChange('') + resetHostname() } const handleSubmit = async ( @@ -279,14 +282,15 @@ export default function Page() { className="text-gray-700 flex justify-between items-center mb-2" > {contact} - + ))} @@ -301,13 +305,15 @@ export default function Page() { inputClassName="border p-2 rounded w-full" errorClassName="text-red-500 text-sm mt-1" className="mr-2 flex-grow" - /> - + addContact(account.accountId)} + className="bg-green-500 text-white p-2 rounded ml-2" + > + + +
@@ -329,14 +335,15 @@ export default function Page() { : 'Not Upcoming'}
- + ))} @@ -351,25 +358,27 @@ export default function Page() { inputClassName="border p-2 rounded w-full" errorClassName="text-red-500 text-sm mt-1" className="mr-2 flex-grow" - /> - + addHostname(account.accountId)} + className="bg-green-500 text-white p-2 rounded ml-2" + > + + +
- + Submit diff --git a/src/ClientApp/app/register/page.tsx b/src/ClientApp/app/register/page.tsx index 795b065..442c440 100644 --- a/src/ClientApp/app/register/page.tsx +++ b/src/ClientApp/app/register/page.tsx @@ -10,24 +10,26 @@ import { } from '@/hooks/useValidation' import { CustomButton, CustomInput } from '@/controls' import { FaTrash, FaPlus } from 'react-icons/fa' -import { GetAccountResponse } from '@/models/letsEncryptServer/cache/responses/GetAccountResponse' import { deepCopy } from '../functions' - -interface CacheAccount { - description?: string - contacts: string[] - hostnames: string[] -} +import { + PostAccountRequest, + validatePostAccountRequest +} from '@/models/letsEncryptServer/certsFlow/PostAccountRequest' +import App from 'next/app' +import { useAppDispatch } from '@/redux/store' +import { showToast } from '@/redux/slices/toastSlice' const RegisterPage = () => { - const [account, setAccount] = useState(null) + const [account, setAccount] = useState(null) + + const dispatch = useAppDispatch() const { value: newContact, error: contactError, handleChange: handleContactChange, reset: resetContact - } = useValidation({ + } = useValidation({ initialValue: '', validateFn: isValidContact, errorMessage: 'Invalid contact. Must be a valid email or phone number.' @@ -38,7 +40,7 @@ const RegisterPage = () => { error: hostnameError, handleChange: handleHostnameChange, reset: resetHostname - } = useValidation({ + } = useValidation({ initialValue: '', validateFn: isValidHostname, errorMessage: 'Invalid hostname format.' @@ -55,10 +57,17 @@ const RegisterPage = () => { const handleDescription = (description: string) => {} const handleAddContact = () => { - if (newContact !== '' || contactError) return + if ( + newContact === '' || + account?.contacts.includes(newContact) || + contactError !== '' + ) { + resetContact() + return + } setAccount((prev) => { - const newAccount: CacheAccount = + const newAccount: PostAccountRequest = prev !== null ? deepCopy(prev) : { @@ -75,10 +84,17 @@ const RegisterPage = () => { } const handleAddHostname = () => { - if (newHostname !== '' || hostnameError) return + if ( + newHostname === '' || + account?.hostnames.includes(newHostname) || + hostnameError !== '' + ) { + resetHostname() + return + } setAccount((prev) => { - const newAccount: CacheAccount = + const newAccount: PostAccountRequest = prev !== null ? deepCopy(prev) : { @@ -119,6 +135,17 @@ const RegisterPage = () => { const handleSubmit = async (e: FormEvent) => { e.preventDefault() + const error = validatePostAccountRequest(account) + if (error) { + console.error(`Validation failed: ${error}`) + // dipatch toasterror + dispatch(showToast({ message: error, type: 'error' })) + + return + } + + // httpService.post('', account) + console.log(account) } @@ -148,13 +175,13 @@ const RegisterPage = () => { className="text-gray-700 flex justify-between items-center mb-2" > {contact} - + ))} @@ -169,14 +196,15 @@ const RegisterPage = () => { inputClassName="border p-2 rounded w-full" errorClassName="text-red-500 text-sm mt-1" className="mr-2 flex-grow" - /> - + + + +
@@ -188,13 +216,13 @@ const RegisterPage = () => { className="text-gray-700 flex justify-between items-center mb-2" > {hostname} - + ))} @@ -209,14 +237,15 @@ const RegisterPage = () => { inputClassName="border p-2 rounded w-full" errorClassName="text-red-500 text-sm mt-1" className="mr-2 flex-grow" - /> - + + + +
= ({ +const CustomInput: FC = ({ value, onChange, placeholder = '', @@ -23,23 +24,29 @@ const CustomInput: React.FC = ({ title, inputClassName = '', errorClassName = '', - className = '' + className = '', + children // Added for additional elements }) => { const handleChange = (e: React.ChangeEvent) => { onChange?.(e.target.value) } return ( -
- {title && } - - {error &&

{error}

} +
+ {title && } +
+ + {children &&
{children}
} +
+ {error && ( +

{error}

+ )}
) } diff --git a/src/ClientApp/hooks/useValidation.tsx b/src/ClientApp/hooks/useValidation.tsx index 1710c9f..7f36ec6 100644 --- a/src/ClientApp/hooks/useValidation.tsx +++ b/src/ClientApp/hooks/useValidation.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' // Helper functions for validation const isValidEmail = (email: string) => { @@ -21,38 +21,68 @@ const isValidHostname = (hostname: string) => { } // Props interface for useValidation hook -interface UseValidationProps { - initialValue: string - validateFn: (value: string) => boolean +interface UseValidationProps { + initialValue: T + validateFn: (value: T) => boolean errorMessage: string + emptyFieldMessage?: string // Optional custom message for empty fields + defaultResetValue?: T // Optional default reset value } // Custom hook for input validation -const useValidation = ({ - initialValue, - validateFn, - errorMessage -}: UseValidationProps) => { - const [value, setValue] = useState(initialValue) +const useValidation = ( + props: UseValidationProps +) => { + const { + initialValue, + validateFn, + errorMessage, + emptyFieldMessage = 'This field cannot be empty.', // Default message + defaultResetValue + } = props + + const [value, setValue] = useState(initialValue) const [error, setError] = useState('') - const handleChange = (newValue: string) => { - console.log(newValue) - setValue(newValue) - if (newValue.trim() === '') { - setError('This field cannot be empty.') - } else if (!validateFn(newValue.trim())) { - setError(errorMessage) - } else { - setError('') - } - } + const handleChange = useCallback( + (newValue: T) => { + setValue(newValue) + const stringValue = + newValue instanceof Date + ? newValue.toISOString() + : newValue.toString().trim() + if (stringValue === '') { + setError(emptyFieldMessage) + } else if (!validateFn(newValue)) { + setError(errorMessage) + } else { + setError('') + } + }, + [emptyFieldMessage, errorMessage, validateFn] + ) useEffect(() => { handleChange(initialValue) - }, [initialValue]) + }, [initialValue, handleChange]) - return { value, error, handleChange, reset: () => setValue('') } + const reset = useCallback(() => { + const resetValue = + defaultResetValue !== undefined ? defaultResetValue : initialValue + + setValue(resetValue) + const stringValue = + resetValue instanceof Date + ? resetValue.toISOString() + : resetValue.toString().trim() + if (stringValue === '') { + setError(emptyFieldMessage) + } else { + setError('') + } + }, [defaultResetValue, initialValue, emptyFieldMessage]) + + return { value, error, handleChange, reset } } export { diff --git a/src/ClientApp/models/PatchAction.ts b/src/ClientApp/models/PatchAction.ts new file mode 100644 index 0000000..cb4e914 --- /dev/null +++ b/src/ClientApp/models/PatchAction.ts @@ -0,0 +1,5 @@ +export interface PatchAction { + op: PatchOperation // Enum for operation type + index?: number // Index for the operation (for arrays/lists) + value?: T // Value for the operation +} diff --git a/src/ClientApp/models/PatchOperation.ts b/src/ClientApp/models/PatchOperation.ts new file mode 100644 index 0000000..a00d27b --- /dev/null +++ b/src/ClientApp/models/PatchOperation.ts @@ -0,0 +1,5 @@ +enum PatchOperation { + Add, + Remove, + Replace +} diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PatchAccountRequest.ts b/src/ClientApp/models/letsEncryptServer/cache/requests/PatchAccountRequest.ts new file mode 100644 index 0000000..966a8d0 --- /dev/null +++ b/src/ClientApp/models/letsEncryptServer/cache/requests/PatchAccountRequest.ts @@ -0,0 +1,6 @@ +import { PatchAction } from '@/models/PatchAction' + +export interface PatchAccountRequest { + description?: PatchAction + contacts?: PatchAction[] +} diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PatchContactsRequest.ts b/src/ClientApp/models/letsEncryptServer/cache/requests/PatchContactsRequest.ts new file mode 100644 index 0000000..376f68a --- /dev/null +++ b/src/ClientApp/models/letsEncryptServer/cache/requests/PatchContactsRequest.ts @@ -0,0 +1,5 @@ +import { PatchAction } from '@/models/PatchAction' + +export interface PatchContactsRequest { + contacts: PatchAction[] +} diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PostContactsRequest.ts b/src/ClientApp/models/letsEncryptServer/cache/requests/PostContactsRequest.ts new file mode 100644 index 0000000..2239abf --- /dev/null +++ b/src/ClientApp/models/letsEncryptServer/cache/requests/PostContactsRequest.ts @@ -0,0 +1,3 @@ +export interface PostContactsRequest { + contacts: string[] +} diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PutAccountRequest.ts b/src/ClientApp/models/letsEncryptServer/cache/requests/PutAccountRequest.ts new file mode 100644 index 0000000..f57164c --- /dev/null +++ b/src/ClientApp/models/letsEncryptServer/cache/requests/PutAccountRequest.ts @@ -0,0 +1,4 @@ +export interface PutAccountRequest { + description: string + contacts: string[] +} diff --git a/src/ClientApp/models/letsEncryptServer/cache/requests/PutContactsRequest.ts b/src/ClientApp/models/letsEncryptServer/cache/requests/PutContactsRequest.ts new file mode 100644 index 0000000..0ca5ecc --- /dev/null +++ b/src/ClientApp/models/letsEncryptServer/cache/requests/PutContactsRequest.ts @@ -0,0 +1,3 @@ +export interface PutContactsRequest { + contacts: string[] +} diff --git a/src/ClientApp/models/letsEncryptServer/certsFlow/PostAccountRequest.ts b/src/ClientApp/models/letsEncryptServer/certsFlow/PostAccountRequest.ts new file mode 100644 index 0000000..4946dc5 --- /dev/null +++ b/src/ClientApp/models/letsEncryptServer/certsFlow/PostAccountRequest.ts @@ -0,0 +1,32 @@ +import { isValidContact, isValidHostname } from '@/hooks/useValidation' + +export interface PostAccountRequest { + description?: string + contacts: string[] + hostnames: string[] +} + +const validatePostAccountRequest = ( + request: PostAccountRequest | null +): string | null => { + if (request === null) return 'Request is null' + + // Validate contacts + for (const contact of request.contacts) { + if (!isValidContact(contact)) { + return `Invalid contact: ${contact}` + } + } + + // Validate hostnames + for (const hostname of request.hostnames) { + if (!isValidHostname(hostname)) { + return `Invalid hostname: ${hostname}` + } + } + + // If all validations pass, return null + return null +} + +export { validatePostAccountRequest } diff --git a/src/ClientApp/redux/store.ts b/src/ClientApp/redux/store.ts index 7714f19..71e80e2 100644 --- a/src/ClientApp/redux/store.ts +++ b/src/ClientApp/redux/store.ts @@ -1,5 +1,11 @@ import { configureStore } from '@reduxjs/toolkit' -import loaderReducer from '@/redux/slices//loaderSlice' +import { + TypedUseSelectorHook, + useDispatch, + useSelector, + useStore +} from 'react-redux' +import loaderReducer from '@/redux/slices/loaderSlice' import toastReducer from '@/redux/slices/toastSlice' export const store = configureStore({ @@ -11,3 +17,9 @@ export const store = configureStore({ export type RootState = ReturnType export type AppDispatch = typeof store.dispatch +export type AppStore = typeof store + +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch = () => useDispatch() +export const useAppSelector: TypedUseSelectorHook = useSelector +export const useAppStore = () => useStore() diff --git a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs index fc5eb33..9ffef1b 100644 --- a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs +++ b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs @@ -4,9 +4,8 @@ using DomainResults.Common; using MaksIT.LetsEncryptServer.Services; -using Models.LetsEncryptServer.CertsFlow.Requests; -using Models.LetsEncryptServer.Cache.Responses; using MaksIT.LetsEncrypt.Entities; +using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; namespace MaksIT.LetsEncryptServer.BackgroundServices { public class AutoRenewal : BackgroundService { @@ -62,7 +61,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { return IDomainResult.Success(); } - var renewResult = await RenewCertificatesForHostnames(cache.AccountId, cache.Contacts, hostnames); + var renewResult = await RenewCertificatesForHostnames(cache.AccountId, cache.Description, cache.Contacts, hostnames); if (!renewResult.IsSuccess) return renewResult; @@ -71,7 +70,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { return IDomainResult.Success(); } - private async Task RenewCertificatesForHostnames(Guid accountId, string[] contacts, string[] hostnames) { + private async Task RenewCertificatesForHostnames(Guid accountId, string description, string[] contacts, string[] hostnames) { var (sessionId, configureClientResult) = await _certsFlowService.ConfigureClientAsync(); if (!configureClientResult.IsSuccess || sessionId == null) { LogErrors(configureClientResult.Errors); @@ -81,6 +80,7 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { var sessionIdValue = sessionId.Value; var (_, initResult) = await _certsFlowService.InitAsync(sessionIdValue, accountId, new InitRequest { + Description = description, Contacts = contacts }); if (!initResult.IsSuccess) { diff --git a/src/LetsEncryptServer/Controllers/CacheController.cs b/src/LetsEncryptServer/Controllers/CacheController.cs index 552ade3..539bffc 100644 --- a/src/LetsEncryptServer/Controllers/CacheController.cs +++ b/src/LetsEncryptServer/Controllers/CacheController.cs @@ -57,7 +57,7 @@ public class CacheController : ControllerBase { return result.ToActionResult(); } - [HttpDelete("account/{accountId:guid}/contacts/{index:int}")] + [HttpDelete("account/{accountId:guid}/contact/{index:int}")] public async Task DeleteContact(Guid accountId, int index) { var result = await _cacheService.DeleteContactAsync(accountId, index); return result.ToActionResult(); diff --git a/src/LetsEncryptServer/Controllers/CertsFlowController.cs b/src/LetsEncryptServer/Controllers/CertsFlowController.cs index 0d9faae..4b8a76f 100644 --- a/src/LetsEncryptServer/Controllers/CertsFlowController.cs +++ b/src/LetsEncryptServer/Controllers/CertsFlowController.cs @@ -2,11 +2,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using DomainResults.Mvc; using MaksIT.LetsEncryptServer.Services; -using Models.LetsEncryptServer.CertsFlow.Requests; +using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; namespace MaksIT.LetsEncryptServer.Controllers { [ApiController] - [Route("api/[controller]")] + [Route("api/certs")] public class CertsFlowController : ControllerBase { private readonly Configuration _appSettings; private readonly ICertsFlowService _certsFlowService; @@ -34,7 +34,7 @@ namespace MaksIT.LetsEncryptServer.Controllers { /// /// Session ID /// Terms of service - [HttpGet("terms-of-service/{sessionId}")] + [HttpGet("{sessionId}/terms-of-service")] public IActionResult TermsOfService(Guid sessionId) { var result = _certsFlowService.GetTermsOfService(sessionId); return result.ToActionResult(); diff --git a/src/LetsEncryptServer/Controllers/WellKnownController.cs b/src/LetsEncryptServer/Controllers/WellKnownController.cs index 36f1710..000e0ae 100644 --- a/src/LetsEncryptServer/Controllers/WellKnownController.cs +++ b/src/LetsEncryptServer/Controllers/WellKnownController.cs @@ -13,7 +13,7 @@ namespace MaksIT.LetsEncryptServer.Controllers; public class WellKnownController : ControllerBase { private readonly Configuration _appSettings; - private readonly ICertsFlowServiceBase _certsFlowService; + private readonly ICertsRestChallengeService _certsFlowService; private readonly string _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme"); diff --git a/src/LetsEncryptServer/Services/CacheService.cs b/src/LetsEncryptServer/Services/CacheService.cs index 8ae823f..fed2a68 100644 --- a/src/LetsEncryptServer/Services/CacheService.cs +++ b/src/LetsEncryptServer/Services/CacheService.cs @@ -6,7 +6,7 @@ using MaksIT.Core.Extensions; using MaksIT.LetsEncrypt.Entities; using MaksIT.Models; using MaksIT.Models.LetsEncryptServer.Cache.Requests; -using Models.LetsEncryptServer.Cache.Responses; +using MaksIT.Models.LetsEncryptServer.Cache.Responses; namespace MaksIT.LetsEncryptServer.Services; diff --git a/src/LetsEncryptServer/Services/CertsFlowService.cs b/src/LetsEncryptServer/Services/CertsFlowService.cs index 7138ff7..921303f 100644 --- a/src/LetsEncryptServer/Services/CertsFlowService.cs +++ b/src/LetsEncryptServer/Services/CertsFlowService.cs @@ -6,16 +6,21 @@ using DomainResults.Common; using MaksIT.LetsEncrypt.Entities; using MaksIT.LetsEncrypt.Services; -using Models.LetsEncryptServer.CertsFlow.Requests; +using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; namespace MaksIT.LetsEncryptServer.Services; -public interface ICertsFlowServiceBase { +public interface ICertsInternalService { + +} + + +public interface ICertsRestChallengeService { (string?, IDomainResult) AcmeChallenge(string fileName); } -public interface ICertsFlowService : ICertsFlowServiceBase { +public interface ICertsRestService { Task<(Guid?, IDomainResult)> ConfigureClientAsync(); (string?, IDomainResult) GetTermsOfService(Guid sessionId); Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData); @@ -24,7 +29,12 @@ public interface ICertsFlowService : ICertsFlowServiceBase { Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData); Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); - } +} + +public interface ICertsFlowService + : ICertsInternalService, + ICertsRestService, + ICertsRestChallengeService {} public class CertsFlowService : ICertsFlowService { diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PutAccountRequest.cs b/src/Models/LetsEncryptServer/Cache/Requests/PutAccountRequest.cs index 0e21aad..858cc29 100644 --- a/src/Models/LetsEncryptServer/Cache/Requests/PutAccountRequest.cs +++ b/src/Models/LetsEncryptServer/Cache/Requests/PutAccountRequest.cs @@ -1,12 +1,21 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { - public class PutAccountRequest { - public string Description { get; set; } - public string[] Contacts { get; set; } + public class PutAccountRequest : IValidatableObject { + public required string Description { get; set; } + public required string[] Contacts { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) { + if (string.IsNullOrWhiteSpace(Description)) + yield return new ValidationResult("Description is required", new[] { nameof(Description) }); + + if (Contacts == null || Contacts.Length == 0) + yield return new ValidationResult("Contacts is required", new[] { nameof(Contacts) }); + } } } diff --git a/src/Models/LetsEncryptServer/Cache/Responses/GetAccountResponse.cs b/src/Models/LetsEncryptServer/Cache/Responses/GetAccountResponse.cs index 1c868d4..144618f 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/GetAccountResponse.cs +++ b/src/Models/LetsEncryptServer/Cache/Responses/GetAccountResponse.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { public class GetAccountResponse { public Guid AccountId { get; set; } diff --git a/src/Models/LetsEncryptServer/Cache/Responses/GetContactsResponse.cs b/src/Models/LetsEncryptServer/Cache/Responses/GetContactsResponse.cs index fac9d70..1e2e876 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/GetContactsResponse.cs +++ b/src/Models/LetsEncryptServer/Cache/Responses/GetContactsResponse.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { public class GetContactsResponse { public string[] Contacts { get; set; } } diff --git a/src/Models/LetsEncryptServer/Cache/Responses/GetHostnamesResponse.cs b/src/Models/LetsEncryptServer/Cache/Responses/GetHostnamesResponse.cs index 09d4f4d..207ce2b 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/GetHostnamesResponse.cs +++ b/src/Models/LetsEncryptServer/Cache/Responses/GetHostnamesResponse.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { public class GetHostnamesResponse { public List Hostnames { get; set; } diff --git a/src/Models/LetsEncryptServer/Cache/Responses/HostnameResponse.cs b/src/Models/LetsEncryptServer/Cache/Responses/HostnameResponse.cs index 69576a7..79ab200 100644 --- a/src/Models/LetsEncryptServer/Cache/Responses/HostnameResponse.cs +++ b/src/Models/LetsEncryptServer/Cache/Responses/HostnameResponse.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Models.LetsEncryptServer.Cache.Responses { +namespace MaksIT.Models.LetsEncryptServer.Cache.Responses { public class HostnameResponse { public string Hostname { get; set; } public DateTime Expires { get; set; } diff --git a/src/Models/LetsEncryptServer/CertsFlow/Requests/GetCerificatesRequest.cs b/src/Models/LetsEncryptServer/CertsFlow/Requests/GetCerificatesRequest.cs index 60422b4..c5161f3 100644 --- a/src/Models/LetsEncryptServer/CertsFlow/Requests/GetCerificatesRequest.cs +++ b/src/Models/LetsEncryptServer/CertsFlow/Requests/GetCerificatesRequest.cs @@ -1,7 +1,13 @@ -namespace Models.LetsEncryptServer.CertsFlow.Requests +using System.ComponentModel.DataAnnotations; + +namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests { - public class GetCertificatesRequest - { - public string[] Hostnames { get; set; } + public class GetCertificatesRequest : 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/CertsFlow/Requests/GetOrderRequest.cs b/src/Models/LetsEncryptServer/CertsFlow/Requests/GetOrderRequest.cs index de6f0ce..c39acaf 100644 --- a/src/Models/LetsEncryptServer/CertsFlow/Requests/GetOrderRequest.cs +++ b/src/Models/LetsEncryptServer/CertsFlow/Requests/GetOrderRequest.cs @@ -1,7 +1,13 @@ -namespace Models.LetsEncryptServer.CertsFlow.Requests +using System.ComponentModel.DataAnnotations; + +namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests { - public class GetOrderRequest - { - public string[] Hostnames { get; set; } + public class GetOrderRequest : 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/CertsFlow/Requests/InitRequest.cs b/src/Models/LetsEncryptServer/CertsFlow/Requests/InitRequest.cs index dbf3989..102383e 100644 --- a/src/Models/LetsEncryptServer/CertsFlow/Requests/InitRequest.cs +++ b/src/Models/LetsEncryptServer/CertsFlow/Requests/InitRequest.cs @@ -1,13 +1,21 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Models.LetsEncryptServer.CertsFlow.Requests -{ - public class InitRequest - { - public string[] Contacts { get; set; } +namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests { + public class InitRequest: IValidatableObject { + public required string Description { get; set; } + public required string[] Contacts { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) { + if (string.IsNullOrWhiteSpace(Description)) + yield return new ValidationResult("Description is required", new[] { nameof(Description) }); + + if (Contacts == null || Contacts.Length == 0) + yield return new ValidationResult("Contacts is required", new[] { nameof(Contacts) }); } + } } diff --git a/src/Models/LetsEncryptServer/CertsFlow/Requests/NewOrderRequest.cs b/src/Models/LetsEncryptServer/CertsFlow/Requests/NewOrderRequest.cs index e787b9f..62e58b9 100644 --- a/src/Models/LetsEncryptServer/CertsFlow/Requests/NewOrderRequest.cs +++ b/src/Models/LetsEncryptServer/CertsFlow/Requests/NewOrderRequest.cs @@ -1,9 +1,18 @@ -namespace Models.LetsEncryptServer.CertsFlow.Requests -{ - public class NewOrderRequest - { - public string[] Hostnames { get; set; } +using System.ComponentModel.DataAnnotations; - public string ChallengeType { get; set; } +namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests +{ + public class NewOrderRequest : IValidatableObject { + public required string[] Hostnames { get; set; } + + public required string ChallengeType { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) { + 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/CertsFlow/Requests/PostAccountRequest.cs b/src/Models/LetsEncryptServer/CertsFlow/Requests/PostAccountRequest.cs new file mode 100644 index 0000000..5dd6619 --- /dev/null +++ b/src/Models/LetsEncryptServer/CertsFlow/Requests/PostAccountRequest.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests { + public class PostAccountRequest : IValidatableObject { + public required string Description { get; set; } + public required string[] Contacts { get; set; } + public required string [] Hostnames { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) { + if (Description == null || Description.Length == 0) + yield return new ValidationResult("Description is required", new[] { nameof(Description) }); + + if (Contacts == null || Contacts.Length == 0) + yield return new ValidationResult("Contacts is required", new[] { nameof(Contacts) }); + + if (Hostnames == null || Hostnames.Length == 0) + yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) }); + } + } +}