Skip to content

Commit

Permalink
Reimplemented API keys. (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
Utar94 authored Dec 15, 2024
1 parent 18ef8b6 commit 7511909
Show file tree
Hide file tree
Showing 34 changed files with 619 additions and 661 deletions.

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions lib/Logitar.Identity.Core/ApiKeys/ApiKeyId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using Logitar.EventSourcing;

namespace Logitar.Identity.Core.ApiKeys;

/// <summary>
/// Represents the identifier of an API key.
/// </summary>
public readonly struct ApiKeyId
{
/// <summary>
/// Gets the identifier of the event stream.
/// </summary>
public StreamId StreamId { get; }
/// <summary>
/// Gets the value of the identifier.
/// </summary>
public string Value => StreamId.Value;

/// <summary>
/// Gets the tenant identifier.
/// </summary>
public TenantId? TenantId { get; }
/// <summary>
/// Gets the entity identifier.
/// </summary>
public EntityId EntityId { get; }

/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyId"/> struct.
/// </summary>
/// <param name="tenantId">The tenant identifier.</param>
/// <param name="entityId">The entity identifier.</param>
public ApiKeyId(TenantId? tenantId, Guid entityId) : this(tenantId, Convert.ToBase64String(entityId.ToByteArray()).ToUriSafeBase64())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyId"/> struct.
/// </summary>
/// <param name="tenantId">The tenant identifier.</param>
/// <param name="entityId">The entity identifier.</param>
public ApiKeyId(TenantId? tenantId, string entityId)
{
TenantId = tenantId;
EntityId = new(entityId);
StreamId = new(tenantId.HasValue ? $"{tenantId}:{entityId}" : entityId);
}
/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyId"/> struct.
/// </summary>
/// <param name="streamId">A stream identifier.</param>
public ApiKeyId(StreamId streamId)
{
StreamId = streamId;
}

/// <summary>
/// Randomly generates a new API key identifier.
/// </summary>
/// <param name="tenantId">The tenant identifier.</param>
/// <returns>The generated identifier.</returns>
public static ApiKeyId NewId(TenantId? tenantId = null) => new(tenantId, Guid.NewGuid());

/// <summary>
/// Returns a value indicating whether or not the specified identifiers are equal.
/// </summary>
/// <param name="left">The first identifier to compare.</param>
/// <param name="right">The other identifier to compare.</param>
/// <returns>True if the identifiers are equal.</returns>
public static bool operator ==(ApiKeyId left, ApiKeyId right) => left.Equals(right);
/// <summary>
/// Returns a value indicating whether or not the specified identifiers are different.
/// </summary>
/// <param name="left">The first identifier to compare.</param>
/// <param name="right">The other identifier to compare.</param>
/// <returns>True if the identifiers are different.</returns>
public static bool operator !=(ApiKeyId left, ApiKeyId right) => !left.Equals(right);

/// <summary>
/// Returns a value indicating whether or not the specified object is equal to the identifier.
/// </summary>
/// <param name="obj">The object to be compared to.</param>
/// <returns>True if the object is equal to the identifier.</returns>
public override bool Equals([NotNullWhen(true)] object? obj) => obj is ApiKeyId id && id.Value == Value;
/// <summary>
/// Returns the hash code of the current identifier.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode() => Value.GetHashCode();
/// <summary>
/// Returns a string representation of the identifier.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString() => Value;
}
39 changes: 39 additions & 0 deletions lib/Logitar.Identity.Core/ApiKeys/ApiKeyIsExpiredException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Logitar.Identity.Core.ApiKeys;

/// <summary>
/// The exception raised when an expired API key is authenticated.
/// </summary>
public class ApiKeyIsExpiredException : InvalidCredentialsException
{
/// <summary>
/// A generic error message for this exception.
/// </summary>
private const string ErrorMessage = "The specified API key is expired.";

/// <summary>
/// Gets the identifier of the expired API key.
/// </summary>
public string ApiKeyId
{
get => (string)Data[nameof(ApiKeyId)]!;
private set => Data[nameof(ApiKeyId)] = value;
}

/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyIsExpiredException"/> class.
/// </summary>
/// <param name="apiKey">The API key that is expired.</param>
public ApiKeyIsExpiredException(ApiKey apiKey) : base(BuildMessage(apiKey))
{
ApiKeyId = apiKey.Id.Value;
}

/// <summary>
/// Builds the exception message.
/// </summary>
/// <param name="apiKey">The API key that is expired.</param>
/// <returns>The exception message.</returns>
private static string BuildMessage(ApiKey apiKey) => new ErrorMessageBuilder(ErrorMessage)
.AddData(nameof(ApiKeyId), apiKey.Id)
.Build();
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Logitar.EventSourcing;
using MediatR;

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

/// <summary>
/// The event raised when an API key is authenticated.
/// </summary>
public class ApiKeyAuthenticatedEvent : DomainEvent, INotification;
public record ApiKeyAuthenticated : DomainEvent, INotification;
Original file line number Diff line number Diff line change
@@ -1,40 +1,32 @@
using Logitar.EventSourcing;
using Logitar.Identity.Domain.Passwords;
using Logitar.Identity.Domain.Shared;
using Logitar.Identity.Core.Passwords;
using MediatR;

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

/// <summary>
/// The event raised when a new API key is created.
/// </summary>
public class ApiKeyCreatedEvent : DomainEvent, INotification
public record ApiKeyCreated : DomainEvent, INotification
{
/// <summary>
/// Gets the secret of the API key.
/// </summary>
public Password Secret { get; }

/// <summary>
/// Gets the tenant identifier of the API key.
/// Gets the display name of the API key.
/// </summary>
public TenantId? TenantId { get; }
public DisplayName DisplayName { get; }

/// <summary>
/// Gets the display name of the API key.
/// Gets the secret of the API key.
/// </summary>
public DisplayNameUnit DisplayName { get; }
public Password Secret { get; }

/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyCreatedEvent"/> class.
/// Initializes a new instance of the <see cref="ApiKeyCreated"/> class.
/// </summary>
/// <param name="displayName">The display name of the API key.</param>
/// <param name="secret">The secret of the API key.</param>
/// <param name="tenantId">The tenant identifier of the API key.</param>
public ApiKeyCreatedEvent(DisplayNameUnit displayName, Password secret, TenantId? tenantId)
public ApiKeyCreated(DisplayName displayName, Password secret)
{
DisplayName = displayName;
Secret = secret;
TenantId = tenantId;
}
}
9 changes: 9 additions & 0 deletions lib/Logitar.Identity.Core/ApiKeys/Events/ApiKeyDeleted.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Logitar.EventSourcing;
using MediatR;

namespace Logitar.Identity.Core.ApiKeys.Events;

/// <summary>
/// The event raised when an API key is deleted.
/// </summary>
public record ApiKeyDeleted : DomainEvent, IDeleteEvent, INotification;
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
using Logitar.EventSourcing;
using Logitar.Identity.Domain.Roles;
using Logitar.Identity.Core.Roles;
using MediatR;

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

/// <summary>
/// The event raised when a role is added to an API key.
/// </summary>
public class ApiKeyRoleAddedEvent : DomainEvent, INotification
public record ApiKeyRoleAdded : DomainEvent, INotification
{
/// <summary>
/// Gets the role identifier.
/// </summary>
public RoleId RoleId { get; }

/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyRoleAddedEvent"/> class.
/// Initializes a new instance of the <see cref="ApiKeyRoleAdded"/> class.
/// </summary>
/// <param name="roleId">The role identifier.</param>
public ApiKeyRoleAddedEvent(RoleId roleId)
public ApiKeyRoleAdded(RoleId roleId)
{
RoleId = roleId;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
using Logitar.EventSourcing;
using Logitar.Identity.Domain.Roles;
using Logitar.Identity.Core.Roles;
using MediatR;

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

/// <summary>
/// The event raised when a role is removed from an API key.
/// </summary>
public class ApiKeyRoleRemovedEvent : DomainEvent, INotification
public record ApiKeyRoleRemoved : DomainEvent, INotification
{
/// <summary>
/// Gets the role identifier.
/// </summary>
public RoleId RoleId { get; }

/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyRoleRemovedEvent"/> class.
/// Initializes a new instance of the <see cref="ApiKeyRoleRemoved"/> class.
/// </summary>
/// <param name="roleId">The role identifier.</param>
public ApiKeyRoleRemovedEvent(RoleId roleId)
public ApiKeyRoleRemoved(RoleId roleId)
{
RoleId = roleId;
}
Expand Down
34 changes: 34 additions & 0 deletions lib/Logitar.Identity.Core/ApiKeys/Events/ApiKeyUpdated.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Logitar.EventSourcing;
using MediatR;

namespace Logitar.Identity.Core.ApiKeys.Events;

/// <summary>
/// The event raised when an API key is updated.
/// </summary>
public record ApiKeyUpdated : DomainEvent, INotification
{
/// <summary>
/// Gets or sets the new display name of the API key.
/// </summary>
public DisplayName? DisplayName { get; set; }
/// <summary>
/// Gets or sets the new description of the API key.
/// </summary>
public Change<Description>? Description { get; set; }
/// <summary>
/// Gets or sets the new expiration date and time of the API key.
/// </summary>
public DateTime? ExpiresOn { get; set; }

/// <summary>
/// Gets or sets the custom attribute modifications of the API key.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the API key has been updated.
/// </summary>
[JsonIgnore]
public bool HasChanges => DisplayName != null || Description != null || ExpiresOn != null || CustomAttributes.Count > 0;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Logitar.Identity.Domain.Shared;

namespace Logitar.Identity.Domain.ApiKeys;
namespace Logitar.Identity.Core.ApiKeys;

/// <summary>
/// The exception raised when an API key secret check fails.
Expand All @@ -10,15 +8,15 @@ public class IncorrectApiKeySecretException : InvalidCredentialsException
/// <summary>
/// A generic error message for this exception.
/// </summary>
public new const string ErrorMessage = "The specified secret did not match the API key.";
private const string ErrorMessage = "The specified secret did not match the API key.";

/// <summary>
/// Gets or sets the identifier of the API key.
/// </summary>
public ApiKeyId ApiKeyId
public string ApiKeyId
{
get => new((string)Data[nameof(ApiKeyId)]!);
private set => Data[nameof(ApiKeyId)] = value.Value;
get => (string)Data[nameof(ApiKeyId)]!;
private set => Data[nameof(ApiKeyId)] = value;
}
/// <summary>
/// Gets or sets the attempted secret.
Expand All @@ -34,15 +32,21 @@ public string AttemptedSecret
/// </summary>
/// <param name="apiKey">The API key.</param>
/// <param name="attemptedSecret">The attempted secret.</param>
public IncorrectApiKeySecretException(ApiKeyAggregate apiKey, string attemptedSecret)
public IncorrectApiKeySecretException(ApiKey apiKey, string attemptedSecret)
: base(BuildMessage(apiKey, attemptedSecret))
{
ApiKeyId = apiKey.Id;
ApiKeyId = apiKey.Id.Value;
AttemptedSecret = attemptedSecret;
}

private static string BuildMessage(ApiKeyAggregate apiKey, string attemptedSecret) => new ErrorMessageBuilder(ErrorMessage)
.AddData(nameof(ApiKeyId), apiKey.Id.Value)
/// <summary>
/// Builds the exception message.
/// </summary>
/// <param name="apiKey">The API key.</param>
/// <param name="attemptedSecret">The attempted secret.</param>
/// <returns>The exception message.</returns>
private static string BuildMessage(ApiKey apiKey, string attemptedSecret) => new ErrorMessageBuilder(ErrorMessage)
.AddData(nameof(ApiKeyId), apiKey.Id)
.AddData(nameof(AttemptedSecret), attemptedSecret)
.Build();
}
Loading

0 comments on commit 7511909

Please sign in to comment.