using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using line_gestao_api.Dtos; using line_gestao_api.Models; using line_gestao_api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; namespace line_gestao_api.Controllers; [ApiController] [Route("auth")] public class AuthController : ControllerBase { private readonly UserManager _userManager; private readonly ITenantProvider _tenantProvider; private readonly IConfiguration _config; public AuthController( UserManager userManager, ITenantProvider tenantProvider, IConfiguration config) { _userManager = userManager; _tenantProvider = tenantProvider; _config = config; } [HttpPost("register")] [Authorize(Roles = "admin")] public async Task Register(RegisterRequest req) { if (req.Password != req.ConfirmPassword) return BadRequest("As senhas não conferem."); var tenantId = _tenantProvider.TenantId; if (tenantId == null) return Unauthorized("Tenant inválido."); var email = req.Email.Trim().ToLowerInvariant(); var normalizedEmail = _userManager.NormalizeEmail(email); var exists = await _userManager.Users .AnyAsync(u => u.NormalizedEmail == normalizedEmail && u.TenantId == tenantId); if (exists) return BadRequest("E-mail já cadastrado."); var user = new ApplicationUser { Name = req.Name.Trim(), Email = email, UserName = email, TenantId = tenantId.Value, IsActive = true, EmailConfirmed = true }; var createResult = await _userManager.CreateAsync(user, req.Password); if (!createResult.Succeeded) return BadRequest(createResult.Errors.Select(e => e.Description).ToList()); await _userManager.AddToRoleAsync(user, "leitura"); var token = await GenerateJwtAsync(user); return Ok(new AuthResponse(token)); } [HttpPost("login")] public async Task Login(LoginRequest req) { var email = (req.Email ?? "").Trim().ToLowerInvariant(); var password = req.Password ?? ""; var normalizedEmail = _userManager.NormalizeEmail(email); var tenantId = ResolveTenantId(req); ApplicationUser? user; if (tenantId == null) { var users = await _userManager.Users .IgnoreQueryFilters() .Where(u => u.NormalizedEmail == normalizedEmail) .ToListAsync(); if (users.Count == 0) return Unauthorized("Credenciais inválidas."); if (users.Count > 1) return BadRequest("Informe o tenant para realizar o login."); user = users[0]; } else { user = await _userManager.Users .IgnoreQueryFilters() .FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail && u.TenantId == tenantId); } if (user == null) return Unauthorized("Credenciais inválidas."); if (!user.IsActive) return Unauthorized("Usuário desativado."); var valid = await _userManager.CheckPasswordAsync(user, password); if (!valid) return Unauthorized("Credenciais inválidas."); var token = await GenerateJwtAsync(user); return Ok(new AuthResponse(token)); } private Guid? ResolveTenantId(LoginRequest req) { if (req.TenantId.HasValue) return req.TenantId.Value; var headerValue = Request.Headers["X-Tenant-Id"].FirstOrDefault(); return Guid.TryParse(headerValue, out var parsed) ? parsed : null; } private async Task GenerateJwtAsync(ApplicationUser user) { var key = _config["Jwt:Key"]!; var issuer = _config["Jwt:Issuer"]!; var audience = _config["Jwt:Audience"]!; var expiresMinutes = int.Parse(_config["Jwt:ExpiresMinutes"]!); var roles = await _userManager.GetRolesAsync(user); var claims = new List { new(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new(JwtRegisteredClaimNames.Email, user.Email ?? string.Empty), new("name", user.Name), new("tenantId", user.TenantId.ToString()) }; claims.AddRange(roles.Select(r => new Claim(ClaimTypes.Role, r))); var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); var creds = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(expiresMinutes), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } }