using System.Security.Cryptography; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Tiku.Application.Auth; using Tiku.Application.Security; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Auth; public sealed class SessionService( TikuDbContext dbContext, ITokenService tokenService, IOptions options) : ISessionService { private readonly JwtOptions options = options.Value; public string GenerateRefreshToken() { return Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(64)); } public string HashRefreshToken(string refreshToken) { var hash = SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(refreshToken)); return Convert.ToHexString(hash).ToLowerInvariant(); } public async Task IssueAsync( Guid userId, string? phone, string? email, TenantMembership membership, string provider, string? ipAddress, string? userAgent, CancellationToken cancellationToken = default) { var refreshToken = GenerateRefreshToken(); var session = new AuthSession { TenantId = membership.TenantId, UserId = userId, TokenHash = HashRefreshToken(refreshToken), Provider = provider, ExpiresAt = DateTimeOffset.UtcNow.AddDays(options.RefreshTokenDays), IpAddress = ipAddress, UserAgent = userAgent }; dbContext.AuthSessions.Add(session); await dbContext.SaveChangesAsync(cancellationToken); var accessToken = tokenService.CreateAccessToken( userId, session.Id, phone, email, membership); return new AuthTokenPair( accessToken.Token, refreshToken, accessToken.ExpiresAt, session.ExpiresAt); } }