| | | 1 | | using ArturRios.Heimdall.Command.Auditing; |
| | | 2 | | using ArturRios.Heimdall.Command.Handlers; |
| | | 3 | | using ArturRios.Heimdall.Command.Input; |
| | | 4 | | using ArturRios.Heimdall.Command.Input.Validation; |
| | | 5 | | using ArturRios.Heimdall.Command.Output; |
| | | 6 | | using ArturRios.Heimdall.Command.Services; |
| | | 7 | | using ArturRios.Mediator.Command; |
| | | 8 | | using FluentValidation; |
| | | 9 | | using Microsoft.Extensions.DependencyInjection; |
| | | 10 | | |
| | | 11 | | namespace ArturRios.Heimdall.Command.Registration; |
| | | 12 | | |
| | | 13 | | public static class CommandServiceCollectionExtensions |
| | | 14 | | { |
| | | 15 | | /// <summary> |
| | | 16 | | /// Registers the command mediator, the audit log writer and the audited command handlers (with their |
| | | 17 | | /// validators) of the scope, person, authentication, application and scope permission use cases. |
| | | 18 | | /// </summary> |
| | | 19 | | public static IServiceCollection AddCommandHandlers(this IServiceCollection services) |
| | | 20 | | { |
| | | 21 | | // #219: commands are dispatched without a cancellation token, by design. A write that has begun |
| | | 22 | | // must finish or roll back as one unit, not stop halfway because the client went away, so the |
| | | 23 | | // controllers hand ExecuteCommandAsync no HttpContext.RequestAborted and the command handlers |
| | | 24 | | // forward no token to EF Core. Queries are the opposite: they receive the request's token. |
| | 973 | 25 | | services.AddScoped<CommandMediator>(); |
| | 973 | 26 | | services.AddScoped<IAuditLogWriter, AuditLogWriter>(); |
| | 973 | 27 | | services.AddScoped<IValidator<CreateScopeCommand>, CreateScopeCommandValidator>(); |
| | 973 | 28 | | services.AddAuditedCommandHandler<CreateScopeCommand, CreateScopeCommandOutput, CreateScopeCommandHandler>(); |
| | 973 | 29 | | services.AddScoped<IValidator<UpdateScopeCommand>, UpdateScopeCommandValidator>(); |
| | 973 | 30 | | services.AddAuditedCommandHandler<UpdateScopeCommand, UpdateScopeCommandOutput, UpdateScopeCommandHandler>(); |
| | 973 | 31 | | services.AddAuditedCommandHandler<DeleteScopeCommand, DeleteScopeCommandOutput, DeleteScopeCommandHandler>(); |
| | 973 | 32 | | services.AddAuditedCommandHandler<HardDeleteScopeCommand, HardDeleteScopeCommandOutput, HardDeleteScopeCommandHa |
| | | 33 | | // UC-24 does have a validator despite carrying a single field: Enabled is nullable so an |
| | | 34 | | // omitted value is refused (NFR-10) rather than binding to false and disabling the setting. |
| | 973 | 35 | | services.AddScoped<IValidator<SetGoogleSignInCommand>, SetGoogleSignInCommandValidator>(); |
| | 973 | 36 | | services.AddAuditedCommandHandler<SetGoogleSignInCommand, SetGoogleSignInCommandOutput, SetGoogleSignInCommandHa |
| | 973 | 37 | | services.AddScoped<IValidator<SetSelfRegistrationCommand>, SetSelfRegistrationCommandValidator>(); |
| | 973 | 38 | | services.AddAuditedCommandHandler<SetSelfRegistrationCommand, SetSelfRegistrationCommandOutput, SetSelfRegistrat |
| | 973 | 39 | | services.AddScoped<IValidator<SetScopeEmailLinksCommand>, SetScopeEmailLinksCommandValidator>(); |
| | 973 | 40 | | services.AddAuditedCommandHandler<SetScopeEmailLinksCommand, SetScopeEmailLinksCommandOutput, SetScopeEmailLinks |
| | | 41 | | // UC-50 (FR-SR-12): no validator — both identifiers are route values already constrained to |
| | | 42 | | // GUIDs. Audited like every write, with the registration as target. |
| | 973 | 43 | | services.AddAuditedCommandHandler<DiscardPendingRegistrationCommand, DiscardPendingRegistrationCommandOutput, Di |
| | 973 | 44 | | services.AddScoped<IValidator<CreateAdminCommand>, CreateAdminCommandValidator>(); |
| | 973 | 45 | | services.AddAuditedCommandHandler<CreateAdminCommand, CreatePersonCommandOutput, CreateAdminCommandHandler>(); |
| | 973 | 46 | | services.AddScoped<IValidator<CreateUserCommand>, CreateUserCommandValidator>(); |
| | 973 | 47 | | services.AddAuditedCommandHandler<CreateUserCommand, CreatePersonCommandOutput, CreateUserCommandHandler>(); |
| | 973 | 48 | | services.AddScoped<IValidator<CreateScopeOwnerCommand>, CreateScopeOwnerCommandValidator>(); |
| | 973 | 49 | | services.AddAuditedCommandHandler<CreateScopeOwnerCommand, CreatePersonCommandOutput, CreateScopeOwnerCommandHan |
| | | 50 | | // No validator: UC-21's request carries no body — both identifiers are route values already |
| | | 51 | | // constrained to GUIDs — so there is nothing left for NFR-10 to validate. |
| | 973 | 52 | | services.AddAuditedCommandHandler<AddScopeOwnerCommand, AddScopeOwnerCommandOutput, AddScopeOwnerCommandHandler> |
| | | 53 | | // UC-58: an owner or a System Admin withdraws an open scope owner invitation. |
| | 973 | 54 | | services.AddAuditedCommandHandler<WithdrawScopeOwnerInvitationCommand, WithdrawScopeOwnerInvitationCommandOutput |
| | | 55 | | // UC-60/UC-61: the invitee accepts or declines; no validator — the route's id is the whole input. |
| | 973 | 56 | | services.AddAuditedCommandHandler<AcceptScopeOwnerInvitationCommand, AcceptScopeOwnerInvitationCommandOutput, Ac |
| | 973 | 57 | | services.AddAuditedCommandHandler<DeclineScopeOwnerInvitationCommand, DeclineScopeOwnerInvitationCommandOutput, |
| | | 58 | | // Likewise no validator for UC-22: both identifiers are route values. |
| | 973 | 59 | | services.AddAuditedCommandHandler<RemoveScopeOwnerCommand, RemoveScopeOwnerCommandOutput, RemoveScopeOwnerComman |
| | | 60 | | // Likewise no validator for UC-23: both identifiers are route values. |
| | 973 | 61 | | services.AddAuditedCommandHandler<PromoteScopeUserCommand, PromoteScopeUserCommandOutput, PromoteScopeUserComman |
| | 973 | 62 | | services.AddScoped<IValidator<UpdatePersonCommand>, UpdatePersonCommandValidator>(); |
| | 973 | 63 | | services.AddAuditedCommandHandler<UpdatePersonCommand, UpdatePersonCommandOutput, UpdatePersonCommandHandler>(); |
| | 973 | 64 | | services.AddAuditedCommandHandler<DeletePersonCommand, DeletePersonCommandOutput, DeletePersonCommandHandler>(); |
| | 973 | 65 | | services.AddAuditedCommandHandler<HardDeletePersonCommand, HardDeletePersonCommandOutput, HardDeletePersonComman |
| | 973 | 66 | | services.AddScoped<IValidator<LoginCommand>, LoginCommandValidator>(); |
| | 973 | 67 | | services.AddAuditedCommandHandler<LoginCommand, LoginCommandOutput, LoginCommandHandler>(); |
| | 973 | 68 | | services.AddScoped<IValidator<PasswordRecoveryCommand>, PasswordRecoveryCommandValidator>(); |
| | 973 | 69 | | services.AddAuditedCommandHandler<PasswordRecoveryCommand, PasswordRecoveryCommandOutput, PasswordRecoveryComman |
| | 973 | 70 | | services.AddScoped<IValidator<RegisterCommand>, RegisterCommandValidator>(); |
| | 973 | 71 | | services.AddAuditedCommandHandler<RegisterCommand, RegisterCommandOutput, RegisterCommandHandler>(); |
| | 973 | 72 | | services.AddScoped<IValidator<ResetPasswordCommand>, ResetPasswordCommandValidator>(); |
| | 973 | 73 | | services.AddAuditedCommandHandler<ResetPasswordCommand, ResetPasswordCommandOutput, ResetPasswordCommandHandler> |
| | 973 | 74 | | services.AddScoped<IValidator<VerifyEmailCommand>, VerifyEmailCommandValidator>(); |
| | 973 | 75 | | services.AddAuditedCommandHandler<VerifyEmailCommand, VerifyEmailCommandOutput, VerifyEmailCommandHandler>(); |
| | 973 | 76 | | services.AddScoped<IValidator<RequestErasureCommand>, RequestErasureCommandValidator>(); |
| | 973 | 77 | | services |
| | 973 | 78 | | .AddAuditedCommandHandler<RequestErasureCommand, RequestErasureCommandOutput, |
| | 973 | 79 | | RequestErasureCommandHandler>(); |
| | 973 | 80 | | services |
| | 973 | 81 | | .AddAuditedCommandHandler<ExportMyDataCommand, DataExportCommandOutput, |
| | 973 | 82 | | ExportMyDataCommandHandler>(); |
| | 973 | 83 | | services |
| | 973 | 84 | | .AddAuditedCommandHandler<RestrictProcessingCommand, RestrictProcessingCommandOutput, |
| | 973 | 85 | | RestrictProcessingCommandHandler>(); |
| | 973 | 86 | | services |
| | 973 | 87 | | .AddAuditedCommandHandler<LiftProcessingRestrictionCommand, |
| | 973 | 88 | | LiftProcessingRestrictionCommandOutput, LiftProcessingRestrictionCommandHandler>(); |
| | 973 | 89 | | services |
| | 973 | 90 | | .AddAuditedCommandHandler<ReapplyErasuresCommand, ReapplyErasuresCommandOutput, |
| | 973 | 91 | | ReapplyErasuresCommandHandler>(); |
| | | 92 | | // No validator: UC-15's request carries no caller-supplied input at all — the person comes |
| | | 93 | | // from the bearer token — so there is nothing for NFR-10 to validate. |
| | 973 | 94 | | services.AddAuditedCommandHandler<ResendVerificationEmailCommand, ResendVerificationEmailCommandOutput, ResendVe |
| | | 95 | | // Likewise no validator for UC-25, for a different reason: the use case defines no 400 flow |
| | | 96 | | // and needs none, since an absent ID token fails verification (AF-25a, 401) and an empty |
| | | 97 | | // scope identifier matches no scope (AF-25b, 403). |
| | 973 | 98 | | services.AddAuditedCommandHandler<GoogleSignInCommand, GoogleSignInCommandOutput, GoogleSignInCommandHandler>(); |
| | | 99 | | // No validator for UC-26 either, and for UC-15's reason: the sign-out request carries no |
| | | 100 | | // caller-supplied input — the Google User comes from the bearer token. |
| | 973 | 101 | | services.AddAuditedCommandHandler<GoogleSignOutCommand, GoogleSignOutCommandOutput, GoogleSignOutCommandHandler> |
| | | 102 | | // UC-28 needs no validator either: both fields are typed route parameters, so there is no |
| | | 103 | | // caller-supplied input NFR-10 could reject that the route would not have refused first. |
| | 973 | 104 | | services.AddAuditedCommandHandler<DeleteGoogleUserCommand, DeleteGoogleUserCommandOutput, DeleteGoogleUserComman |
| | 973 | 105 | | services.AddAuditedCommandHandler<HardDeleteGoogleUserCommand, HardDeleteGoogleUserCommandOutput, HardDeleteGoog |
| | 973 | 106 | | services.AddScoped<IValidator<CreateApplicationCommand>, CreateApplicationCommandValidator>(); |
| | 973 | 107 | | services.AddAuditedCommandHandler<CreateApplicationCommand, CreateApplicationCommandOutput, CreateApplicationCom |
| | 973 | 108 | | services.AddScoped<IValidator<UpdateApplicationCommand>, UpdateApplicationCommandValidator>(); |
| | 973 | 109 | | services.AddAuditedCommandHandler<UpdateApplicationCommand, UpdateApplicationCommandOutput, UpdateApplicationCom |
| | 973 | 110 | | services.AddAuditedCommandHandler<DeleteApplicationCommand, DeleteApplicationCommandOutput, DeleteApplicationCom |
| | 973 | 111 | | services.AddAuditedCommandHandler<HardDeleteApplicationCommand, HardDeleteApplicationCommandOutput, HardDeleteAp |
| | 973 | 112 | | services.AddScoped<IValidator<IssueApplicationCredentialCommand>, IssueApplicationCredentialCommandValidator>(); |
| | 973 | 113 | | services.AddAuditedCommandHandler<IssueApplicationCredentialCommand, IssueApplicationCredentialCommandOutput, Is |
| | 973 | 114 | | services.AddAuditedCommandHandler<RevokeApplicationCredentialCommand, RevokeApplicationCredentialCommandOutput, |
| | 973 | 115 | | services.AddScoped<IValidator<IssueApplicationTokenCommand>, IssueApplicationTokenCommandValidator>(); |
| | 973 | 116 | | services.AddAuditedCommandHandler<IssueApplicationTokenCommand, IssueApplicationTokenCommandOutput, IssueApplica |
| | 973 | 117 | | services.AddScoped<IValidator<CreateScopePermissionCommand>, CreateScopePermissionCommandValidator>(); |
| | 973 | 118 | | services.AddAuditedCommandHandler<CreateScopePermissionCommand, CreateScopePermissionCommandOutput, CreateScopeP |
| | 973 | 119 | | services.AddScoped<IValidator<UpdateScopePermissionCommand>, UpdateScopePermissionCommandValidator>(); |
| | 973 | 120 | | services.AddAuditedCommandHandler<UpdateScopePermissionCommand, UpdateScopePermissionCommandOutput, UpdateScopeP |
| | 973 | 121 | | services.AddAuditedCommandHandler<DeleteScopePermissionCommand, DeleteScopePermissionCommandOutput, DeleteScopeP |
| | 973 | 122 | | services.AddAuditedCommandHandler<HardDeleteScopePermissionCommand, HardDeleteScopePermissionCommandOutput, Hard |
| | | 123 | | |
| | 973 | 124 | | return services; |
| | | 125 | | } |
| | | 126 | | |
| | | 127 | | /// <summary> |
| | | 128 | | /// Registers the TOTP secret protector and verifier, the two-factor services and the audited command |
| | | 129 | | /// handlers of the two-factor use cases (UC-36 to UC-40, UC-46). |
| | | 130 | | /// </summary> |
| | | 131 | | public static IServiceCollection AddTwoFactorServices(this IServiceCollection services) |
| | | 132 | | { |
| | 973 | 133 | | services.AddScoped<ITotpSecretProtector, TotpSecretProtector>(); |
| | | 134 | | // Shared by UC-37's confirmation and ITwoFactorFactorVerifier (UC-38/39/40): the TOTP secret, |
| | | 135 | | // the clock-drift window, and the single-use rule that keeps an app code from being replayed |
| | | 136 | | // live in one place rather than a copy per call site. |
| | 973 | 137 | | services.AddScoped<ITotpCodeVerifier, TotpCodeVerifier>(); |
| | 973 | 138 | | services.AddScoped<IValidator<EnableTwoFactorAuthCommand>, EnableTwoFactorAuthCommandValidator>(); |
| | 973 | 139 | | services.AddAuditedCommandHandler<EnableTwoFactorAuthCommand, EnableTwoFactorAuthCommandOutput, EnableTwoFactorA |
| | | 140 | | |
| | | 141 | | // UC-37 (FR-2F-04/05): no validator — which code(s) are required depends on the pending row's |
| | | 142 | | // AppEnabled/EmailEnabled, a database read the handler alone can make. |
| | 973 | 143 | | services.AddAuditedCommandHandler<ConfirmTwoFactorAuthCommand, ConfirmTwoFactorAuthCommandOutput, ConfirmTwoFact |
| | | 144 | | |
| | | 145 | | // UC-38 (FR-2F-06…FR-2F-09): no validator either — same reason as UC-37, plus the challenge |
| | | 146 | | // token itself is validated inside the handler, the same "opaque token as a body value" |
| | | 147 | | // shape UC-13's ResetPasswordCommand uses. |
| | 973 | 148 | | services.AddAuditedCommandHandler<VerifyTwoFactorAuthCommand, VerifyTwoFactorAuthCommandOutput, VerifyTwoFactorA |
| | | 149 | | |
| | | 150 | | // UC-46 (FR-2F-16): no validator — the challenge token is the whole of the input and is |
| | | 151 | | // validated inside the handler, the same shape UC-38 uses. |
| | 973 | 152 | | services.AddAuditedCommandHandler<ResendTwoFactorChallengeCodeCommand, ResendTwoFactorChallengeCodeCommandOutput |
| | | 153 | | |
| | | 154 | | // Shared by UC-11's AF-11g, UC-36's Email method and UC-46's reissue: retire-then-issue is |
| | | 155 | | // one step in one place, so the three cannot drift and leave two live codes for one |
| | | 156 | | // configuration — the retirement is what makes a code single-use in practice. |
| | 973 | 157 | | services.AddScoped<ITwoFactorEmailCodeIssuer, TwoFactorEmailCodeIssuer>(); |
| | | 158 | | |
| | | 159 | | // Shared by VerifyTwoFactorAuthCommandHandler (UC-38), DisableTwoFactorAuthCommandHandler |
| | | 160 | | // (UC-39), and RegenerateRecoveryCodesCommandHandler (UC-40) — the "code against TOTP, or |
| | | 161 | | // against the current email code, or against an unused recovery code" comparison lives in |
| | | 162 | | // exactly one place. |
| | 973 | 163 | | services.AddScoped<ITwoFactorFactorVerifier, TwoFactorFactorVerifier>(); |
| | | 164 | | |
| | | 165 | | // UC-39 (FR-2F-11): no validator — which shape of second factor (code vs. recoveryCode) is |
| | | 166 | | // valid depends on a database read (the pending row's AppEnabled/EmailEnabled), the same |
| | | 167 | | // reason UC-37/UC-38 have none. |
| | 973 | 168 | | services.AddAuditedCommandHandler<DisableTwoFactorAuthCommand, DisableTwoFactorAuthCommandOutput, DisableTwoFact |
| | | 169 | | |
| | | 170 | | // UC-40 (FR-2F-12): no validator either, same reason as UC-39 — reuses ITwoFactorFactorVerifier |
| | | 171 | | // (registered above) rather than reimplementing the second-factor check. |
| | 973 | 172 | | services.AddAuditedCommandHandler<RegenerateRecoveryCodesCommand, RegenerateRecoveryCodesCommandOutput, Regenera |
| | | 173 | | |
| | 973 | 174 | | return services; |
| | | 175 | | } |
| | | 176 | | } |