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.Refresh; using Microsoft.AspNetCore.Http; namespace AMREZ.EOP.Application.UseCases.Authentications; public sealed class RefreshUseCase : IRefreshUseCase { private readonly ITenantResolver _resolver; private readonly ITenantRepository _tenants; private readonly IUserRepository _users; private readonly IJwtFactory _jwt; private readonly IHttpContextAccessor _http; private readonly IUnitOfWork _uow; private const int RefreshDays = 14; public RefreshUseCase( ITenantResolver resolver, ITenantRepository tenants, IUserRepository users, IJwtFactory jwt, IHttpContextAccessor http, IUnitOfWork uow) { _resolver = resolver; _tenants = tenants; _users = users; _jwt = jwt; _http = http; _uow = uow; } public async Task ExecuteAsync(RefreshRequest request, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(request.RefreshToken)) return null; var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext"); var tenantCtx = _resolver.Resolve(http, request); if (tenantCtx is null) return null; await _uow.BeginAsync(tenantCtx, IsolationLevel.ReadCommitted, ct); try { var tn = await _tenants.GetAsync(tenantCtx.TenantKey, ct); var tenantId = tn.TenantId; var hash = Sha256(request.RefreshToken); var session = await _users.FindSessionByRefreshHashAsync(tenantId, hash, ct); if (session is null || session.RevokedAt.HasValue || (session.ExpiresAt.HasValue && session.ExpiresAt.Value <= DateTimeOffset.UtcNow)) { await _uow.RollbackAsync(ct); return null; } if (!await _users.IsSessionActiveAsync(session.UserId, session.Id, ct)) { await _uow.RollbackAsync(ct); return null; } var tv = await _users.GetTenantTokenVersionAsync(tenantId, ct); var sstamp = await _users.GetUserSecurityStampAsync(session.UserId, ct) ?? string.Empty; var claims = new List { new("sub", session.UserId.ToString()), new("tenant_id", tenantId.ToString()), new("sid", session.Id.ToString()), new("jti", Guid.NewGuid().ToString("N")), new("tv", tv), new("sstamp", sstamp) }; var (access, accessExp) = _jwt.CreateAccessToken(claims); var newRaw = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); var newHash = Sha256(newRaw); var now = DateTimeOffset.UtcNow; var newExp = now.AddDays(RefreshDays); var ok = await _users.RotateSessionRefreshAsync(tenantId, session.Id, newHash, now, newExp, ct); if (!ok) { await _uow.RollbackAsync(ct); return null; } await _uow.CommitAsync(ct); return new RefreshResponse { AccessToken = access, AccessExpiresAt = accessExp, RefreshToken = newRaw, RefreshExpiresAt = newExp }; } catch { await _uow.RollbackAsync(ct); throw; } } private static string Sha256(string raw) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw)); return Convert.ToHexString(bytes); } }