forked from xiongyuxing/tiku-backend.net
91 lines
2.8 KiB
C#
91 lines
2.8 KiB
C#
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<JwtOptions> options) : ISessionService
|
|
{
|
|
private readonly JwtOptions options = options.Value;
|
|
|
|
public string GenerateRefreshToken(Guid tenantId, Guid sessionId)
|
|
{
|
|
return $"v1.{tenantId:N}.{sessionId:N}.{Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(64))}";
|
|
}
|
|
|
|
public bool TryParseRefreshToken(string refreshToken, out RefreshTokenLocator locator)
|
|
{
|
|
locator = default;
|
|
if (string.IsNullOrWhiteSpace(refreshToken))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var parts = refreshToken.Split('.', 4, StringSplitOptions.None);
|
|
if (parts.Length != 4 || parts[0] != "v1" || parts[3].Length < 32 ||
|
|
!Guid.TryParseExact(parts[1], "N", out var tenantId) ||
|
|
!Guid.TryParseExact(parts[2], "N", out var sessionId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
locator = new RefreshTokenLocator(tenantId, sessionId);
|
|
return true;
|
|
}
|
|
|
|
public string HashRefreshToken(string refreshToken)
|
|
{
|
|
var hash = SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(refreshToken));
|
|
return Convert.ToHexString(hash).ToLowerInvariant();
|
|
}
|
|
|
|
public async Task<AuthTokenPair> IssueAsync(
|
|
Guid userId,
|
|
string? phone,
|
|
string? email,
|
|
TenantMembership membership,
|
|
string provider,
|
|
string? ipAddress,
|
|
string? userAgent,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var session = new AuthSession
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = membership.TenantId,
|
|
UserId = userId,
|
|
TokenHash = string.Empty,
|
|
Provider = provider,
|
|
ExpiresAt = DateTimeOffset.UtcNow.AddDays(options.RefreshTokenDays),
|
|
IpAddress = ipAddress,
|
|
UserAgent = userAgent
|
|
};
|
|
var refreshToken = GenerateRefreshToken(session.TenantId, session.Id);
|
|
session.TokenHash = HashRefreshToken(refreshToken);
|
|
|
|
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);
|
|
}
|
|
}
|