using FastEndpoints; using FrameworkDAL.DTO.KeyCloak; using Microsoft.Extensions.Options; namespace FrameworkSL.Endpoints.KeyCloak { // H-17 fix (Authentication_Analysis.md): migrated off MVC ControllerBase // (was KeyCloakService.RedirectToKeycloakLogin under [Route("Authorize")]) onto native // FastEndpoints — CLAUDE.md prohibits MVC controllers repo-wide. Pre-auth/public (no LoginDTO // yet), so this bypasses BaseEndpoint (which always serializes a ResponseStandardDTO // body) in favor of a plain Endpoint, mirroring // PartnerSL/EndPoints/ClientDomain/GetDomainTenant.cs's existing precedent for anonymous, // non-BaseEndpoint FastEndpoints in this repo. public class KeyCloakLoginRedirect : Endpoint { private readonly KeycloakOptions _keycloakOptions; public KeyCloakLoginRedirect(IOptions keycloakOptions) { _keycloakOptions = keycloakOptions.Value; } public override void Configure() { Get("/Authorize/UserLoginvalidation"); AllowAnonymous(); } public class Request { [QueryParam] public string Realm { get; set; } = ""; [QueryParam] public string ClientId { get; set; } = ""; [QueryParam] public string BackendCallback { get; set; } = ""; [QueryParam] public string FECallBackUrl { get; set; } = ""; } public override async Task HandleAsync(Request req, CancellationToken ct) { if (string.IsNullOrWhiteSpace(req.Realm) || string.IsNullOrWhiteSpace(req.ClientId) || string.IsNullOrWhiteSpace(req.BackendCallback)) { await Send.StringAsync("Missing parameters.", 400, cancellation: ct); return; } // No client secret in state — the authorize redirect never needs it (only the token // exchange does); the callback resolves it fresh from Vault by realm+clientId. string state = Uri.EscapeDataString($"{req.Realm}|{req.ClientId}|{req.BackendCallback}|{req.FECallBackUrl}"); string loginUrl = $"{_keycloakOptions.KeycloakHost}/realms/{req.Realm}/protocol/openid-connect/auth" + $"?client_id={req.ClientId}" + $"&response_type=code" + $"&scope=openid" + $"&redirect_uri={Uri.EscapeDataString(req.BackendCallback)}" + $"&state={state}"; // allowRemoteRedirects: true — this always targets the tenant's own configured // Keycloak host (server-side config, never client input), not a redirect target // supplied by the caller. await Send.RedirectAsync(loginUrl, allowRemoteRedirects: true); } } }