using System.Data; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using AMREZ.EOP.Abstractions.Applications.Tenancy; using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications; using AMREZ.EOP.Abstractions.Infrastructures.Common; using AMREZ.EOP.Abstractions.Infrastructures.Repositories; using AMREZ.EOP.Abstractions.Security; using AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair; using AMREZ.EOP.Domain.Entities.Authentications; using Microsoft.AspNetCore.Http; namespace AMREZ.EOP.Application.UseCases.Authentications; public sealed class IssueTokenPairUseCase : IIssueTokenPairUseCase { private readonly IUserRepository _users; private readonly IJwtFactory _jwt; private readonly IHttpContextAccessor _http; private const int RefreshDays = 14; public IssueTokenPairUseCase( IUserRepository users, IJwtFactory jwt, IHttpContextAccessor http) { _users = users; _jwt = jwt; _http = http; } public async Task ExecuteAsync(IssueTokenPairRequest request, CancellationToken ct = default) { var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); var tenantId = request.TenantId; // ---- สร้าง/บันทึก refresh session ---- var refreshRaw = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); var refreshHash = Sha256(refreshRaw); var now = DateTimeOffset.UtcNow; var session = await _users.CreateSessionAsync(new UserSession { Id = Guid.NewGuid(), TenantId = tenantId, UserId = request.UserId, RefreshTokenHash = refreshHash, IssuedAt = now, ExpiresAt = now.AddDays(RefreshDays), DeviceId = http.Request.Headers["X-Device-Id"].FirstOrDefault(), UserAgent = http.Request.Headers["User-Agent"].FirstOrDefault(), IpAddress = http.Connection.RemoteIpAddress?.ToString() }, ct); // ---- เวอร์ชัน/สแตมป์ความปลอดภัย ---- var tv = await _users.GetTenantTokenVersionAsync(tenantId, ct); var sstamp = await _users.GetUserSecurityStampAsync(request.UserId, ct) ?? string.Empty; // ---- เตรียม claims พื้นฐาน ---- var claims = new List { new("sub", request.UserId.ToString()), new("email", request.Email), new("tenant", request.Tenant), // tenantKey ที่ FE ใช้ new("tenant_id", tenantId.ToString()), new("sid", session.Id.ToString()), new("jti", Guid.NewGuid().ToString("N")), new("tv", tv), new("sstamp", sstamp), new("iat", now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) }; string[] roles = request.Roles ?? Array.Empty(); if (roles.Length == 0) { roles = await _users.GetRoleCodesByUserIdAsync(request.UserId, tenantId, ct); } foreach (var r in roles.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct(StringComparer.OrdinalIgnoreCase)) { claims.Add(new Claim("role", r)); claims.Add(new Claim(ClaimTypes.Role, r)); } // ---- ออก access token ---- var (access, accessExp) = _jwt.CreateAccessToken(claims); return new IssueTokenPairResponse { AccessToken = access, AccessExpiresAt = accessExp, RefreshToken = refreshRaw, RefreshExpiresAt = session.ExpiresAt }; } private static string Sha256(string raw) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw)); return Convert.ToHexString(bytes); } }