111 lines
4.0 KiB
C#
111 lines
4.0 KiB
C#
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 ITenantResolver _tenantResolver; // ctx/key for UoW
|
|
private readonly ITenantRepository _tenants; // get TenantId (Guid)
|
|
private readonly IUserRepository _users;
|
|
private readonly IJwtFactory _jwt;
|
|
private readonly IHttpContextAccessor _http;
|
|
private readonly IUnitOfWork _uow;
|
|
|
|
private const int RefreshDays = 14;
|
|
|
|
public IssueTokenPairUseCase(
|
|
ITenantResolver resolver,
|
|
ITenantRepository tenants,
|
|
IUserRepository users,
|
|
IJwtFactory jwt,
|
|
IHttpContextAccessor http,
|
|
IUnitOfWork uow)
|
|
{
|
|
_tenantResolver = resolver;
|
|
_tenants = tenants;
|
|
_users = users;
|
|
_jwt = jwt;
|
|
_http = http;
|
|
_uow = uow;
|
|
}
|
|
|
|
public async Task<IssueTokenPairResponse> ExecuteAsync(IssueTokenPairRequest request, CancellationToken ct = default)
|
|
{
|
|
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
|
|
|
var tenantCtx = _tenantResolver.Resolve(http, request);
|
|
if (tenantCtx is null) throw new InvalidOperationException("Cannot resolve tenant context");
|
|
|
|
await _uow.BeginAsync(tenantCtx, IsolationLevel.ReadCommitted, ct);
|
|
|
|
try
|
|
{
|
|
var tn = await _tenants.GetAsync(tenantCtx.Id, ct);
|
|
var tenantId = tn.TenantId;
|
|
|
|
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);
|
|
|
|
// 6) tv / sstamp
|
|
var tv = await _users.GetTenantTokenVersionAsync(tenantId, ct);
|
|
var sstamp = await _users.GetUserSecurityStampAsync(request.UserId, ct) ?? string.Empty;
|
|
|
|
var claims = new List<Claim>
|
|
{
|
|
new("sub", request.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);
|
|
|
|
await _uow.CommitAsync(ct);
|
|
|
|
return new IssueTokenPairResponse
|
|
{
|
|
AccessToken = access,
|
|
AccessExpiresAt = accessExp,
|
|
RefreshToken = refreshRaw, // raw ออกให้ client
|
|
RefreshExpiresAt = session.ExpiresAt
|
|
};
|
|
}
|
|
catch
|
|
{
|
|
await _uow.RollbackAsync(ct);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static string Sha256(string raw)
|
|
{
|
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw));
|
|
return Convert.ToHexString(bytes);
|
|
}
|
|
} |