Skip to content

Commit

Permalink
Circuit handler to capture users
Browse files Browse the repository at this point in the history
  • Loading branch information
neozhu committed Nov 12, 2024
1 parent 9275e50 commit b5611d4
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;
using System.Security.Claims;

namespace CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;

/// <summary>
/// Interface for setting and clearing the current user context.
Expand All @@ -8,8 +10,8 @@ public interface ICurrentUserContextSetter
/// <summary>
/// Sets the current user context with the provided session information.
/// </summary>
/// <param name="sessionInfo">The session information of the current user.</param>
void SetCurrentUser(SessionInfo sessionInfo);
/// <param name="user">The session information of the current user.</param>
void SetCurrentUser(ClaimsPrincipal user);

/// <summary>
/// Clears the current user context.
Expand Down
70 changes: 44 additions & 26 deletions src/Infrastructure/Services/Circuits/UserSessionCircuitHandler.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,55 @@
using CleanArchitecture.Blazor.Application.Features.Fusion;
using CleanArchitecture.Blazor.Infrastructure.Extensions;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.Http;

namespace CleanArchitecture.Blazor.Infrastructure.Services.Circuits;

/// <summary>
/// Handles user session tracking and online user tracking for Blazor server circuits.
/// </summary>
public class UserSessionCircuitHandler : CircuitHandler
public class UserSessionCircuitHandler : CircuitHandler,IDisposable
{
private readonly IServiceProvider _serviceProvider;
private readonly ICurrentUserContextSetter _currentUserContextSetter;
private readonly AuthenticationStateProvider _authenticationStateProvider;

/// <summary>
/// Initializes a new instance of the <see cref="UserSessionCircuitHandler"/> class.
/// </summary>
/// <param name="userSessionTracker">The user session tracker service.</param>
/// <param name="onlineUserTracker">The online user tracker service.</param>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
public UserSessionCircuitHandler(IServiceProvider serviceProvider)
public UserSessionCircuitHandler(IServiceProvider serviceProvider, ICurrentUserContextSetter currentUserContextSetter, AuthenticationStateProvider authenticationStateProvider)
{
_serviceProvider = serviceProvider;
_currentUserContextSetter = currentUserContextSetter;
_authenticationStateProvider = authenticationStateProvider;
}
public override Task OnCircuitOpenedAsync(Circuit circuit,
CancellationToken cancellationToken)
{
_authenticationStateProvider.AuthenticationStateChanged +=
AuthenticationChanged;

return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}

private void AuthenticationChanged(Task<AuthenticationState> task)
{
_ = UpdateAuthentication(task);

async Task UpdateAuthentication(Task<AuthenticationState> task)
{
try
{
var state = await task;
_currentUserContextSetter.SetCurrentUser(state.User);
}
catch
{
}
}
}
/// <summary>
/// Called when a new circuit connection is established.
/// </summary>
Expand All @@ -31,31 +58,16 @@ public UserSessionCircuitHandler(IServiceProvider serviceProvider)
/// <returns>A task that represents the asynchronous operation.</returns>
public override async Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
var httpContextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
_currentUserContextSetter.SetCurrentUser(state.User);
var usersStateContainer = _serviceProvider.GetRequiredService<IUsersStateContainer>();
var currentUserContextSetter = _serviceProvider.GetRequiredService<ICurrentUserContextSetter>();
var context = httpContextAccessor.HttpContext;

if (context?.User?.Identity?.IsAuthenticated == true)
if (state.User.Identity?.IsAuthenticated??false)
{
var headers = context?.Request?.Headers;
string clientIp = headers != null && headers.ContainsKey("X-Forwarded-For")
? headers["X-Forwarded-For"].ToString().Split(',').First().Trim()
: context!.Connection.RemoteIpAddress?.ToString() ?? "";
var sessionInfo = new SessionInfo
(
context.User.GetUserId(),
context.User.GetUserName(),
context.User.GetDisplayName(),
clientIp,
context.User.GetTenantId(),
context.User.GetProfilePictureDataUrl(),
UserPresence.Available
);
currentUserContextSetter.SetCurrentUser(sessionInfo);
if (!string.IsNullOrEmpty(sessionInfo.UserId))
var userId = state.User.GetUserId();
if (!string.IsNullOrEmpty(userId))
{
usersStateContainer.AddOrUpdate(circuit.Id, sessionInfo.UserId);
usersStateContainer.AddOrUpdate(circuit.Id, userId);
}
}
await base.OnConnectionUpAsync(circuit, cancellationToken);
Expand All @@ -74,7 +86,7 @@ public override async Task OnConnectionDownAsync(Circuit circuit, CancellationTo
var onlineUserTracker = _serviceProvider.GetRequiredService<IOnlineUserTracker>();
var usersStateContainer = _serviceProvider.GetRequiredService<IUsersStateContainer>();
var currentUserContextSetter = _serviceProvider.GetRequiredService<ICurrentUserContextSetter>();
if (currentUserAccessor.SessionInfo!=null)
if (currentUserAccessor.SessionInfo != null)
{
await userSessionTracker.RemoveAllSessions(currentUserAccessor.SessionInfo.UserId, cancellationToken);
await onlineUserTracker.Clear(currentUserAccessor.SessionInfo.UserId, cancellationToken);
Expand All @@ -84,4 +96,10 @@ public override async Task OnConnectionDownAsync(Circuit circuit, CancellationTo
currentUserContextSetter.Clear();
await base.OnConnectionDownAsync(circuit, cancellationToken);
}
}

public void Dispose()
{
_authenticationStateProvider.AuthenticationStateChanged -=
AuthenticationChanged;
}
}
18 changes: 14 additions & 4 deletions src/Infrastructure/Services/Identity/CurrentUserContextSetter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace CleanArchitecture.Blazor.Infrastructure.Services.Identity;
using CleanArchitecture.Blazor.Infrastructure.Extensions;

namespace CleanArchitecture.Blazor.Infrastructure.Services.Identity;

/// <summary>
/// Service for setting and clearing the current user context.
Expand All @@ -19,10 +21,18 @@ public CurrentUserContextSetter(ICurrentUserContext currentUserContext)
/// <summary>
/// Sets the current user context with the provided session information.
/// </summary>
/// <param name="sessionInfo">The session information of the current user.</param>
public void SetCurrentUser(SessionInfo sessionInfo)
/// <param name="user">The session information of the current user.</param>
public void SetCurrentUser(ClaimsPrincipal user)
{
_currentUserContext.SessionInfo = sessionInfo;
_currentUserContext.SessionInfo = new SessionInfo(
user.GetUserId(),
user.GetUserName(),
user.GetDisplayName(),
"",
user.GetTenantId(),
user.GetProfilePictureDataUrl(),
UserPresence.Available
);
}

/// <summary>
Expand Down

0 comments on commit b5611d4

Please sign in to comment.