From 1d5ac07611354e68210604a329b82545bdf3d5b3 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:44:58 -0300
Subject: [PATCH 001/135] build: enforce explicit global usings
---
src/InvisibleSP.Domain/InvisibleSP.Domain.csproj | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/InvisibleSP.Domain/InvisibleSP.Domain.csproj b/src/InvisibleSP.Domain/InvisibleSP.Domain.csproj
index 6d36c6d..dcb1a85 100644
--- a/src/InvisibleSP.Domain/InvisibleSP.Domain.csproj
+++ b/src/InvisibleSP.Domain/InvisibleSP.Domain.csproj
@@ -1,9 +1,7 @@
-
-
-
- net10.0
- enable
- enable
-
-
-
+
+
+ net10.0
+ disable
+ enable
+
+
From b40682becd74e988b3194b921164ca91d933ab15 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:02 -0300
Subject: [PATCH 002/135] build: configure application dependencies
---
.../InvisibleSP.Application.csproj | 29 ++++++++-----------
1 file changed, 12 insertions(+), 17 deletions(-)
diff --git a/src/InvisibleSP.Application/InvisibleSP.Application.csproj b/src/InvisibleSP.Application/InvisibleSP.Application.csproj
index e752c93..fcebf20 100644
--- a/src/InvisibleSP.Application/InvisibleSP.Application.csproj
+++ b/src/InvisibleSP.Application/InvisibleSP.Application.csproj
@@ -1,17 +1,12 @@
-
-
-
-
-
-
-
-
-
-
-
- net10.0
- enable
- enable
-
-
-
+
+
+
+
+
+
+
+ net10.0
+ disable
+ enable
+
+
From c758ae2e517304fa2322ade6dbb2dcde8c8eaf84 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:06 -0300
Subject: [PATCH 003/135] build: configure identity infrastructure dependencies
---
.../InvisibleSP.Infrastructure.csproj | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/InvisibleSP.Infrastructure/InvisibleSP.Infrastructure.csproj b/src/InvisibleSP.Infrastructure/InvisibleSP.Infrastructure.csproj
index d5cbf5e..2f3eca1 100644
--- a/src/InvisibleSP.Infrastructure/InvisibleSP.Infrastructure.csproj
+++ b/src/InvisibleSP.Infrastructure/InvisibleSP.Infrastructure.csproj
@@ -1,13 +1,13 @@
-
-
-
-
-
-
-
- net10.0
- enable
- enable
-
-
-
+
+
+
+
+
+
+
+
+ net10.0
+ disable
+ enable
+
+
From e45e31e9831885a59b6f473474337c432956761d Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:10 -0300
Subject: [PATCH 004/135] build: configure composition dependencies
---
.../InvisibleSP.Composition.csproj | 28 +++++++++----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/InvisibleSP.Composition/InvisibleSP.Composition.csproj b/src/InvisibleSP.Composition/InvisibleSP.Composition.csproj
index 12a22f3..6a0f993 100644
--- a/src/InvisibleSP.Composition/InvisibleSP.Composition.csproj
+++ b/src/InvisibleSP.Composition/InvisibleSP.Composition.csproj
@@ -1,14 +1,14 @@
-
-
-
-
-
-
-
-
- net10.0
- enable
- enable
-
-
-
+
+
+
+
+
+
+
+
+
+ net10.0
+ disable
+ enable
+
+
From 9ed5d9eeb5d402ade017192bfe99bac213919104 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:13 -0300
Subject: [PATCH 005/135] build: enforce explicit presentation imports
---
src/InvisibleSP/InvisibleSP.csproj | 27 ++++++++++++---------------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/src/InvisibleSP/InvisibleSP.csproj b/src/InvisibleSP/InvisibleSP.csproj
index f6f22a7..cfd5456 100644
--- a/src/InvisibleSP/InvisibleSP.csproj
+++ b/src/InvisibleSP/InvisibleSP.csproj
@@ -1,15 +1,12 @@
-
-
-
-
-
-
-
-
- net10.0
- enable
- enable
- true
-
-
-
+
+
+
+
+
+
+ net10.0
+ enable
+ disable
+ true
+
+
From 3195f41074b5520555459867a1027c8de651abb2 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:16 -0300
Subject: [PATCH 006/135] build: add domain global usings
---
src/InvisibleSP.Domain/GlobalUsings.cs | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 src/InvisibleSP.Domain/GlobalUsings.cs
diff --git a/src/InvisibleSP.Domain/GlobalUsings.cs b/src/InvisibleSP.Domain/GlobalUsings.cs
new file mode 100644
index 0000000..3e07978
--- /dev/null
+++ b/src/InvisibleSP.Domain/GlobalUsings.cs
@@ -0,0 +1,5 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Linq;
+global using System.Threading;
+global using System.Threading.Tasks;
From da0c339a23706eed5547c86ead4b991e7334d73b Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:19 -0300
Subject: [PATCH 007/135] build: add application global usings
---
src/InvisibleSP.Application/GlobalUsings.cs | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 src/InvisibleSP.Application/GlobalUsings.cs
diff --git a/src/InvisibleSP.Application/GlobalUsings.cs b/src/InvisibleSP.Application/GlobalUsings.cs
new file mode 100644
index 0000000..3f20d99
--- /dev/null
+++ b/src/InvisibleSP.Application/GlobalUsings.cs
@@ -0,0 +1,8 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Linq;
+global using System.Threading;
+global using System.Threading.Tasks;
+global using FluentValidation;
+global using Mediator.Net;
+global using Mediator.Net.Contracts;
From 6f94a32fd5e41bd1033066f21d1b06ee79e4081d Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:25 -0300
Subject: [PATCH 008/135] feat: add soft deletable domain contract
---
src/InvisibleSP.Domain/Common/ISoftDeletable.cs | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 src/InvisibleSP.Domain/Common/ISoftDeletable.cs
diff --git a/src/InvisibleSP.Domain/Common/ISoftDeletable.cs b/src/InvisibleSP.Domain/Common/ISoftDeletable.cs
new file mode 100644
index 0000000..f847a83
--- /dev/null
+++ b/src/InvisibleSP.Domain/Common/ISoftDeletable.cs
@@ -0,0 +1,7 @@
+namespace InvisibleSP.Domain.Common;
+
+public interface ISoftDeletable
+{
+ bool IsDeleted { get; set; }
+ DateTimeOffset? DeletedAt { get; set; }
+}
From 547baefc8e0e7367d64f7f69f3ff42106238feff Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:29 -0300
Subject: [PATCH 009/135] feat: define identity application contracts
---
.../Common/Identity/IdentityContracts.cs | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 src/InvisibleSP.Application/Common/Identity/IdentityContracts.cs
diff --git a/src/InvisibleSP.Application/Common/Identity/IdentityContracts.cs b/src/InvisibleSP.Application/Common/Identity/IdentityContracts.cs
new file mode 100644
index 0000000..cc4b2f8
--- /dev/null
+++ b/src/InvisibleSP.Application/Common/Identity/IdentityContracts.cs
@@ -0,0 +1,36 @@
+namespace InvisibleSP.Application.Common.Identity;
+
+public static class IdentityClaimTypes
+{
+ public const string Permission = "permission";
+}
+
+public static class IdentityPolicies
+{
+ public const string Setup = "setup";
+}
+
+public sealed record TokenResponse(
+ string TokenType,
+ string AccessToken,
+ int ExpiresIn,
+ string RefreshToken);
+
+public sealed record IdentityResultResponse(bool Succeeded, IReadOnlyCollection Errors)
+{
+ public static IdentityResultResponse Success() => new(true, Array.Empty());
+
+ public static IdentityResultResponse Failure(IEnumerable errors) =>
+ new(false, errors.ToArray());
+}
+
+public sealed record IdentityInfoResponse(string Email, bool IsEmailConfirmed);
+
+public sealed record TwoFactorResponse(
+ string? SharedKey,
+ int RecoveryCodesLeft,
+ IReadOnlyCollection? RecoveryCodes,
+ bool IsTwoFactorEnabled,
+ bool IsMachineRemembered);
+
+public sealed record SetupStatusResponse(bool IsSetupRequired, bool IsSetupComplete);
From 383acf86d49100cbfffac11aaeddbad09d007c00 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:32 -0300
Subject: [PATCH 010/135] x
---
src/InvisibleSP.Application/Identity/IIdentityService.cs | 1 +
1 file changed, 1 insertion(+)
create mode 100644 src/InvisibleSP.Application/Identity/IIdentityService.cs
diff --git a/src/InvisibleSP.Application/Identity/IIdentityService.cs b/src/InvisibleSP.Application/Identity/IIdentityService.cs
new file mode 100644
index 0000000..a0c0f2d
--- /dev/null
+++ b/src/InvisibleSP.Application/Identity/IIdentityService.cs
@@ -0,0 +1 @@
+using?
\ No newline at end of file
From 3a70b4402c04e4b229757baf57b1a50f9b927e62 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:37 -0300
Subject: [PATCH 011/135] feat: define identity service abstraction
---
.../Identity/IIdentityService.cs | 21 ++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/InvisibleSP.Application/Identity/IIdentityService.cs b/src/InvisibleSP.Application/Identity/IIdentityService.cs
index a0c0f2d..de84ff0 100644
--- a/src/InvisibleSP.Application/Identity/IIdentityService.cs
+++ b/src/InvisibleSP.Application/Identity/IIdentityService.cs
@@ -1 +1,20 @@
-using?
\ No newline at end of file
+namespace InvisibleSP.Application.Identity;
+
+using InvisibleSP.Application.Common.Identity;
+
+public interface IIdentityService
+{
+ Task RegisterAsync(string email, string password, CancellationToken cancellationToken);
+ Task LoginAsync(string email, string password, string? twoFactorCode, string? twoFactorRecoveryCode, CancellationToken cancellationToken);
+ Task RefreshAsync(string refreshToken, CancellationToken cancellationToken);
+ Task RevokeAsync(string accessToken, CancellationToken cancellationToken);
+ Task ConfirmEmailAsync(string userId, string code, string? changedEmail, CancellationToken cancellationToken);
+ Task ResendConfirmationEmailAsync(string email, CancellationToken cancellationToken);
+ Task ForgotPasswordAsync(string email, CancellationToken cancellationToken);
+ Task ResetPasswordAsync(string email, string resetCode, string newPassword, CancellationToken cancellationToken);
+ Task GetInfoAsync(string userId, CancellationToken cancellationToken);
+ Task UpdateInfoAsync(string userId, string? newEmail, string? newPassword, string oldPassword, CancellationToken cancellationToken);
+ Task ConfigureTwoFactorAsync(string userId, bool? enable, string? twoFactorCode, bool resetRecoveryCodes, bool resetSharedKey, bool forgetMachine, CancellationToken cancellationToken);
+ Task GetSetupStatusAsync(CancellationToken cancellationToken);
+ Task InitializeSetupAsync(string email, string password, CancellationToken cancellationToken);
+}
From eab141e1465ab948cb989b7441e1dfc744ae9f94 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:45 -0300
Subject: [PATCH 012/135] build: centralize application imports
---
src/InvisibleSP.Application/GlobalUsings.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/InvisibleSP.Application/GlobalUsings.cs b/src/InvisibleSP.Application/GlobalUsings.cs
index 3f20d99..3bd8ce0 100644
--- a/src/InvisibleSP.Application/GlobalUsings.cs
+++ b/src/InvisibleSP.Application/GlobalUsings.cs
@@ -5,4 +5,6 @@
global using System.Threading.Tasks;
global using FluentValidation;
global using Mediator.Net;
+global using Mediator.Net.Context;
global using Mediator.Net.Contracts;
+global using InvisibleSP.Application.Common.Identity;
From c40a636abaabbfb35941094535491d0bb41bbcf8 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:49 -0300
Subject: [PATCH 013/135] build: enforce file scoped application source
---
src/InvisibleSP.Application/Identity/IIdentityService.cs | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/InvisibleSP.Application/Identity/IIdentityService.cs b/src/InvisibleSP.Application/Identity/IIdentityService.cs
index de84ff0..0861cd3 100644
--- a/src/InvisibleSP.Application/Identity/IIdentityService.cs
+++ b/src/InvisibleSP.Application/Identity/IIdentityService.cs
@@ -1,7 +1,5 @@
namespace InvisibleSP.Application.Identity;
-using InvisibleSP.Application.Common.Identity;
-
public interface IIdentityService
{
Task RegisterAsync(string email, string password, CancellationToken cancellationToken);
From 0389ab01ba7099fd47af5457d22d2e59248f6ef5 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:45:58 -0300
Subject: [PATCH 014/135] feat: add identity commands and queries
---
.../Identity/Requests/IdentityRequests.cs | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 src/InvisibleSP.Application/Identity/Requests/IdentityRequests.cs
diff --git a/src/InvisibleSP.Application/Identity/Requests/IdentityRequests.cs b/src/InvisibleSP.Application/Identity/Requests/IdentityRequests.cs
new file mode 100644
index 0000000..01096f6
--- /dev/null
+++ b/src/InvisibleSP.Application/Identity/Requests/IdentityRequests.cs
@@ -0,0 +1,41 @@
+namespace InvisibleSP.Application.Identity.Requests;
+
+public sealed record RegisterCommand(string Email, string Password) : IRequest;
+
+public sealed record LoginCommand(
+ string Email,
+ string Password,
+ string? TwoFactorCode = null,
+ string? TwoFactorRecoveryCode = null) : IRequest;
+
+public sealed record RefreshTokenCommand(string RefreshToken) : IRequest;
+
+public sealed record RevokeTokenCommand(string AccessToken) : IRequest;
+
+public sealed record ConfirmEmailCommand(string UserId, string Code, string? ChangedEmail = null) : IRequest;
+
+public sealed record ResendConfirmationEmailCommand(string Email) : IRequest;
+
+public sealed record ForgotPasswordCommand(string Email) : IRequest;
+
+public sealed record ResetPasswordCommand(string Email, string ResetCode, string NewPassword) : IRequest;
+
+public sealed record GetIdentityInfoQuery(string UserId) : IRequest;
+
+public sealed record UpdateIdentityInfoCommand(
+ string UserId,
+ string? NewEmail,
+ string? NewPassword,
+ string OldPassword) : IRequest;
+
+public sealed record ConfigureTwoFactorCommand(
+ string UserId,
+ bool? Enable,
+ string? TwoFactorCode,
+ bool ResetRecoveryCodes,
+ bool ResetSharedKey,
+ bool ForgetMachine) : IRequest;
+
+public sealed record GetSetupStatusQuery : IRequest;
+
+public sealed record InitializeSetupCommand(string Email, string Password) : IRequest;
From 47dcd45511aa566739ef51fa9dfc185ea73c8393 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:46:03 -0300
Subject: [PATCH 015/135] feat: add identity fluent validators
---
.../Identity/Validators/IdentityValidators.cs | 80 +++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 src/InvisibleSP.Application/Identity/Validators/IdentityValidators.cs
diff --git a/src/InvisibleSP.Application/Identity/Validators/IdentityValidators.cs b/src/InvisibleSP.Application/Identity/Validators/IdentityValidators.cs
new file mode 100644
index 0000000..1209ad6
--- /dev/null
+++ b/src/InvisibleSP.Application/Identity/Validators/IdentityValidators.cs
@@ -0,0 +1,80 @@
+namespace InvisibleSP.Application.Identity.Validators;
+
+public sealed class RegisterCommandValidator : AbstractValidator
+{
+ public RegisterCommandValidator()
+ {
+ RuleFor(x => x.Email).NotEmpty().EmailAddress();
+ RuleFor(x => x.Password).NotEmpty().MinimumLength(8);
+ }
+}
+
+public sealed class LoginCommandValidator : AbstractValidator
+{
+ public LoginCommandValidator()
+ {
+ RuleFor(x => x.Email).NotEmpty().EmailAddress();
+ RuleFor(x => x.Password).NotEmpty();
+ RuleFor(x => x).Must(x => string.IsNullOrWhiteSpace(x.TwoFactorCode) || string.IsNullOrWhiteSpace(x.TwoFactorRecoveryCode))
+ .WithMessage("Only one two-factor authentication code may be supplied.");
+ }
+}
+
+public sealed class RefreshTokenCommandValidator : AbstractValidator
+{
+ public RefreshTokenCommandValidator() => RuleFor(x => x.RefreshToken).NotEmpty();
+}
+
+public sealed class RevokeTokenCommandValidator : AbstractValidator
+{
+ public RevokeTokenCommandValidator() => RuleFor(x => x.AccessToken).NotEmpty();
+}
+
+public sealed class ConfirmEmailCommandValidator : AbstractValidator
+{
+ public ConfirmEmailCommandValidator()
+ {
+ RuleFor(x => x.UserId).NotEmpty();
+ RuleFor(x => x.Code).NotEmpty();
+ }
+}
+
+public sealed class ResendConfirmationEmailCommandValidator : AbstractValidator
+{
+ public ResendConfirmationEmailCommandValidator() => RuleFor(x => x.Email).NotEmpty().EmailAddress();
+}
+
+public sealed class ForgotPasswordCommandValidator : AbstractValidator
+{
+ public ForgotPasswordCommandValidator() => RuleFor(x => x.Email).NotEmpty().EmailAddress();
+}
+
+public sealed class ResetPasswordCommandValidator : AbstractValidator
+{
+ public ResetPasswordCommandValidator()
+ {
+ RuleFor(x => x.Email).NotEmpty().EmailAddress();
+ RuleFor(x => x.ResetCode).NotEmpty();
+ RuleFor(x => x.NewPassword).NotEmpty().MinimumLength(8);
+ }
+}
+
+public sealed class UpdateIdentityInfoCommandValidator : AbstractValidator
+{
+ public UpdateIdentityInfoCommandValidator()
+ {
+ RuleFor(x => x.UserId).NotEmpty();
+ RuleFor(x => x.OldPassword).NotEmpty();
+ RuleFor(x => x.NewEmail).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.NewEmail));
+ RuleFor(x => x.NewPassword).MinimumLength(8).When(x => !string.IsNullOrWhiteSpace(x.NewPassword));
+ }
+}
+
+public sealed class InitializeSetupCommandValidator : AbstractValidator
+{
+ public InitializeSetupCommandValidator()
+ {
+ RuleFor(x => x.Email).NotEmpty().EmailAddress();
+ RuleFor(x => x.Password).NotEmpty().MinimumLength(8);
+ }
+}
From 1db2acb41354902a4edc4b4f23c4f94a8a5a8694 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:46:07 -0300
Subject: [PATCH 016/135] build: expose application feature contracts globally
---
src/InvisibleSP.Application/GlobalUsings.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/InvisibleSP.Application/GlobalUsings.cs b/src/InvisibleSP.Application/GlobalUsings.cs
index 3bd8ce0..78fec66 100644
--- a/src/InvisibleSP.Application/GlobalUsings.cs
+++ b/src/InvisibleSP.Application/GlobalUsings.cs
@@ -8,3 +8,4 @@
global using Mediator.Net.Context;
global using Mediator.Net.Contracts;
global using InvisibleSP.Application.Common.Identity;
+global using InvisibleSP.Application.Identity.Requests;
From e0cf0ca89f2a930fa446a60350240de09709e4d3 Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:46:13 -0300
Subject: [PATCH 017/135] feat: add identity mediator handlers
---
.../Identity/Handlers/IdentityHandlers.cs | 79 +++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 src/InvisibleSP.Application/Identity/Handlers/IdentityHandlers.cs
diff --git a/src/InvisibleSP.Application/Identity/Handlers/IdentityHandlers.cs b/src/InvisibleSP.Application/Identity/Handlers/IdentityHandlers.cs
new file mode 100644
index 0000000..87712c8
--- /dev/null
+++ b/src/InvisibleSP.Application/Identity/Handlers/IdentityHandlers.cs
@@ -0,0 +1,79 @@
+namespace InvisibleSP.Application.Identity.Handlers;
+
+public sealed class RegisterCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.RegisterAsync(context.Message.Email, context.Message.Password, cancellationToken);
+}
+
+public sealed class LoginCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.LoginAsync(context.Message.Email, context.Message.Password, context.Message.TwoFactorCode, context.Message.TwoFactorRecoveryCode, cancellationToken);
+}
+
+public sealed class RefreshTokenCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.RefreshAsync(context.Message.RefreshToken, cancellationToken);
+}
+
+public sealed class RevokeTokenCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.RevokeAsync(context.Message.AccessToken, cancellationToken);
+}
+
+public sealed class ConfirmEmailCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.ConfirmEmailAsync(context.Message.UserId, context.Message.Code, context.Message.ChangedEmail, cancellationToken);
+}
+
+public sealed class ResendConfirmationEmailCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.ResendConfirmationEmailAsync(context.Message.Email, cancellationToken);
+}
+
+public sealed class ForgotPasswordCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.ForgotPasswordAsync(context.Message.Email, cancellationToken);
+}
+
+public sealed class ResetPasswordCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.ResetPasswordAsync(context.Message.Email, context.Message.ResetCode, context.Message.NewPassword, cancellationToken);
+}
+
+public sealed class GetIdentityInfoQueryHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.GetInfoAsync(context.Message.UserId, cancellationToken);
+}
+
+public sealed class UpdateIdentityInfoCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.UpdateInfoAsync(context.Message.UserId, context.Message.NewEmail, context.Message.NewPassword, context.Message.OldPassword, cancellationToken);
+}
+
+public sealed class ConfigureTwoFactorCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.ConfigureTwoFactorAsync(context.Message.UserId, context.Message.Enable, context.Message.TwoFactorCode, context.Message.ResetRecoveryCodes, context.Message.ResetSharedKey, context.Message.ForgetMachine, cancellationToken);
+}
+
+public sealed class GetSetupStatusQueryHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.GetSetupStatusAsync(cancellationToken);
+}
+
+public sealed class InitializeSetupCommandHandler(IIdentityService identityService) : IRequestHandler
+{
+ public Task Handle(IReceiveContext context, CancellationToken cancellationToken) =>
+ identityService.InitializeSetupAsync(context.Message.Email, context.Message.Password, cancellationToken);
+}
From 3cdaada1363c21acc50d3cd8fdfee50da1e1232f Mon Sep 17 00:00:00 2001
From: Weslley Luiz <37993565+weslleymurdock@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:46:43 -0300
Subject: [PATCH 018/135] feat: add mediator validation pipeline middleware
---
.../Validation/ValidationMiddleware.cs | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 src/InvisibleSP.Application/Pipeline/Validation/ValidationMiddleware.cs
diff --git a/src/InvisibleSP.Application/Pipeline/Validation/ValidationMiddleware.cs b/src/InvisibleSP.Application/Pipeline/Validation/ValidationMiddleware.cs
new file mode 100644
index 0000000..758144b
--- /dev/null
+++ b/src/InvisibleSP.Application/Pipeline/Validation/ValidationMiddleware.cs
@@ -0,0 +1,55 @@
+namespace InvisibleSP.Application.Pipeline.Validation;
+
+public interface IMessageValidator
+{
+ Task ValidateAsync(object message, CancellationToken cancellationToken);
+}
+
+public sealed class FluentMessageValidator(IServiceProvider serviceProvider) : IMessageValidator
+{
+ public async Task ValidateAsync(object message, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(message);
+
+ var validatorType = typeof(IValidator<>).MakeGenericType(message.GetType());
+ var validator = serviceProvider.GetService(validatorType) as IValidator;
+
+ if (validator is null)
+ {
+ return;
+ }
+
+ var context = new ValidationContext