diff --git a/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs b/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs index 084dad3..50bf914 100644 --- a/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs +++ b/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs @@ -41,6 +41,7 @@ public class ApiKey : AggregateRoot /// /// Gets or sets the display name of the API key. /// + /// The display name has not been initialized yet. public DisplayName DisplayName { get => _displayName ?? throw new InvalidOperationException($"The {nameof(DisplayName)} has not been initialized yet."); @@ -79,15 +80,21 @@ public Description? Description /// /// Gets or sets the expiration date and time of the API key. /// + /// The new expiration date and time was greater (more in the future) than the old expiration date and time. + /// The date and time was not set in the future. public DateTime? ExpiresOn { get => _expiresOn; set { - if (_expiresOn.HasValue && _expiresOn.Value.AsUniversalTime() <= DateTime.UtcNow) + if (value.HasValue && value.Value.AsUniversalTime() <= DateTime.UtcNow) { throw new ArgumentOutOfRangeException(nameof(ExpiresOn), "The expiration date and time must be set in the future."); } + if (_expiresOn.HasValue && (!value.HasValue || value.Value.AsUniversalTime() > _expiresOn.Value.AsUniversalTime())) + { + throw new ArgumentException("The API key expiration cannot be extended.", nameof(ExpiresOn)); + } if (_expiresOn != value) { @@ -154,7 +161,7 @@ protected virtual void Handle(ApiKeyCreated @event) /// The role to be added. /// The actor identifier. /// The role and API key tenant identifiers do not match. - public void AddRole(Role role, ActorId actorId = default) + public void AddRole(Role role, ActorId? actorId = null) { if (role.TenantId != TenantId) { @@ -236,7 +243,7 @@ public void Delete(ActorId? actorId = null) /// /// The role to be removed. /// The actor identifier. - public void RemoveRole(Role role, ActorId actorId = default) + public void RemoveRole(Role role, ActorId? actorId = null) { if (HasRole(role)) { @@ -270,6 +277,7 @@ public void RemoveCustomAttribute(string key) /// /// The key of the custom attribute. /// The value of the custom attribute. + /// The key was not a valid identifier. public void SetCustomAttribute(string key, string value) { if (string.IsNullOrWhiteSpace(value)) diff --git a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordCreatedEvent.cs b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordCreated.cs similarity index 65% rename from old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordCreatedEvent.cs rename to lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordCreated.cs index 2a43a49..772c1a1 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordCreatedEvent.cs +++ b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordCreated.cs @@ -1,19 +1,13 @@ using Logitar.EventSourcing; -using Logitar.Identity.Domain.Shared; using MediatR; -namespace Logitar.Identity.Domain.Passwords.Events; +namespace Logitar.Identity.Core.Passwords.Events; /// /// The event raised when a new One-Time Password (OTP) is created. /// -public class OneTimePasswordCreatedEvent : DomainEvent, INotification +public record OneTimePasswordCreated : DomainEvent, INotification { - /// - /// Gets the tenant identifier of the One-Time Password (OTP). - /// - public TenantId? TenantId { get; } - /// /// Gets the encoded value of the One-Time Password (OTP). /// @@ -29,17 +23,15 @@ public class OneTimePasswordCreatedEvent : DomainEvent, INotification public int? MaximumAttempts { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The expiration date and time of the One-Time Password (OTP). /// The maximum number of attempts of the One-Time Password (OTP). /// The encoded value of the One-Time Password (OTP). - /// The tenant identifier of the One-Time Password (OTP). - public OneTimePasswordCreatedEvent(DateTime? expiresOn, int? maximumAttempts, Password password, TenantId? tenantId) + public OneTimePasswordCreated(DateTime? expiresOn, int? maximumAttempts, Password password) { ExpiresOn = expiresOn; MaximumAttempts = maximumAttempts; Password = password; - TenantId = tenantId; } } diff --git a/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordDeleted.cs b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordDeleted.cs new file mode 100644 index 0000000..439b81d --- /dev/null +++ b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordDeleted.cs @@ -0,0 +1,9 @@ +using Logitar.EventSourcing; +using MediatR; + +namespace Logitar.Identity.Core.Passwords.Events; + +/// +/// The event raised when a One-Time Password (OTP) is deleted. +/// +public record OneTimePasswordDeleted : DomainEvent, IDeleteEvent, INotification; diff --git a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordUpdatedEvent.cs b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordUpdated.cs similarity index 81% rename from old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordUpdatedEvent.cs rename to lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordUpdated.cs index e67af45..4b15b0b 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordUpdatedEvent.cs +++ b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordUpdated.cs @@ -1,12 +1,12 @@ using Logitar.EventSourcing; using MediatR; -namespace Logitar.Identity.Domain.Passwords.Events; +namespace Logitar.Identity.Core.Passwords.Events; /// /// The event raised when an existing One-Time Password (OTP) is modified. /// -public class OneTimePasswordUpdatedEvent : DomainEvent, INotification +public record OneTimePasswordUpdated : DomainEvent, INotification { /// /// Gets or sets the custom attribute modifications of the One-Time Password (OTP). diff --git a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordValidationFailedEvent.cs b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordValidationFailed.cs similarity index 53% rename from old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordValidationFailedEvent.cs rename to lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordValidationFailed.cs index bb45c15..abe0f5e 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordValidationFailedEvent.cs +++ b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordValidationFailed.cs @@ -1,9 +1,9 @@ using Logitar.EventSourcing; using MediatR; -namespace Logitar.Identity.Domain.Passwords.Events; +namespace Logitar.Identity.Core.Passwords.Events; /// /// The event raised when a One-Time Password (OTP) validation failed. /// -public class OneTimePasswordValidationFailedEvent : DomainEvent, INotification; +public record OneTimePasswordValidationFailed : DomainEvent, INotification; diff --git a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordValidationSucceededEvent.cs b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordValidationSucceeded.cs similarity index 53% rename from old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordValidationSucceededEvent.cs rename to lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordValidationSucceeded.cs index a0d4dab..b2ce8ea 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordValidationSucceededEvent.cs +++ b/lib/Logitar.Identity.Core/Passwords/Events/OneTimePasswordValidationSucceeded.cs @@ -1,9 +1,9 @@ using Logitar.EventSourcing; using MediatR; -namespace Logitar.Identity.Domain.Passwords.Events; +namespace Logitar.Identity.Core.Passwords.Events; /// /// The event raised when a One-Time Password (OTP) is successfully validated. /// -public class OneTimePasswordValidationSucceededEvent : DomainEvent, INotification; +public record OneTimePasswordValidationSucceeded : DomainEvent, INotification; diff --git a/old/src/Logitar.Identity.Domain/Passwords/IncorrectOneTimePasswordPasswordException.cs b/lib/Logitar.Identity.Core/Passwords/IncorrectOneTimePasswordPasswordException.cs similarity index 66% rename from old/src/Logitar.Identity.Domain/Passwords/IncorrectOneTimePasswordPasswordException.cs rename to lib/Logitar.Identity.Core/Passwords/IncorrectOneTimePasswordPasswordException.cs index 6c02fff..60e0134 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/IncorrectOneTimePasswordPasswordException.cs +++ b/lib/Logitar.Identity.Core/Passwords/IncorrectOneTimePasswordPasswordException.cs @@ -1,6 +1,4 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.Passwords; +namespace Logitar.Identity.Core.Passwords; /// /// The exception raised when a One-Time Password (OTP) validation fails. @@ -10,15 +8,15 @@ public class IncorrectOneTimePasswordPasswordException : InvalidCredentialsExcep /// /// A generic error message for this exception. /// - public new const string ErrorMessage = "The specified password did not match the One-Time Password (OTP)."; + private const string ErrorMessage = "The specified password did not match the One-Time Password (OTP)."; /// /// Gets or sets the identifier of the One-Time Password (OTP). /// - public OneTimePasswordId OneTimePasswordId + public string OneTimePasswordId { - get => new((string)Data[nameof(OneTimePasswordId)]!); - private set => Data[nameof(OneTimePasswordId)] = value.Value; + get => (string)Data[nameof(OneTimePasswordId)]!; + private set => Data[nameof(OneTimePasswordId)] = value; } /// /// Gets or sets the attempted password. @@ -34,14 +32,14 @@ public string AttemptedPassword /// /// The One-Time Password (OTP). /// The attempted password. - public IncorrectOneTimePasswordPasswordException(OneTimePasswordAggregate oneTimePassword, string attemptedPassword) + public IncorrectOneTimePasswordPasswordException(OneTimePassword oneTimePassword, string attemptedPassword) : base(BuildMessage(oneTimePassword, attemptedPassword)) { AttemptedPassword = attemptedPassword; - OneTimePasswordId = oneTimePassword.Id; + OneTimePasswordId = oneTimePassword.Id.Value; } - private static string BuildMessage(OneTimePasswordAggregate oneTimePassword, string attemptedPassword) => new ErrorMessageBuilder(ErrorMessage) + private static string BuildMessage(OneTimePassword oneTimePassword, string attemptedPassword) => new ErrorMessageBuilder(ErrorMessage) .AddData(nameof(OneTimePasswordId), oneTimePassword.Id.Value) .AddData(nameof(AttemptedPassword), attemptedPassword) .Build(); diff --git a/old/src/Logitar.Identity.Domain/Passwords/MaximumAttemptsReachedException.cs b/lib/Logitar.Identity.Core/Passwords/MaximumAttemptsReachedException.cs similarity index 62% rename from old/src/Logitar.Identity.Domain/Passwords/MaximumAttemptsReachedException.cs rename to lib/Logitar.Identity.Core/Passwords/MaximumAttemptsReachedException.cs index ed8b249..c8f8b6b 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/MaximumAttemptsReachedException.cs +++ b/lib/Logitar.Identity.Core/Passwords/MaximumAttemptsReachedException.cs @@ -1,6 +1,4 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.Passwords; +namespace Logitar.Identity.Core.Passwords; /// /// The exception raised when the maximum number of attempts has been reached for a One-Time Password (OTP). @@ -10,15 +8,15 @@ public class MaximumAttemptsReachedException : InvalidCredentialsException /// /// A generic error message for this exception. /// - public new const string ErrorMessage = "The maximum number of attempts has been reached for this One-Time Password (OTP)."; + private const string ErrorMessage = "The maximum number of attempts has been reached for this One-Time Password (OTP)."; /// /// Gets or sets the identifier of the One-Time Password (OTP). /// - public OneTimePasswordId OneTimePasswordId + public string OneTimePasswordId { - get => new((string)Data[nameof(OneTimePasswordId)]!); - private set => Data[nameof(OneTimePasswordId)] = value.Value; + get => (string)Data[nameof(OneTimePasswordId)]!; + private set => Data[nameof(OneTimePasswordId)] = value; } /// /// Gets or sets the number of attempts. @@ -34,14 +32,14 @@ public int AttemptCount /// /// The One-Time Password (OTP). /// The number of attempts. - public MaximumAttemptsReachedException(OneTimePasswordAggregate oneTimePassword, int attemptCount) + public MaximumAttemptsReachedException(OneTimePassword oneTimePassword, int attemptCount) : base(BuildMessage(oneTimePassword, attemptCount)) { AttemptCount = attemptCount; - OneTimePasswordId = oneTimePassword.Id; + OneTimePasswordId = oneTimePassword.Id.Value; } - private static string BuildMessage(OneTimePasswordAggregate oneTimePassword, int attemptCount) => new ErrorMessageBuilder(ErrorMessage) + private static string BuildMessage(OneTimePassword oneTimePassword, int attemptCount) => new ErrorMessageBuilder(ErrorMessage) .AddData(nameof(OneTimePasswordId), oneTimePassword.Id.Value) .AddData(nameof(AttemptCount), attemptCount) .Build(); diff --git a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordAggregate.cs b/lib/Logitar.Identity.Core/Passwords/OneTimePassword.cs similarity index 68% rename from old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordAggregate.cs rename to lib/Logitar.Identity.Core/Passwords/OneTimePassword.cs index 9d080f5..f67d60f 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordAggregate.cs +++ b/lib/Logitar.Identity.Core/Passwords/OneTimePassword.cs @@ -1,30 +1,37 @@ -using FluentValidation; -using Logitar.EventSourcing; -using Logitar.Identity.Domain.Passwords.Events; -using Logitar.Identity.Domain.Passwords.Validators; -using Logitar.Identity.Domain.Shared; -using Logitar.Identity.Domain.Shared.Validators; +using Logitar.EventSourcing; +using Logitar.Identity.Core.ApiKeys; +using Logitar.Identity.Core.Passwords.Events; -namespace Logitar.Identity.Domain.Passwords; +namespace Logitar.Identity.Core.Passwords; /// /// Represents a One-Time Password (OTP) in the identity system. These passwords can be used for multiple purposes, such as Multi-Factor Authentication (MFA). /// Several attempts can be made for a single password. Passwords can expire, and once they have been successfully validated, then cannot be used again. /// -public class OneTimePasswordAggregate : AggregateRoot +public class OneTimePassword : AggregateRoot { + /// + /// The updated event. + /// + private OneTimePasswordUpdated _updated = new(); + + /// + /// The encoded value of the One-Time Password (OTP). + /// private Password? _password = null; - private OneTimePasswordUpdatedEvent _updatedEvent = new(); /// /// Gets the identifier of the One-Time Password (OTP). /// - public new OneTimePasswordId Id => new(base.Id); - + public new ApiKeyId Id => new(base.Id); /// /// Gets the tenant identifier of the One-Time Password (OTP). /// - public TenantId? TenantId { get; private set; } + public TenantId? TenantId => Id.TenantId; + /// + /// Gets the entity identifier of the One-Time Password (OTP). This identifier is unique within the tenant. + /// + public EntityId? EntityId => Id.EntityId; /// /// Gets or sets the expiration date and time of the One-Time Password (OTP). @@ -44,6 +51,9 @@ public class OneTimePasswordAggregate : AggregateRoot /// public bool HasValidationSucceeded { get; private set; } + /// + /// The custom attributes of the One-Time Password (OTP). + /// private readonly Dictionary _customAttributes = []; /// /// Gets the custom attributes of the One-Time Password (OTP). @@ -51,46 +61,44 @@ public class OneTimePasswordAggregate : AggregateRoot public IReadOnlyDictionary CustomAttributes => _customAttributes.AsReadOnly(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// DO NOT use this constructor to create a new One-Time Password (OTP). It is only intended to be used by the event sourcing. /// - public OneTimePasswordAggregate() : base() + public OneTimePassword() : base() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The encoded value of the One-Time Password (OTP). - /// The tenant identifier of the One-Time Password (OTP). /// The expiration date and time of the One-Time Password (OTP). /// The maximum number of attempts of the One-Time Password (OTP). /// The actor identifier. /// The identifier of the One-Time Password (OTP). - public OneTimePasswordAggregate(Password password, TenantId? tenantId = null, DateTime? expiresOn = null, int? maximumAttempts = null, ActorId actorId = default, OneTimePasswordId? id = null) - : base((id ?? OneTimePasswordId.NewId()).AggregateId) + /// The expiration date time was not set in the future, or the maximum number of attempts was negative or zero. + public OneTimePassword(Password password, DateTime? expiresOn = null, int? maximumAttempts = null, ActorId? actorId = null, OneTimePasswordId? id = null) + : base((id ?? OneTimePasswordId.NewId()).StreamId) { - if (expiresOn.HasValue) + if (expiresOn.HasValue && expiresOn.Value.AsUniversalTime() <= DateTime.UtcNow) { - new ExpirationValidator().ValidateAndThrow(expiresOn.Value); + throw new ArgumentOutOfRangeException(nameof(expiresOn), "The expiration date and time must be set in the future."); } - if (maximumAttempts.HasValue) + if (maximumAttempts.HasValue && maximumAttempts.Value < 1) { - new MaximumAttemptsValidator().ValidateAndThrow(maximumAttempts.Value); + throw new ArgumentOutOfRangeException(nameof(maximumAttempts), "There should be at least one attempt to validate the One-Time Password (OTP)."); } - Raise(new OneTimePasswordCreatedEvent(expiresOn, maximumAttempts, password, tenantId), actorId); + Raise(new OneTimePasswordCreated(expiresOn, maximumAttempts, password), actorId); } /// /// Applies the specified event. /// /// The event to apply. - protected virtual void Apply(OneTimePasswordCreatedEvent @event) + protected virtual void Handle(OneTimePasswordCreated @event) { _password = @event.Password; - TenantId = @event.TenantId; - ExpiresOn = @event.ExpiresOn; MaximumAttempts = @event.MaximumAttempts; } @@ -99,11 +107,11 @@ protected virtual void Apply(OneTimePasswordCreatedEvent @event) /// Deletes the One-Time Password (OTP), if it is not already deleted. /// /// The actor identifier. - public void Delete(ActorId actorId = default) + public void Delete(ActorId? actorId = null) { if (!IsDeleted) { - Raise(new OneTimePasswordDeletedEvent(), actorId); + Raise(new OneTimePasswordDeleted(), actorId); } } @@ -121,30 +129,36 @@ public void Delete(ActorId actorId = default) public void RemoveCustomAttribute(string key) { key = key.Trim(); - - if (_customAttributes.ContainsKey(key)) + if (_customAttributes.Remove(key)) { - _updatedEvent.CustomAttributes[key] = null; - _customAttributes.Remove(key); + _updated.CustomAttributes[key] = null; } } - private readonly CustomAttributeValidator _customAttributeValidator = new(); /// /// Sets the specified custom attribute on the One-Time Password (OTP). /// /// The key of the custom attribute. /// The value of the custom attribute. + /// The key was not a valid identifier. public void SetCustomAttribute(string key, string value) { + if (string.IsNullOrWhiteSpace(value)) + { + RemoveCustomAttribute(key); + } + key = key.Trim(); value = value.Trim(); - _customAttributeValidator.ValidateAndThrow(key, value); + if (!key.IsIdentifier()) + { + throw new ArgumentException("The value must be an identifier.", nameof(key)); + } if (!_customAttributes.TryGetValue(key, out string? existingValue) || existingValue != value) { - _updatedEvent.CustomAttributes[key] = value; _customAttributes[key] = value; + _updated.CustomAttributes[key] = value; } } @@ -152,19 +166,19 @@ public void SetCustomAttribute(string key, string value) /// Applies updates on the One-Time Password (OTP). /// /// The actor identifier. - public void Update(ActorId actorId = default) + public void Update(ActorId? actorId = null) { - if (_updatedEvent.HasChanges) + if (_updated.HasChanges) { - Raise(_updatedEvent, actorId, DateTime.Now); - _updatedEvent = new(); + Raise(_updated, actorId, DateTime.Now); + _updated = new(); } } /// /// Applies the specified event. /// /// The event to apply. - protected virtual void Apply(OneTimePasswordUpdatedEvent @event) + protected virtual void Handle(OneTimePasswordUpdated @event) { foreach (KeyValuePair customAttribute in @event.CustomAttributes) { @@ -188,7 +202,7 @@ protected virtual void Apply(OneTimePasswordUpdatedEvent @event) /// The One-Time Password (OTP) is expired. /// The maximum number of attempts of the One-Time Password (OTP) has been reached. /// The specified password did not match. - public void Validate(string password, ActorId actorId = default) + public void Validate(string password, ActorId? actorId = null) { if (HasValidationSucceeded) { @@ -204,17 +218,17 @@ public void Validate(string password, ActorId actorId = default) } else if (_password == null || !_password.IsMatch(password)) { - Raise(new OneTimePasswordValidationFailedEvent(), actorId); + Raise(new OneTimePasswordValidationFailed(), actorId); throw new IncorrectOneTimePasswordPasswordException(this, password); } - Raise(new OneTimePasswordValidationSucceededEvent(), actorId); + Raise(new OneTimePasswordValidationSucceeded(), actorId); } /// /// Applies the specified event. /// /// The event to apply. - protected virtual void Apply(OneTimePasswordValidationFailedEvent _) + protected virtual void Handle(OneTimePasswordValidationFailed _) { AttemptCount++; } @@ -222,7 +236,7 @@ protected virtual void Apply(OneTimePasswordValidationFailedEvent _) /// Applies the specified event. /// /// The event to apply. - protected virtual void Apply(OneTimePasswordValidationSucceededEvent _) + protected virtual void Handle(OneTimePasswordValidationSucceeded _) { AttemptCount++; HasValidationSucceeded = true; diff --git a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordAlreadyUsedException.cs b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordAlreadyUsedException.cs similarity index 53% rename from old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordAlreadyUsedException.cs rename to lib/Logitar.Identity.Core/Passwords/OneTimePasswordAlreadyUsedException.cs index 8ea397c..1c11cc8 100644 --- a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordAlreadyUsedException.cs +++ b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordAlreadyUsedException.cs @@ -1,6 +1,4 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.Passwords; +namespace Logitar.Identity.Core.Passwords; /// /// The exception raised when a One-Time Password (OTP) has already been used. @@ -10,28 +8,28 @@ public class OneTimePasswordAlreadyUsedException : InvalidCredentialsException /// /// A generic error message for this exception. /// - public new const string ErrorMessage = "The specified One-Time Password (OTP) has already been used."; + private const string ErrorMessage = "The specified One-Time Password (OTP) has already been used."; /// /// Gets or sets the identifier of the One-Time Password (OTP). /// - public OneTimePasswordId OneTimePasswordId + public string OneTimePasswordId { - get => new((string)Data[nameof(OneTimePasswordId)]!); - private set => Data[nameof(OneTimePasswordId)] = value.Value; + get => (string)Data[nameof(OneTimePasswordId)]!; + private set => Data[nameof(OneTimePasswordId)] = value; } /// /// Initializes a new instance of the class. /// /// The One-Time Password (OTP). - public OneTimePasswordAlreadyUsedException(OneTimePasswordAggregate oneTimePassword) + public OneTimePasswordAlreadyUsedException(OneTimePassword oneTimePassword) : base(BuildMessage(oneTimePassword)) { - OneTimePasswordId = oneTimePassword.Id; + OneTimePasswordId = oneTimePassword.Id.Value; } - private static string BuildMessage(OneTimePasswordAggregate oneTimePassword) => new ErrorMessageBuilder(ErrorMessage) + private static string BuildMessage(OneTimePassword oneTimePassword) => new ErrorMessageBuilder(ErrorMessage) .AddData(nameof(oneTimePassword), oneTimePassword.Id) .Build(); } diff --git a/lib/Logitar.Identity.Core/Passwords/OneTimePasswordId.cs b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordId.cs new file mode 100644 index 0000000..34388d1 --- /dev/null +++ b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordId.cs @@ -0,0 +1,94 @@ +using Logitar.EventSourcing; + +namespace Logitar.Identity.Core.Passwords; + +/// +/// Represents the identifier of a One-Time Password (OTP). +/// +public readonly struct OneTimePasswordId +{ + /// + /// Gets the identifier of the event stream. + /// + public StreamId StreamId { get; } + /// + /// Gets the value of the identifier. + /// + public string Value => StreamId.Value; + + /// + /// Gets the tenant identifier. + /// + public TenantId? TenantId { get; } + /// + /// Gets the entity identifier. + /// + public EntityId EntityId { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// The tenant identifier. + /// The entity identifier. + public OneTimePasswordId(TenantId? tenantId, Guid entityId) : this(tenantId, Convert.ToBase64String(entityId.ToByteArray()).ToUriSafeBase64()) + { + } + /// + /// Initializes a new instance of the struct. + /// + /// The tenant identifier. + /// The entity identifier. + public OneTimePasswordId(TenantId? tenantId, string entityId) + { + TenantId = tenantId; + EntityId = new(entityId); + StreamId = new(tenantId.HasValue ? $"{tenantId}:{entityId}" : entityId); + } + /// + /// Initializes a new instance of the struct. + /// + /// A stream identifier. + public OneTimePasswordId(StreamId streamId) + { + StreamId = streamId; + } + + /// + /// Randomly generates a new One-Time Password (OTP) identifier. + /// + /// The tenant identifier. + /// The generated identifier. + public static OneTimePasswordId NewId(TenantId? tenantId = null) => new(tenantId, Guid.NewGuid()); + + /// + /// Returns a value indicating whether or not the specified identifiers are equal. + /// + /// The first identifier to compare. + /// The other identifier to compare. + /// True if the identifiers are equal. + public static bool operator ==(OneTimePasswordId left, OneTimePasswordId right) => left.Equals(right); + /// + /// Returns a value indicating whether or not the specified identifiers are different. + /// + /// The first identifier to compare. + /// The other identifier to compare. + /// True if the identifiers are different. + public static bool operator !=(OneTimePasswordId left, OneTimePasswordId right) => !left.Equals(right); + + /// + /// Returns a value indicating whether or not the specified object is equal to the identifier. + /// + /// The object to be compared to. + /// True if the object is equal to the identifier. + public override bool Equals([NotNullWhen(true)] object? obj) => obj is OneTimePasswordId id && id.Value == Value; + /// + /// Returns the hash code of the current identifier. + /// + /// The hash code. + public override int GetHashCode() => Value.GetHashCode(); + /// + /// Returns a string representation of the identifier. + /// + /// The string representation. + public override string ToString() => Value; +} diff --git a/lib/Logitar.Identity.Core/Passwords/OneTimePasswordIsExpiredException.cs b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordIsExpiredException.cs new file mode 100644 index 0000000..e1bc943 --- /dev/null +++ b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordIsExpiredException.cs @@ -0,0 +1,34 @@ +namespace Logitar.Identity.Core.Passwords; + +/// +/// The exception raised when an expired One-Time Password (OTP) is validated. +/// +public class OneTimePasswordIsExpiredException : InvalidCredentialsException +{ + /// + /// A generic error message for this exception. + /// + private const string ErrorMessage = "The specified One-Time Password (OTP) is expired."; + + /// + /// Gets the identifier of the expired One-Time Password (OTP). + /// + public string OneTimePasswordId + { + get => (string)Data[nameof(OneTimePasswordId)]!; + private set => Data[nameof(OneTimePasswordId)] = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// The One-Time Password (OTP) that is expired. + public OneTimePasswordIsExpiredException(OneTimePassword oneTimePassword) : base(BuildMessage(oneTimePassword)) + { + OneTimePasswordId = oneTimePassword.Id.Value; + } + + private static string BuildMessage(OneTimePassword oneTimePassword) => new ErrorMessageBuilder(ErrorMessage) + .AddData(nameof(OneTimePasswordId), oneTimePassword.Id) + .Build(); +} diff --git a/lib/Logitar.Identity.Core/Roles/Role.cs b/lib/Logitar.Identity.Core/Roles/Role.cs index ba2d029..33dd1cc 100644 --- a/lib/Logitar.Identity.Core/Roles/Role.cs +++ b/lib/Logitar.Identity.Core/Roles/Role.cs @@ -142,6 +142,7 @@ public void RemoveCustomAttribute(string key) /// /// The key of the custom attribute. /// The value of the custom attribute. + /// The key was not a valid identifier. public void SetCustomAttribute(string key, string value) { if (string.IsNullOrWhiteSpace(value)) @@ -179,7 +180,7 @@ public void SetUniqueName(UniqueName uniqueName, ActorId? actorId) /// Handles the specified event. /// /// The event to apply. - protected virtual void Apply(RoleUniqueNameChanged @event) + protected virtual void Handle(RoleUniqueNameChanged @event) { _uniqueName = @event.UniqueName; } diff --git a/old/.github/workflows/build.yml b/old/.github/workflows/build.yml deleted file mode 100644 index 266e2ea..0000000 --- a/old/.github/workflows/build.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Build Identity Solution - -on: - push: - branches: - - main - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - build: - name: Build Identity Solution - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Build Docker Image - run: docker build . -t francispion.azurecr.io/identity_api:${{ github.sha }} -f src/Logitar.Identity.Demo/Dockerfile diff --git a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordDeletedEvent.cs b/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordDeletedEvent.cs deleted file mode 100644 index fa59b3a..0000000 --- a/old/src/Logitar.Identity.Domain/Passwords/Events/OneTimePasswordDeletedEvent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Logitar.EventSourcing; -using MediatR; - -namespace Logitar.Identity.Domain.Passwords.Events; - -/// -/// The event raised when a One-Time Password (OTP) is deleted. -/// -public class OneTimePasswordDeletedEvent : DomainEvent, INotification -{ - /// - /// Initializes a new instance of the class. - /// - public OneTimePasswordDeletedEvent() - { - IsDeleted = true; - } -} diff --git a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordId.cs b/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordId.cs deleted file mode 100644 index 08cc860..0000000 --- a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordId.cs +++ /dev/null @@ -1,77 +0,0 @@ -using FluentValidation; -using Logitar.EventSourcing; -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.Passwords; - -/// -/// Represents the identifier of a One-Time Password (OTP). -/// -public record OneTimePasswordId -{ - /// - /// Gets the aggregate identifier. - /// - public AggregateId AggregateId { get; } - /// - /// Gets the value of the identifier. - /// - public string Value => AggregateId.Value; - - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier. - /// The name of the property, used for validation. - public OneTimePasswordId(Guid id, string? propertyName = null) : this(new AggregateId(id), propertyName) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The aggregate identifier. - /// The name of the property, used for validation. - public OneTimePasswordId(AggregateId aggregateId, string? propertyName = null) - { - new IdValidator(propertyName).ValidateAndThrow(aggregateId.Value); - - AggregateId = aggregateId; - } - - /// - /// Initializes a new instance of the class. - /// - /// The value of the identifier. - /// The name of the property, used for validation. - public OneTimePasswordId(string value, string? propertyName = null) - { - value = value.Trim(); - new IdValidator(propertyName).ValidateAndThrow(value); - - AggregateId = new(value); - } - - /// - /// Creates a new user identifier. - /// - /// The created identifier. - public static OneTimePasswordId NewId() => new(AggregateId.NewId()); - - /// - /// Returns null if the input is empty, or a new instance of the class otherwise. - /// - /// The value of the identifier. - /// The name of the property, used for validation. - /// The created instance or null. - public static OneTimePasswordId? TryCreate(string? value, string? propertyName = null) - { - return string.IsNullOrWhiteSpace(value) ? null : new(value, propertyName); - } - - /// - /// Converts the identifier to a . The conversion will fail if the identifier has not been created from a . - /// - /// The resulting Guid. - public Guid ToGuid() => AggregateId.ToGuid(); -} diff --git a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordIsExpiredException.cs b/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordIsExpiredException.cs deleted file mode 100644 index b15637b..0000000 --- a/old/src/Logitar.Identity.Domain/Passwords/OneTimePasswordIsExpiredException.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.Passwords; - -/// -/// The exception raised when an expired One-Time Password (OTP) is validated. -/// -public class OneTimePasswordIsExpiredException : InvalidCredentialsException -{ - /// - /// A generic error message for this exception. - /// - public new const string ErrorMessage = "The specified One-Time Password (OTP) is expired."; - - /// - /// Gets the identifier of the expired One-Time Password (OTP). - /// - public OneTimePasswordId OneTimePasswordId - { - get => new((string)Data[nameof(OneTimePasswordId)]!); - private set => Data[nameof(OneTimePasswordId)] = value.Value; - } - - /// - /// Initializes a new instance of the class. - /// - /// The One-Time Password (OTP) that is expired. - public OneTimePasswordIsExpiredException(OneTimePasswordAggregate oneTimePassword) : base(BuildMessage(oneTimePassword)) - { - OneTimePasswordId = oneTimePassword.Id; - } - - private static string BuildMessage(OneTimePasswordAggregate oneTimePassword) => new ErrorMessageBuilder(ErrorMessage) - .AddData(nameof(OneTimePasswordId), oneTimePassword.Id.Value) - .Build(); -} diff --git a/old/src/Logitar.Identity.Domain/Passwords/Validators/MaximumAttemptsValidator.cs b/old/src/Logitar.Identity.Domain/Passwords/Validators/MaximumAttemptsValidator.cs deleted file mode 100644 index fe2a46e..0000000 --- a/old/src/Logitar.Identity.Domain/Passwords/Validators/MaximumAttemptsValidator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FluentValidation; - -namespace Logitar.Identity.Domain.Passwords.Validators; - -/// -/// The validator used to validate One-Time Password (OTP) maximum attempts. -/// -public class MaximumAttemptsValidator : AbstractValidator -{ - /// - /// Initializes a new instance of the class. - /// - /// The name of the property, used for validation. - public MaximumAttemptsValidator(string? propertyName = null) - { - RuleFor(x => x).GreaterThan(0) - .WithErrorCode(nameof(MaximumAttemptsValidator)) - .WithPropertyName(propertyName); - } -}