using System.Security.Claims; using System.Text.Encodings.Web; using Femto.Api.Sessions; using Femto.Common; using Femto.Modules.Auth.Application; using Femto.Modules.Auth.Application.Dto; using Femto.Modules.Auth.Contracts; using Femto.Modules.Auth.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; namespace Femto.Api.Auth; internal class SessionAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IAuthService authService, CurrentUserContext currentUserContext ) : AuthenticationHandler(options, logger, encoder) { protected override async Task HandleAuthenticateAsync() { Logger.LogDebug("{TraceId} Authenticating session", this.Context.TraceIdentifier); var user = await this.TryAuthenticateWithSession(); if (user is null) user = await this.TryAuthenticateWithRememberMeToken(); if (user is null) return AuthenticateResult.NoResult(); var claims = new List { new(ClaimTypes.Name, user.Username), new("sub", user.Id.ToString()), new("user_id", user.Id.ToString()), }; claims.AddRange(user.Roles.Select(role => new Claim(ClaimTypes.Role, role.ToString()))); var identity = new ClaimsIdentity(claims, this.Scheme.Name); var principal = new ClaimsPrincipal(identity); currentUserContext.CurrentUser = new CurrentUser( user.Id, user.Username, user.Roles.Contains(Role.SuperUser) ); return AuthenticateResult.Success(new AuthenticationTicket(principal, this.Scheme.Name)); } private async Task TryAuthenticateWithSession() { var sessionId = this.Context.GetSessionId(); if (sessionId is null) { Logger.LogDebug("{TraceId} SessionId was null ", this.Context.TraceIdentifier); return null; } var session = await authService.GetSession(sessionId); if (session is null) { Logger.LogDebug("{TraceId} Loaded session was null ", this.Context.TraceIdentifier); return null; } if (session.IsExpired) { Logger.LogDebug("{TraceId} Loaded session was expired ", this.Context.TraceIdentifier); await authService.DeleteSession(sessionId); this.Context.DeleteSession(); return null; } var user = await authService.GetUserWithId(session.UserId); if (user is null) { await authService.DeleteSession(sessionId); this.Context.DeleteSession(); return null; } if (session.ExpiresSoon) { session = await authService.CreateWeakSession(session.UserId); this.Context.SetSession(session, user); } return user; } private async Task TryAuthenticateWithRememberMeToken() { /* * load remember me from token * if it is null, return null * if it exists, validate it * if it is valid, create a new weak session, return the user * if it is almost expired, refresh it */ var rememberMeToken = this.Context.GetRememberMeToken(); if (rememberMeToken is null) return null; var (user, newRememberMeToken) = await authService.GetUserWithRememberMeToken( rememberMeToken ); if (user is null) return null; var session = await authService.CreateWeakSession(user.Id); this.Context.SetSession(session, user); if (newRememberMeToken is not null) this.Context.SetRememberMeToken(newRememberMeToken); return user; } }