diff --git a/old/src/Logitar.Identity.Domain/LICENSE b/lib/Logitar.Identity.Core/LICENSE
similarity index 100%
rename from old/src/Logitar.Identity.Domain/LICENSE
rename to lib/Logitar.Identity.Core/LICENSE
diff --git a/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj b/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj
index 6c11a4d..6cacda5 100644
--- a/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj
+++ b/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj
@@ -3,8 +3,27 @@
net9.0
enable
-
- true
+ true
+ Logitar.Identity.Core
+ Francis Pion
+ Logitar
+ Logitar.Identity
+ Exposes domain aggregates, events, entities, value objects, exceptions and services of an Identity system.
+ © 2024 Logitar All Rights Reserved.
+ logitar.png
+ README.md
+ https://github.com/Logitar/Identity
+ git
+ 0.0.0.0
+ $(AssemblyVersion)
+ LICENSE
+ True
+ 0.0.0
+ en-CA
+ True
+ Rewrote the framework to include changes made to EventSourcing.
+ logitar;net;framework;identity;domain
+ https://github.com/Logitar/Identity/tree/main/lib/Logitar.Identity.Core
@@ -40,4 +59,19 @@
+
+
+ \
+ True
+
+
+ \
+ True
+
+
+ \
+ True
+
+
+
diff --git a/lib/Logitar.Identity.Core/README.md b/lib/Logitar.Identity.Core/README.md
new file mode 100644
index 0000000..a9b93b4
--- /dev/null
+++ b/lib/Logitar.Identity.Core/README.md
@@ -0,0 +1,3 @@
+# Logitar.Identity.Core
+
+Exposes domain aggregates, events, entities, value objects, exceptions and services of an Identity system.
diff --git a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionCreatedEvent.cs b/lib/Logitar.Identity.Core/Sessions/Events/SessionCreated.cs
similarity index 70%
rename from old/src/Logitar.Identity.Domain/Sessions/Events/SessionCreatedEvent.cs
rename to lib/Logitar.Identity.Core/Sessions/Events/SessionCreated.cs
index 5a8e9e2..d4d87e2 100644
--- a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionCreatedEvent.cs
+++ b/lib/Logitar.Identity.Core/Sessions/Events/SessionCreated.cs
@@ -1,14 +1,14 @@
using Logitar.EventSourcing;
-using Logitar.Identity.Domain.Passwords;
-using Logitar.Identity.Domain.Users;
+using Logitar.Identity.Core.Passwords;
+using Logitar.Identity.Core.Users;
using MediatR;
-namespace Logitar.Identity.Domain.Sessions.Events;
+namespace Logitar.Identity.Core.Sessions.Events;
///
/// The event raised when a new session is created.
///
-public class SessionCreatedEvent : DomainEvent, INotification
+public record SessionCreated : DomainEvent, INotification
{
///
/// Gets the identifier of the user owning the session.
@@ -20,11 +20,11 @@ public class SessionCreatedEvent : DomainEvent, INotification
public Password? Secret { get; }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The secret of the session.
/// The identifier of the user owning the session.
- public SessionCreatedEvent(Password? secret, UserId userId)
+ public SessionCreated(Password? secret, UserId userId)
{
Secret = secret;
UserId = userId;
diff --git a/lib/Logitar.Identity.Core/Sessions/Events/SessionDeleted.cs b/lib/Logitar.Identity.Core/Sessions/Events/SessionDeleted.cs
new file mode 100644
index 0000000..18de4eb
--- /dev/null
+++ b/lib/Logitar.Identity.Core/Sessions/Events/SessionDeleted.cs
@@ -0,0 +1,9 @@
+using Logitar.EventSourcing;
+using MediatR;
+
+namespace Logitar.Identity.Core.Sessions.Events;
+
+///
+/// The event raised when a session is deleted.
+///
+public record SessionDeleted : DomainEvent, IDeleteEvent, INotification;
diff --git a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionRenewedEvent.cs b/lib/Logitar.Identity.Core/Sessions/Events/SessionRenewed.cs
similarity index 66%
rename from old/src/Logitar.Identity.Domain/Sessions/Events/SessionRenewedEvent.cs
rename to lib/Logitar.Identity.Core/Sessions/Events/SessionRenewed.cs
index b486b76..ccc7ac7 100644
--- a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionRenewedEvent.cs
+++ b/lib/Logitar.Identity.Core/Sessions/Events/SessionRenewed.cs
@@ -1,13 +1,13 @@
using Logitar.EventSourcing;
-using Logitar.Identity.Domain.Passwords;
+using Logitar.Identity.Core.Passwords;
using MediatR;
-namespace Logitar.Identity.Domain.Sessions.Events;
+namespace Logitar.Identity.Core.Sessions.Events;
///
/// The event raised when a session is renewed.
///
-public class SessionRenewedEvent : DomainEvent, INotification
+public record SessionRenewed : DomainEvent, INotification
{
///
/// Gets the new secret of the session.
@@ -15,10 +15,10 @@ public class SessionRenewedEvent : DomainEvent, INotification
public Password Secret { get; }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The new secret of the session.
- public SessionRenewedEvent(Password secret)
+ public SessionRenewed(Password secret)
{
Secret = secret;
}
diff --git a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionSignedOutEvent.cs b/lib/Logitar.Identity.Core/Sessions/Events/SessionSignedOut.cs
similarity index 54%
rename from old/src/Logitar.Identity.Domain/Sessions/Events/SessionSignedOutEvent.cs
rename to lib/Logitar.Identity.Core/Sessions/Events/SessionSignedOut.cs
index de9fe2e..795c638 100644
--- a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionSignedOutEvent.cs
+++ b/lib/Logitar.Identity.Core/Sessions/Events/SessionSignedOut.cs
@@ -1,9 +1,9 @@
using Logitar.EventSourcing;
using MediatR;
-namespace Logitar.Identity.Domain.Sessions.Events;
+namespace Logitar.Identity.Core.Sessions.Events;
///
/// The event raised when an active session is signed-out.
///
-public class SessionSignedOutEvent : DomainEvent, INotification;
+public record SessionSignedOut : DomainEvent, INotification;
diff --git a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionUpdatedEvent.cs b/lib/Logitar.Identity.Core/Sessions/Events/SessionUpdated.cs
similarity index 69%
rename from old/src/Logitar.Identity.Domain/Sessions/Events/SessionUpdatedEvent.cs
rename to lib/Logitar.Identity.Core/Sessions/Events/SessionUpdated.cs
index 3cf2924..e2c8b2c 100644
--- a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionUpdatedEvent.cs
+++ b/lib/Logitar.Identity.Core/Sessions/Events/SessionUpdated.cs
@@ -1,17 +1,17 @@
using Logitar.EventSourcing;
using MediatR;
-namespace Logitar.Identity.Domain.Sessions.Events;
+namespace Logitar.Identity.Core.Sessions.Events;
///
/// The event raised when an existing session is modified.
///
-public class SessionUpdatedEvent : DomainEvent, INotification
+public record SessionUpdated : DomainEvent, INotification
{
///
/// Gets or sets the custom attribute modifications of the session.
///
- public Dictionary CustomAttributes { get; init; } = [];
+ public Dictionary CustomAttributes { get; init; } = [];
///
/// Gets a value indicating whether or not the session is being modified.
diff --git a/old/src/Logitar.Identity.Domain/Sessions/IncorrectSessionSecretException.cs b/lib/Logitar.Identity.Core/Sessions/IncorrectSessionSecretException.cs
similarity index 55%
rename from old/src/Logitar.Identity.Domain/Sessions/IncorrectSessionSecretException.cs
rename to lib/Logitar.Identity.Core/Sessions/IncorrectSessionSecretException.cs
index 0b3c5f3..87fa516 100644
--- a/old/src/Logitar.Identity.Domain/Sessions/IncorrectSessionSecretException.cs
+++ b/lib/Logitar.Identity.Core/Sessions/IncorrectSessionSecretException.cs
@@ -1,6 +1,4 @@
-using Logitar.Identity.Domain.Shared;
-
-namespace Logitar.Identity.Domain.Sessions;
+namespace Logitar.Identity.Core.Sessions;
///
/// The exception raised when a session secret check fails.
@@ -10,15 +8,15 @@ public class IncorrectSessionSecretException : InvalidCredentialsException
///
/// A generic error message for this exception.
///
- public new const string ErrorMessage = "The specified secret did not match the session.";
+ private const string ErrorMessage = "The specified secret did not match the session.";
///
/// Gets or sets the identifier of the session.
///
- public SessionId SessionId
+ public string SessionId
{
- get => new((string)Data[nameof(SessionId)]!);
- private set => Data[nameof(SessionId)] = value.Value;
+ get => (string)Data[nameof(SessionId)]!;
+ private set => Data[nameof(SessionId)] = value;
}
///
/// Gets or sets the attempted secret.
@@ -34,15 +32,21 @@ public string AttemptedSecret
///
/// The attempted secret.
/// The session.
- public IncorrectSessionSecretException(SessionAggregate session, string attemptedSecret)
+ public IncorrectSessionSecretException(Session session, string attemptedSecret)
: base(BuildMessage(session, attemptedSecret))
{
- SessionId = session.Id;
+ SessionId = session.Id.Value;
AttemptedSecret = attemptedSecret;
}
- private static string BuildMessage(SessionAggregate session, string attemptedSecret) => new ErrorMessageBuilder(ErrorMessage)
+ ///
+ /// Builds the exception message.
+ ///
+ /// The attempted secret.
+ /// The session.
+ /// The exception message.
+ private static string BuildMessage(Session session, string attemptedSecret) => new ErrorMessageBuilder(ErrorMessage)
.AddData(nameof(AttemptedSecret), attemptedSecret)
- .AddData(nameof(SessionId), session.Id.Value)
+ .AddData(nameof(SessionId), session.Id)
.Build();
}
diff --git a/old/src/Logitar.Identity.Domain/Sessions/SessionAggregate.cs b/lib/Logitar.Identity.Core/Sessions/Session.cs
similarity index 65%
rename from old/src/Logitar.Identity.Domain/Sessions/SessionAggregate.cs
rename to lib/Logitar.Identity.Core/Sessions/Session.cs
index a502d78..69c4de8 100644
--- a/old/src/Logitar.Identity.Domain/Sessions/SessionAggregate.cs
+++ b/lib/Logitar.Identity.Core/Sessions/Session.cs
@@ -1,24 +1,37 @@
using Logitar.EventSourcing;
-using Logitar.Identity.Domain.Passwords;
-using Logitar.Identity.Domain.Sessions.Events;
-using Logitar.Identity.Domain.Shared;
-using Logitar.Identity.Domain.Users;
+using Logitar.Identity.Core.Passwords;
+using Logitar.Identity.Core.Sessions.Events;
+using Logitar.Identity.Core.Users;
-namespace Logitar.Identity.Domain.Sessions;
+namespace Logitar.Identity.Core.Sessions;
///
/// Represents a session in the identity system. A session allows an user to perform actions in a timeframe.
/// It can be signed-out to close the timeframe, or renewed to extend the timeframe.
///
-public class SessionAggregate : AggregateRoot
+public class Session : AggregateRoot
{
- private SessionUpdatedEvent _updatedEvent = new();
+ ///
+ /// The updated event.
+ ///
+ private SessionUpdated _updated = new();
///
/// Gets the identifier of the session.
///
- public new SessionId Id => new(base.Id);
+ public new UserId Id => new(base.Id);
+ ///
+ /// Gets the tenant identifier of the session.
+ ///
+ public TenantId? TenantId => Id.TenantId;
+ ///
+ /// Gets the entity identifier of the session. This identifier is unique within the tenant.
+ ///
+ public EntityId? EntityId => Id.EntityId;
+ ///
+ /// The identifier of the user owning the session.
+ ///
private UserId? _userId = null;
///
/// Gets the identifier of the user owning the session.
@@ -26,6 +39,9 @@ public class SessionAggregate : AggregateRoot
/// The user identifier has not been initialized yet.
public UserId UserId => _userId ?? throw new InvalidOperationException($"The {nameof(UserId)} has not been initialized yet.");
+ ///
+ /// The secret of the session.
+ ///
private Password? _secret = null;
///
/// Gets a value indicating whether or not the session is persistent.
@@ -39,39 +55,47 @@ public class SessionAggregate : AggregateRoot
///
public bool IsActive { get; private set; }
- private readonly Dictionary _customAttributes = [];
+ ///
+ /// The custom attributes of the session.
+ ///
+ private readonly Dictionary _customAttributes = [];
///
/// Gets the custom attributes of the session.
///
- public IReadOnlyDictionary CustomAttributes => _customAttributes.AsReadOnly();
+ 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 session. It is only intended to be used by the event sourcing.
///
- public SessionAggregate() : base()
+ public Session() : base()
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
/// DO use this constructor to create a new user.
///
/// The user owning the session.
/// The secret of the session.
/// (Optional) The actor identifier. This parameter should be left null so that it defaults to the user's identifier.
/// The identifier of the session.
- public SessionAggregate(UserAggregate user, Password? secret = null, ActorId? actorId = null, SessionId? id = null)
- : base((id ?? SessionId.NewId()).AggregateId)
+ /// The user and session tenant identifiers do not match.
+ public Session(User user, Password? secret = null, ActorId? actorId = null, SessionId? id = null) : base((id ?? SessionId.NewId()).StreamId)
{
+ if (id.HasValue && id.Value.TenantId != user.TenantId)
+ {
+ throw new TenantMismatchException(id.Value.TenantId, user.TenantId);
+ }
+
actorId ??= new(user.Id.Value);
- Raise(new SessionCreatedEvent(secret, user.Id), actorId.Value);
+ Raise(new SessionCreated(secret, user.Id), actorId.Value);
}
///
/// Applies the specified event.
///
/// The event to apply.
- protected virtual void Apply(SessionCreatedEvent @event)
+ protected virtual void Handle(SessionCreated @event)
{
_userId = @event.UserId;
@@ -84,11 +108,11 @@ protected virtual void Apply(SessionCreatedEvent @event)
/// Deletes the session, 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 SessionDeletedEvent(), actorId);
+ Raise(new SessionDeleted(), actorId);
}
}
@@ -96,14 +120,11 @@ public void Delete(ActorId actorId = default)
/// Removes the specified custom attribute on the session.
///
/// The key of the custom attribute.
- public void RemoveCustomAttribute(string key)
+ public void RemoveCustomAttribute(Identifier key)
{
- key = key.Trim();
-
- if (_customAttributes.ContainsKey(key))
+ if (_customAttributes.Remove(key))
{
- _updatedEvent.CustomAttributes[key] = null;
- _customAttributes.Remove(key);
+ _updated.CustomAttributes[key] = null;
}
}
@@ -132,33 +153,34 @@ public void Renew(string currentSecret, Password newSecret, ActorId? actorId = d
}
actorId ??= new(UserId.Value);
- Raise(new SessionRenewedEvent(newSecret), actorId.Value);
+ Raise(new SessionRenewed(newSecret), actorId.Value);
}
///
/// Applies the specified event.
///
/// The event to apply.
- protected virtual void Apply(SessionRenewedEvent @event)
+ protected virtual void Handle(SessionRenewed @event)
{
_secret = @event.Secret;
}
- private readonly CustomAttributeValidator _customAttributeValidator = new();
///
/// Sets the specified custom attribute on the session.
///
/// The key of the custom attribute.
/// The value of the custom attribute.
- public void SetCustomAttribute(string key, string value)
+ public void SetCustomAttribute(Identifier key, string value)
{
- key = key.Trim();
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ RemoveCustomAttribute(key);
+ }
value = value.Trim();
- _customAttributeValidator.ValidateAndThrow(key, value);
if (!_customAttributes.TryGetValue(key, out string? existingValue) || existingValue != value)
{
- _updatedEvent.CustomAttributes[key] = value;
_customAttributes[key] = value;
+ _updated.CustomAttributes[key] = value;
}
}
@@ -171,14 +193,14 @@ public void SignOut(ActorId? actorId = default)
if (IsActive)
{
actorId ??= new(UserId.Value);
- Raise(new SessionSignedOutEvent(), actorId.Value);
+ Raise(new SessionSignedOut(), actorId.Value);
}
}
///
/// Applies the specified event.
///
/// The event to apply.
- protected virtual void Apply(SessionSignedOutEvent _)
+ protected virtual void Handle(SessionSignedOut _)
{
IsActive = false;
}
@@ -187,21 +209,21 @@ protected virtual void Apply(SessionSignedOutEvent _)
/// Applies updates on the session.
///
/// 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(SessionUpdatedEvent @event)
+ protected virtual void Handle(SessionUpdated @event)
{
- foreach (KeyValuePair customAttribute in @event.CustomAttributes)
+ foreach (KeyValuePair customAttribute in @event.CustomAttributes)
{
if (customAttribute.Value == null)
{
diff --git a/lib/Logitar.Identity.Core/Sessions/SessionId.cs b/lib/Logitar.Identity.Core/Sessions/SessionId.cs
new file mode 100644
index 0000000..85fb717
--- /dev/null
+++ b/lib/Logitar.Identity.Core/Sessions/SessionId.cs
@@ -0,0 +1,94 @@
+using Logitar.EventSourcing;
+
+namespace Logitar.Identity.Core.Sessions;
+
+///
+/// Represents the identifier of a session.
+///
+public readonly struct SessionId
+{
+ ///
+ /// 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 SessionId(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 SessionId(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 SessionId(StreamId streamId)
+ {
+ StreamId = streamId;
+ }
+
+ ///
+ /// Randomly generates a new session identifier.
+ ///
+ /// The tenant identifier.
+ /// The generated identifier.
+ public static SessionId 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 ==(SessionId left, SessionId 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 !=(SessionId left, SessionId 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 SessionId 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/Sessions/SessionIsNotActiveException.cs b/lib/Logitar.Identity.Core/Sessions/SessionIsNotActiveException.cs
new file mode 100644
index 0000000..5693443
--- /dev/null
+++ b/lib/Logitar.Identity.Core/Sessions/SessionIsNotActiveException.cs
@@ -0,0 +1,39 @@
+namespace Logitar.Identity.Core.Sessions;
+
+///
+/// The exception raised when an inactive session is renewed.
+///
+public class SessionIsNotActiveException : InvalidCredentialsException
+{
+ ///
+ /// A generic error message for this exception.
+ ///
+ private const string ErrorMessage = "The specified session is not active.";
+
+ ///
+ /// Gets the identifier of the inactive session.
+ ///
+ public string SessionId
+ {
+ get => (string)Data[nameof(SessionId)]!;
+ private set => Data[nameof(SessionId)] = value;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The session that is not active.
+ public SessionIsNotActiveException(Session session) : base(BuildMessage(session))
+ {
+ SessionId = session.Id.Value;
+ }
+
+ ///
+ /// Builds the exception message.
+ ///
+ /// The session that is not active.
+ /// The exception message.
+ private static string BuildMessage(Session session) => new ErrorMessageBuilder(ErrorMessage)
+ .AddData(nameof(SessionId), session.Id)
+ .Build();
+}
diff --git a/lib/Logitar.Identity.Core/Sessions/SessionIsNotPersistentException.cs b/lib/Logitar.Identity.Core/Sessions/SessionIsNotPersistentException.cs
new file mode 100644
index 0000000..973da72
--- /dev/null
+++ b/lib/Logitar.Identity.Core/Sessions/SessionIsNotPersistentException.cs
@@ -0,0 +1,39 @@
+namespace Logitar.Identity.Core.Sessions;
+
+///
+/// The exception raised when an ephemeral (not persistent) session is renewed.
+///
+public class SessionIsNotPersistentException : InvalidCredentialsException
+{
+ ///
+ /// A generic error message for this exception.
+ ///
+ private const string ErrorMessage = "The specified session is not persistent.";
+
+ ///
+ /// Gets the identifier of the ephemeral session.
+ ///
+ public string SessionId
+ {
+ get => (string)Data[nameof(SessionId)]!;
+ private set => Data[nameof(SessionId)] = value;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The session that is ephemeral.
+ public SessionIsNotPersistentException(Session session) : base(BuildMessage(session))
+ {
+ SessionId = session.Id.Value;
+ }
+
+ ///
+ /// Builds the exception message.
+ ///
+ /// The session that is ephemeral.
+ /// The exception message.
+ private static string BuildMessage(Session session) => new ErrorMessageBuilder(ErrorMessage)
+ .AddData(nameof(SessionId), session.Id)
+ .Build();
+}
diff --git a/lib/Logitar.Identity.Core/Users/Events/UserSignedIn.cs b/lib/Logitar.Identity.Core/Users/Events/UserSignedIn.cs
index 9051629..d540ff5 100644
--- a/lib/Logitar.Identity.Core/Users/Events/UserSignedIn.cs
+++ b/lib/Logitar.Identity.Core/Users/Events/UserSignedIn.cs
@@ -12,7 +12,7 @@ public record UserSignedIn : DomainEvent, INotification
/// Initializes a new instance of the class.
///
/// The date and time when the authentication occurred.
- public UserSignedIn(DateTime occurredOn) // TODO(fpion): remove?
+ public UserSignedIn(DateTime occurredOn)
{
OccurredOn = occurredOn;
}
diff --git a/lib/Logitar.Identity.Core/Users/User.cs b/lib/Logitar.Identity.Core/Users/User.cs
index cd7eec7..b37213f 100644
--- a/lib/Logitar.Identity.Core/Users/User.cs
+++ b/lib/Logitar.Identity.Core/Users/User.cs
@@ -1,6 +1,7 @@
using Logitar.EventSourcing;
using Logitar.Identity.Core.Passwords;
using Logitar.Identity.Core.Roles;
+using Logitar.Identity.Core.Sessions;
using Logitar.Identity.Core.Users.Events;
namespace Logitar.Identity.Core.Users;
@@ -679,7 +680,7 @@ public void SetCustomIdentifier(Identifier key, CustomIdentifier value, ActorId?
/// Applies the specified event.
///
/// The event to apply.
- protected virtual void Apply(UserIdentifierChanged @event)
+ protected virtual void Handle(UserIdentifierChanged @event)
{
_customIdentifiers[@event.Key] = @event.Value;
}
@@ -765,6 +766,64 @@ protected virtual void Handle(UserUniqueNameChanged @event)
_uniqueName = @event.UniqueName;
}
+ ///
+ /// Signs-in the user without a password check, opening a new session.
+ ///
+ /// The secret of the session.
+ /// (Optional) The actor identifier. This parameter should be left null so that it defaults to the user's identifier.
+ /// The identifier of the session.
+ /// The newly opened session.
+ /// The password is incorrect.
+ /// The user has no password.
+ /// The user is disabled.
+ public Session SignIn(Password? secret = null, ActorId? actorId = null, SessionId? sessionId = null)
+ {
+ return SignIn(password: null, secret, actorId, sessionId);
+ }
+ ///
+ /// Signs-in the user, opening a new session.
+ ///
+ /// The password to check.
+ /// The secret of the session.
+ /// (Optional) The actor identifier. This parameter should be left null so that it defaults to the user's identifier.
+ /// The identifier of the session.
+ /// The newly opened session.
+ /// The password is incorrect.
+ /// The user has no password.
+ /// The user is disabled.
+ public Session SignIn(string? password, Password? secret = null, ActorId? actorId = null, SessionId? sessionId = null)
+ {
+ if (IsDisabled)
+ {
+ throw new UserIsDisabledException(this);
+ }
+ else if (password != null)
+ {
+ if (_password == null)
+ {
+ throw new UserHasNoPasswordException(this);
+ }
+ else if (!_password.IsMatch(password))
+ {
+ throw new IncorrectUserPasswordException(this, password);
+ }
+ }
+
+ actorId ??= new(Id.Value);
+ Session session = new(this, secret, actorId, sessionId);
+ Raise(new UserSignedIn(session.CreatedOn), actorId.Value);
+
+ return session;
+ }
+ ///
+ /// Applies the specified event.
+ ///
+ /// The event to apply.
+ protected virtual void Handle(UserSignedIn @event)
+ {
+ AuthenticatedOn = @event.OccurredOn;
+ }
+
///
/// Applies updates on the user.
///
diff --git a/old/src/Logitar.Identity.Domain/logitar.png b/lib/Logitar.Identity.Core/logitar.png
similarity index 100%
rename from old/src/Logitar.Identity.Domain/logitar.png
rename to lib/Logitar.Identity.Core/logitar.png
diff --git a/old/src/Logitar.Identity.Domain/Logitar.Identity.Domain.csproj b/old/src/Logitar.Identity.Domain/Logitar.Identity.Domain.csproj
deleted file mode 100644
index aa9bf27..0000000
--- a/old/src/Logitar.Identity.Domain/Logitar.Identity.Domain.csproj
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- true
- Logitar.Identity.Domain
- Francis Pion
- Logitar
- Logitar.Identity
- Exposes aggregates, events and services from the Identity management platform domain.
- © 2024 Logitar All Rights Reserved.
- logitar.png
- README.md
- https://github.com/Logitar/Identity
- git
- 2.0.0.0
- $(AssemblyVersion)
- LICENSE
- True
- 2.0.0
- en-CA
- True
- Refactored domain events and aggregates.
- logitar;net;framework;identity;domain
- https://github.com/Logitar/Identity/tree/main/src/Logitar.Identity.Domain
-
-
-
- True
-
-
-
- True
-
-
-
-
- \
- True
-
-
- \
- True
-
-
- \
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/old/src/Logitar.Identity.Domain/README.md b/old/src/Logitar.Identity.Domain/README.md
deleted file mode 100644
index 2110005..0000000
--- a/old/src/Logitar.Identity.Domain/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Logitar.Identity.Domain
-
-Exposes domain aggregates, events and services of an Identity system.
diff --git a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionDeletedEvent.cs b/old/src/Logitar.Identity.Domain/Sessions/Events/SessionDeletedEvent.cs
deleted file mode 100644
index e77dbec..0000000
--- a/old/src/Logitar.Identity.Domain/Sessions/Events/SessionDeletedEvent.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Logitar.EventSourcing;
-using MediatR;
-
-namespace Logitar.Identity.Domain.Sessions.Events;
-
-///
-/// The event raised when a session is deleted.
-///
-public class SessionDeletedEvent : DomainEvent, INotification
-{
- ///
- /// Initializes a new instance of the class.
- ///
- public SessionDeletedEvent()
- {
- IsDeleted = true;
- }
-}
diff --git a/old/src/Logitar.Identity.Domain/Sessions/SessionId.cs b/old/src/Logitar.Identity.Domain/Sessions/SessionId.cs
deleted file mode 100644
index fca0d57..0000000
--- a/old/src/Logitar.Identity.Domain/Sessions/SessionId.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using FluentValidation;
-using Logitar.EventSourcing;
-using Logitar.Identity.Domain.Shared;
-
-namespace Logitar.Identity.Domain.Sessions;
-
-///
-/// Represents the identifier of a session.
-///
-public record SessionId
-{
- ///
- /// 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 SessionId(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 SessionId(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 SessionId(string value, string? propertyName = null)
- {
- value = value.Trim();
- new IdValidator(propertyName).ValidateAndThrow(value);
-
- AggregateId = new(value);
- }
-
- ///
- /// Creates a new session identifier.
- ///
- /// The created identifier.
- public static SessionId 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 SessionId? 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/Sessions/SessionIsNotActiveException.cs b/old/src/Logitar.Identity.Domain/Sessions/SessionIsNotActiveException.cs
deleted file mode 100644
index e192cc7..0000000
--- a/old/src/Logitar.Identity.Domain/Sessions/SessionIsNotActiveException.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Logitar.Identity.Domain.Shared;
-
-namespace Logitar.Identity.Domain.Sessions;
-
-///
-/// The exception raised when an inactive session is renewed.
-///
-public class SessionIsNotActiveException : InvalidCredentialsException
-{
- ///
- /// A generic error message for this exception.
- ///
- public new const string ErrorMessage = "The specified session is not active.";
-
- ///
- /// Gets the identifier of the inactive session.
- ///
- public SessionId SessionId
- {
- get => new((string)Data[nameof(SessionId)]!);
- private set => Data[nameof(SessionId)] = value.Value;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The session that is not active.
- public SessionIsNotActiveException(SessionAggregate session) : base(BuildMessage(session))
- {
- SessionId = session.Id;
- }
-
- private static string BuildMessage(SessionAggregate session) => new ErrorMessageBuilder(ErrorMessage)
- .AddData(nameof(SessionId), session.Id.Value)
- .Build();
-}
diff --git a/old/src/Logitar.Identity.Domain/Sessions/SessionIsNotPersistentException.cs b/old/src/Logitar.Identity.Domain/Sessions/SessionIsNotPersistentException.cs
deleted file mode 100644
index fd97a7a..0000000
--- a/old/src/Logitar.Identity.Domain/Sessions/SessionIsNotPersistentException.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Logitar.Identity.Domain.Shared;
-
-namespace Logitar.Identity.Domain.Sessions;
-
-///
-/// The exception raised when an ephemeral (not persistent) session is renewed.
-///
-public class SessionIsNotPersistentException : InvalidCredentialsException
-{
- ///
- /// A generic error message for this exception.
- ///
- public new const string ErrorMessage = "The specified session is not persistent.";
-
- ///
- /// Gets the identifier of the ephemeral session.
- ///
- public SessionId SessionId
- {
- get => new((string)Data[nameof(SessionId)]!);
- private set => Data[nameof(SessionId)] = value.Value;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The session that is ephemeral.
- public SessionIsNotPersistentException(SessionAggregate session) : base(BuildMessage(session))
- {
- SessionId = session.Id;
- }
-
- private static string BuildMessage(SessionAggregate session) => new ErrorMessageBuilder(ErrorMessage)
- .AddData(nameof(SessionId), session.Id.Value)
- .Build();
-}
diff --git a/old/src/Logitar.Identity.Domain/Users/UserAggregate.cs b/old/src/Logitar.Identity.Domain/Users/UserAggregate.cs
deleted file mode 100644
index 4daa5d8..0000000
--- a/old/src/Logitar.Identity.Domain/Users/UserAggregate.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Logitar.EventSourcing;
-using Logitar.Identity.Domain.Sessions;
-
-namespace Logitar.Identity.Domain.Users;
-
-public class UserAggregate : AggregateRoot
-{
- ///
- /// Signs-in the user without a password check, opening a new session.
- ///
- /// The secret of the session.
- /// (Optional) The actor identifier. This parameter should be left null so that it defaults to the user's identifier.
- /// The identifier of the session.
- /// The newly opened session.
- /// The user is disabled.
- public SessionAggregate SignIn(Password? secret = null, ActorId? actorId = null, SessionId? sessionId = null)
- {
- return SignIn(password: null, secret, actorId, sessionId);
- }
- ///
- /// Signs-in the user, opening a new session.
- ///
- /// The password to check.
- /// The secret of the session.
- /// (Optional) The actor identifier. This parameter should be left null so that it defaults to the user's identifier.
- /// The identifier of the session.
- /// The newly opened session.
- /// The password is incorrect.
- /// The user has no password.
- /// The user is disabled.
- public SessionAggregate SignIn(string? password, Password? secret = null, ActorId? actorId = null, SessionId? sessionId = null)
- {
- if (IsDisabled)
- {
- throw new UserIsDisabledException(this);
- }
- else if (password != null)
- {
- if (_password == null)
- {
- throw new UserHasNoPasswordException(this);
- }
- else if (!_password.IsMatch(password))
- {
- throw new IncorrectUserPasswordException(this, password);
- }
- }
-
- actorId ??= new(Id.Value);
- SessionAggregate session = new(this, secret, actorId, sessionId);
- Raise(new UserSignedInEvent(session.CreatedOn), actorId.Value);
-
- return session;
- }
- ///
- /// Applies the specified event.
- ///
- /// The event to apply.
- protected virtual void Apply(UserSignedInEvent @event)
- {
- AuthenticatedOn = @event.OccurredOn;
- }
-}