using System.Text.Json; using FrameworkDAL.DTO.SSO; using GB5Shared.DTO.SSO; using GB5Shared.SSO; using StackExchange.Redis; using FastEndpoints; namespace FrameworkSL.Endpoints.SSO { // H-18 fix: migrated off MVC ControllerBase (was SSOService.Logout under // [Route("SSO/Authorize")]) onto native FastEndpoints. public class SSOLogout : Endpoint { private readonly OidcDiscovery _discovery; private readonly IHttpClientFactory _httpFactory; private readonly IDatabase _db; private const string SESSION_PREFIX = "sso_session_"; public SSOLogout(OidcDiscovery discovery, IHttpClientFactory httpFactory, IConnectionMultiplexer redis) { _discovery = discovery; _httpFactory = httpFactory; _db = redis.GetDatabase(); } public override void Configure() { Get("/SSO/Authorize/Logout"); AllowAnonymous(); } public class Request { [QueryParam] public string Token { get; set; } = ""; } public override async Task HandleAsync(Request req, CancellationToken ct) { var raw = await _db.StringGetAsync(SESSION_PREFIX + req.Token); if (raw.IsNullOrEmpty) { await Send.StringAsync("Invalid or expired token", 400, cancellation: ct); return; } var session = JsonSerializer.Deserialize((string)raw!); await _db.KeyDeleteAsync(SESSION_PREFIX + req.Token); if (!string.IsNullOrEmpty(session?.RefreshToken)) { var revocation = await _discovery.GetRevocationEndpointAsync(ct); if (!string.IsNullOrEmpty(revocation)) { var http = _httpFactory.CreateClient("oidc"); await http.PostAsync(revocation, new FormUrlEncodedContent(new Dictionary { ["token"] = session.RefreshToken, ["client_id"] = session?.ClientId!, ["client_secret"] = session?.ClientSecret! }), ct); } } var endSession = await _discovery.GetEndSessionEndpointAsync(ct); var logoutUrl = $"{endSession}?" + $"post_logout_redirect_uri={Uri.EscapeDataString(session?.FrontendUrl!)}&" + $"id_token_hint={Uri.EscapeDataString(session?.IdToken!)}"; await Send.RedirectAsync(logoutUrl, allowRemoteRedirects: true); } } }