using System.Security.Claims; using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications; using AMREZ.EOP.Application.UseCases.Authentications; using AMREZ.EOP.Contracts.DTOs.Authentications.AddEmailIdentity; using AMREZ.EOP.Contracts.DTOs.Authentications.AssignRole; using AMREZ.EOP.Contracts.DTOs.Authentications.ChangePassword; using AMREZ.EOP.Contracts.DTOs.Authentications.DisableMfa; using AMREZ.EOP.Contracts.DTOs.Authentications.EnableTotp; using AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair; using AMREZ.EOP.Contracts.DTOs.Authentications.Login; using AMREZ.EOP.Contracts.DTOs.Authentications.Logout; using AMREZ.EOP.Contracts.DTOs.Authentications.LogoutAll; using AMREZ.EOP.Contracts.DTOs.Authentications.Refresh; using AMREZ.EOP.Contracts.DTOs.Authentications.Register; using AMREZ.EOP.Contracts.DTOs.Authentications.Role; using AMREZ.EOP.Contracts.DTOs.Authentications.UnassignRole; using AMREZ.EOP.Contracts.DTOs.Authentications.VerifyEmail; using AMREZ.EOP.Domain.Shared.Contracts; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; namespace AMREZ.EOP.API.Controllers; [ApiController] [Route("api/[controller]")] public class AuthenticationController : ControllerBase { private readonly ILoginUseCase _login; private readonly IRegisterUseCase _register; private readonly IChangePasswordUseCase _changePassword; private readonly IAddEmailIdentityUseCase _addEmail; private readonly IVerifyEmailUseCase _verifyEmail; private readonly IEnableTotpUseCase _enableTotp; private readonly IDisableMfaUseCase _disableMfa; private readonly ILogoutUseCase _logout; private readonly ILogoutAllUseCase _logoutAll; private readonly IIssueTokenPairUseCase _issueTokens; private readonly IRefreshUseCase _refresh; public AuthenticationController( ILoginUseCase login, IRegisterUseCase register, IChangePasswordUseCase changePassword, IAddEmailIdentityUseCase addEmail, IVerifyEmailUseCase verifyEmail, IEnableTotpUseCase enableTotp, IDisableMfaUseCase disableMfa, ILogoutUseCase logout, ILogoutAllUseCase logoutAll, IIssueTokenPairUseCase issueTokens, IRefreshUseCase refresh) { _login = login; _register = register; _changePassword = changePassword; _addEmail = addEmail; _verifyEmail = verifyEmail; _enableTotp = enableTotp; _disableMfa = disableMfa; _logout = logout; _logoutAll = logoutAll; _issueTokens = issueTokens; _refresh = refresh; } [HttpPost("login")] public async Task PostLogin([FromBody] LoginRequest body, CancellationToken ct) { var res = await _login.ExecuteAsync(body, ct); if (res is null) return Unauthorized(new { message = "Invalid credentials" }); var claims = new List { new(ClaimTypes.NameIdentifier, res.UserId.ToString()), new(ClaimTypes.Name, res.Email), new(ClaimTypes.Email, res.Email), new("tenant", res.TenantKey), new("tenantId", res.TenantId.ToString()) }; var roles = (res.Roles ?? Array.Empty()) .Where(r => !string.IsNullOrWhiteSpace(r)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); foreach (var r in roles) claims.Add(new Claim(ClaimTypes.Role, r)); if (roles.Length > 0) claims.Add(new Claim("roles_csv", string.Join(",", roles))); var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, AuthPolicies.Scheme)); await HttpContext.SignInAsync(AuthPolicies.Scheme, principal); var tokenPair = await _issueTokens.ExecuteAsync(new IssueTokenPairRequest() { UserId = res.UserId, TenantId = res.TenantId, Tenant = res.TenantKey, Email = res.Email, Roles = roles }, ct); if (!string.IsNullOrWhiteSpace(tokenPair.RefreshToken)) { Response.Cookies.Append( "refresh_token", tokenPair.RefreshToken!, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = tokenPair.RefreshExpiresAt?.UtcDateTime }); } return Ok(new { user = res, roles, access_token = tokenPair.AccessToken, token_type = "Bearer", expires_at = tokenPair.AccessExpiresAt }); } [HttpPost("refresh")] public async Task Refresh([FromBody] RefreshRequest body, CancellationToken ct) { var raw = string.IsNullOrWhiteSpace(body.RefreshToken) ? Request.Cookies["refresh_token"] : body.RefreshToken; if (string.IsNullOrWhiteSpace(raw)) return Unauthorized(new { message = "Missing refresh token" }); var res = await _refresh.ExecuteAsync(new RefreshRequest { RefreshToken = raw }, ct); if (res is null) return Unauthorized(new { message = "Invalid/expired refresh token" }); if (!string.IsNullOrWhiteSpace(res.RefreshToken)) { Response.Cookies.Append( "refresh_token", res.RefreshToken!, new CookieOptions { HttpOnly = true, Secure = false, SameSite = SameSiteMode.None, Expires = res.RefreshExpiresAt?.UtcDateTime }); } return Ok(new { access_token = res.AccessToken, token_type = "Bearer", expires_at = res.AccessExpiresAt }); } [HttpPost("register")] public async Task Register([FromBody] RegisterRequest body, CancellationToken ct) { var res = await _register.ExecuteAsync(body, ct); if (res is null) return Conflict(new { message = "Email already exists or tenant not found" }); return Created($"/api/authentication/users/{res.UserId}", res); } [HttpPost("change-password")] public async Task ChangePassword([FromBody] ChangePasswordRequest body, CancellationToken ct) { var ok = await _changePassword.ExecuteAsync(body, ct); if (!ok) return BadRequest(new { message = "Change password failed" }); return NoContent(); } [HttpPost("logout")] public async Task Logout() { await HttpContext.SignOutAsync(AuthPolicies.Scheme); return NoContent(); } [HttpPost("email")] public async Task AddEmail([FromBody] AddEmailIdentityRequest body, CancellationToken ct) { var ok = await _addEmail.ExecuteAsync(body, ct); if (!ok) return BadRequest(new { message = "Cannot add email identity" }); return NoContent(); } [HttpPost("email/verify")] public async Task VerifyEmail([FromBody] VerifyEmailRequest body, CancellationToken ct) { var ok = await _verifyEmail.ExecuteAsync(body, ct); if (!ok) return BadRequest(new { message = "Verify email failed" }); return NoContent(); } [HttpPost("totp/enable")] public async Task EnableTotp([FromBody] EnableTotpRequest body, CancellationToken ct) { var res = await _enableTotp.ExecuteAsync(body, ct); if (res is null) return BadRequest(new { message = "Enable TOTP failed" }); return Ok(res); } [HttpPost("disable")] public async Task DisableMfa([FromBody] DisableMfaRequest body, CancellationToken ct) { var ok = await _disableMfa.ExecuteAsync(body, ct); if (!ok) return BadRequest(new { message = "Disable MFA failed" }); return NoContent(); } [HttpPost("revoke")] public async Task Revoke([FromBody] LogoutRequest body, CancellationToken ct) { var ok = await _logout.ExecuteAsync(body, ct); if (!ok) return NotFound(new { message = "Session not found" }); return NoContent(); } [HttpPost("revoke-all")] public async Task RevokeAll([FromBody] LogoutAllRequest body, CancellationToken ct) { var n = await _logoutAll.ExecuteAsync(body, ct); return Ok(new { revoked = n }); } }