using Indotalent.ConfigFrontEnd.Service; using Indotalent.Data.Entities; using Indotalent.Features.Account.ForgotPassword.Cqrs; using Indotalent.Features.Account.Login.Cqrs; using Indotalent.Features.Account.Register.Cqrs; using Indotalent.Features.Account.ResetPassword.Cqrs; using Indotalent.Features.Account.TenantSelection.Cqrs; using Indotalent.Infrastructure.Authentication.Identity; using MediatR; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using System.Security.Claims; namespace Indotalent.Features.Account; public static class AccountEndPoint { public static IEndpointConventionBuilder MapAccountEndpoints(this IEndpointRouteBuilder endpoints) { var group = endpoints.MapGroup("/account").WithTags("Account"); group.MapPost("/signin-sso", async ([FromQuery] string email, UserManager userManager, SignInManager signInManager, IOptions jwtOptions, HttpContext context) => { if (string.IsNullOrWhiteSpace(email)) return Results.Json(new { status = 401, title = "Email is required" }, statusCode: 401); var user = await userManager.FindByEmailAsync(email); if (user == null || !user.IsActive) { return Results.Json(new { status = 401, title = "Account not found or inactive" }, statusCode: 401); } var jwtSettings = jwtOptions.Value; var token = JwtService.GenerateNewJwt(user, jwtSettings); var refreshToken = Guid.NewGuid().ToString().Replace("-", ""); user.RefreshToken = refreshToken; user.LastLoginAt = DateTime.Now; await userManager.UpdateAsync(user); await signInManager.SignInAsync(user, isPersistent: true); context.Response.Cookies.Append("X-Auth-Token", token, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(1) }); context.Response.Cookies.Append("X-Refresh-Token", refreshToken, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(7) }); return Results.Ok(new { status = 200, title = "SSO Login successful" }); }); group.MapGet("/active-user-exists", async ([FromQuery] string email, UserManager userManager) => { if (string.IsNullOrWhiteSpace(email)) { return Results.Ok(new { exists = false, isActive = false }); } var user = await userManager.FindByEmailAsync(email); if (user == null) { return Results.Ok(new { exists = false, isActive = false }); } return Results.Ok(new { exists = true, isActive = user.IsActive }); }); group.MapPost("/signin", async ([FromBody] LoginRequest request, IMediator mediator, HttpContext context, UserManager userManager) => { var command = new LoginCommand(request); var response = await mediator.Send(command); if (response.Succeeded) { var user = await userManager.FindByEmailAsync(request.Email); if (user != null) { context.Response.Cookies.Append("X-Auth-Token", response.Token!, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(1) }); context.Response.Cookies.Append("X-Refresh-Token", user.RefreshToken!, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(7) }); } return Results.Ok(new { status = 200, title = response.Message }); } return Results.Json(new { status = 401, title = response.Message }, statusCode: 401); }); group.MapPost("/refresh-token", async (HttpContext context) => { var userManager = context.RequestServices.GetRequiredService>(); var jwtSettings = context.RequestServices.GetRequiredService>().Value; var refreshToken = context.Request.Headers["X-Refresh-Token"].ToString(); if (string.IsNullOrEmpty(refreshToken)) return Results.Unauthorized(); var user = await userManager.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken); if (user == null || !user.IsActive) return Results.Unauthorized(); var newToken = JwtService.GenerateNewJwt(user, jwtSettings); context.Response.Cookies.Append("X-Auth-Token", newToken, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(1) }); return Results.Ok(new LoginResponse { Succeeded = true, Token = newToken }); }); group.MapPost("/signup", async ([FromBody] CreateUserRequest request, IMediator mediator) => { try { var command = new CreateUserCommand(request); var response = await mediator.Send(command); return Results.Ok(response); } catch (Exception ex) { return Results.BadRequest(new { errors = new[] { ex.Message } }); } }); group.MapPost("/signout", async ( SignInManager signInManager, UserManager userManager, HttpContext context) => { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (!string.IsNullOrEmpty(userId)) { var user = await userManager.FindByIdAsync(userId); if (user != null) { user.RefreshToken = null; await userManager.UpdateAsync(user); } } await signInManager.SignOutAsync(); context.Response.Cookies.Delete("X-Auth-Token"); context.Response.Cookies.Delete("X-Refresh-Token"); return Results.Ok(new { status = 200, title = "Successfully signed out" }); }); group.MapPost("/forgot-password", async ([FromBody] ForgotPasswordRequest request, IMediator mediator) => { try { var command = new ForgotPasswordCommand(request); var response = await mediator.Send(command); return Results.Ok(response); } catch (Exception ex) { return Results.BadRequest(new { errors = new[] { ex.Message } }); } }); group.MapPost("/logout", async (SignInManager signInManager) => { await signInManager.SignOutAsync(); return Results.Ok(new { title = "Success user logout" }); }).RequireAuthorization(); group.MapPost("/reset-password", async ([FromBody] ResetPasswordRequest request, IMediator mediator) => { try { var command = new ResetPasswordCommand(request); var response = await mediator.Send(command); return Results.Ok(response); } catch (Exception ex) { return Results.BadRequest(new { errors = new[] { ex.Message } }); } }); group.MapGet("/my-tenants", async (IMediator mediator, HttpContext context) => { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return Results.Unauthorized(); var query = new GetTenantsQuery(userId); var response = await mediator.Send(query); return Results.Ok(response); }).RequireAuthorization(); group.MapPost("/confirm-tenant", async ( [FromBody] ConfirmTenantRequest request, IMediator mediator, HttpContext context, SignInManager signInManager, UserManager userManager) => { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return Results.Unauthorized(); var command = new ConfirmTenantCommand(userId, request); var response = await mediator.Send(command); if (!response.Success) { return Results.Json(new { success = false, title = response.Message }, statusCode: 400); } var user = await userManager.FindByIdAsync(userId); if (user == null) return Results.Unauthorized(); var principal = await signInManager.CreateUserPrincipalAsync(user); var identity = (ClaimsIdentity?)principal.Identity; if (identity != null) { identity.AddClaim(new Claim(ClaimTypes.GroupSid, request.TenantId)); var jwtClaims = new List { new Claim(ClaimTypes.NameIdentifier, user.Id ?? string.Empty), new Claim(ClaimTypes.Name, user.UserName ?? string.Empty), new Claim(ClaimTypes.Email, user.Email ?? string.Empty), new Claim(ClaimTypes.GivenName, user.FullName ?? string.Empty), new Claim(ClaimTypes.GroupSid, request.TenantId) }; var jwtSettings = context.RequestServices.GetRequiredService>().Value; var newToken = JwtService.GenerateNewJwtWithClaims(user, jwtClaims, jwtSettings); var newRefreshToken = Guid.NewGuid().ToString().Replace("-", ""); user.RefreshToken = newRefreshToken; await userManager.UpdateAsync(user); context.Response.Cookies.Append("X-Auth-Token", newToken, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(1) }); return Results.Ok(new { success = true, token = newToken }); } return Results.Json(new { success = false, title = "Failed to reconstruct security claims" }, statusCode: 400); }).RequireAuthorization(); return group; } }