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);
- }
-}