Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplemented One-Time Passwords (OTP). #64

Merged
merged 4 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class ApiKey : AggregateRoot
/// <summary>
/// Gets or sets the display name of the API key.
/// </summary>
/// <exception cref="InvalidOperationException">The display name has not been initialized yet.</exception>
public DisplayName DisplayName
{
get => _displayName ?? throw new InvalidOperationException($"The {nameof(DisplayName)} has not been initialized yet.");
Expand Down Expand Up @@ -79,15 +80,21 @@ public Description? Description
/// <summary>
/// Gets or sets the expiration date and time of the API key.
/// </summary>
/// <exception cref="ArgumentException">The new expiration date and time was greater (more in the future) than the old expiration date and time.</exception>
/// <exception cref="ArgumentOutOfRangeException">The date and time was not set in the future.</exception>
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)
{
Expand Down Expand Up @@ -154,7 +161,7 @@ protected virtual void Handle(ApiKeyCreated @event)
/// <param name="role">The role to be added.</param>
/// <param name="actorId">The actor identifier.</param>
/// <exception cref="TenantMismatchException">The role and API key tenant identifiers do not match.</exception>
public void AddRole(Role role, ActorId actorId = default)
public void AddRole(Role role, ActorId? actorId = null)
{
if (role.TenantId != TenantId)
{
Expand Down Expand Up @@ -236,7 +243,7 @@ public void Delete(ActorId? actorId = null)
/// </summary>
/// <param name="role">The role to be removed.</param>
/// <param name="actorId">The actor identifier.</param>
public void RemoveRole(Role role, ActorId actorId = default)
public void RemoveRole(Role role, ActorId? actorId = null)
{
if (HasRole(role))
{
Expand Down Expand Up @@ -270,6 +277,7 @@ public void RemoveCustomAttribute(string key)
/// </summary>
/// <param name="key">The key of the custom attribute.</param>
/// <param name="value">The value of the custom attribute.</param>
/// <exception cref="ArgumentException">The key was not a valid identifier.</exception>
public void SetCustomAttribute(string key, string value)
{
if (string.IsNullOrWhiteSpace(value))
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// The event raised when a new One-Time Password (OTP) is created.
/// </summary>
public class OneTimePasswordCreatedEvent : DomainEvent, INotification
public record OneTimePasswordCreated : DomainEvent, INotification
{
/// <summary>
/// Gets the tenant identifier of the One-Time Password (OTP).
/// </summary>
public TenantId? TenantId { get; }

/// <summary>
/// Gets the encoded value of the One-Time Password (OTP).
/// </summary>
Expand All @@ -29,17 +23,15 @@ public class OneTimePasswordCreatedEvent : DomainEvent, INotification
public int? MaximumAttempts { get; }

/// <summary>
/// Initializes a new instance of the <see cref="OneTimePasswordCreatedEvent"/> class.
/// Initializes a new instance of the <see cref="OneTimePasswordCreated"/> class.
/// </summary>
/// <param name="expiresOn">The expiration date and time of the One-Time Password (OTP).</param>
/// <param name="maximumAttempts">The maximum number of attempts of the One-Time Password (OTP).</param>
/// <param name="password">The encoded value of the One-Time Password (OTP).</param>
/// <param name="tenantId">The tenant identifier of the One-Time Password (OTP).</param>
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Logitar.EventSourcing;
using MediatR;

namespace Logitar.Identity.Core.Passwords.Events;

/// <summary>
/// The event raised when a One-Time Password (OTP) is deleted.
/// </summary>
public record OneTimePasswordDeleted : DomainEvent, IDeleteEvent, INotification;
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using Logitar.EventSourcing;
using MediatR;

namespace Logitar.Identity.Domain.Passwords.Events;
namespace Logitar.Identity.Core.Passwords.Events;

/// <summary>
/// The event raised when an existing One-Time Password (OTP) is modified.
/// </summary>
public class OneTimePasswordUpdatedEvent : DomainEvent, INotification
public record OneTimePasswordUpdated : DomainEvent, INotification
{
/// <summary>
/// Gets or sets the custom attribute modifications of the One-Time Password (OTP).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Logitar.EventSourcing;
using MediatR;

namespace Logitar.Identity.Domain.Passwords.Events;
namespace Logitar.Identity.Core.Passwords.Events;

/// <summary>
/// The event raised when a One-Time Password (OTP) validation failed.
/// </summary>
public class OneTimePasswordValidationFailedEvent : DomainEvent, INotification;
public record OneTimePasswordValidationFailed : DomainEvent, INotification;
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Logitar.EventSourcing;
using MediatR;

namespace Logitar.Identity.Domain.Passwords.Events;
namespace Logitar.Identity.Core.Passwords.Events;

/// <summary>
/// The event raised when a One-Time Password (OTP) is successfully validated.
/// </summary>
public class OneTimePasswordValidationSucceededEvent : DomainEvent, INotification;
public record OneTimePasswordValidationSucceeded : DomainEvent, INotification;
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Logitar.Identity.Domain.Shared;

namespace Logitar.Identity.Domain.Passwords;
namespace Logitar.Identity.Core.Passwords;

/// <summary>
/// The exception raised when a One-Time Password (OTP) validation fails.
Expand All @@ -10,15 +8,15 @@ public class IncorrectOneTimePasswordPasswordException : InvalidCredentialsExcep
/// <summary>
/// A generic error message for this exception.
/// </summary>
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).";

/// <summary>
/// Gets or sets the identifier of the One-Time Password (OTP).
/// </summary>
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;
}
/// <summary>
/// Gets or sets the attempted password.
Expand All @@ -34,14 +32,14 @@ public string AttemptedPassword
/// </summary>
/// <param name="oneTimePassword">The One-Time Password (OTP).</param>
/// <param name="attemptedPassword">The attempted password.</param>
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Logitar.Identity.Domain.Shared;

namespace Logitar.Identity.Domain.Passwords;
namespace Logitar.Identity.Core.Passwords;

/// <summary>
/// The exception raised when the maximum number of attempts has been reached for a One-Time Password (OTP).
Expand All @@ -10,15 +8,15 @@ public class MaximumAttemptsReachedException : InvalidCredentialsException
/// <summary>
/// A generic error message for this exception.
/// </summary>
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).";

/// <summary>
/// Gets or sets the identifier of the One-Time Password (OTP).
/// </summary>
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;
}
/// <summary>
/// Gets or sets the number of attempts.
Expand All @@ -34,14 +32,14 @@ public int AttemptCount
/// </summary>
/// <param name="oneTimePassword">The One-Time Password (OTP).</param>
/// <param name="attemptCount">The number of attempts.</param>
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();
Expand Down
Loading
Loading