Updeted Session Model and Session Opener Service

renamed RefreshToken in model to RefreshTokenHash - now we have a hash of token, not a token
This commit is contained in:
Artemy
2025-12-14 14:03:18 +07:00
parent 0ad6b52de5
commit cc2921d257
18 changed files with 1153 additions and 191 deletions
@@ -7,86 +7,97 @@ using Govor.Data.Repositories.Exceptions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Govor.Application.Services.UserSessions
namespace Govor.Application.Services.UserSessions;
public class UserSessionOpener : IUserSessionOpener
{
public class UserSessionOpener : IUserSessionOpener
private readonly IUserSessionsRepository _repository;
private readonly ILogger<UserSessionOpener> _logger;
private readonly IJwtTokenHasher _jwtTokenHasher;
private readonly JwtRefreshOption _options;
private readonly IJwtService _jwtService;
public UserSessionOpener(
IUserSessionsRepository repository,
IJwtService jwtService,
IJwtTokenHasher jwtTokenHasher,
IOptions<JwtRefreshOption> options,
ILogger<UserSessionOpener> logger)
{
private readonly IUserSessionsRepository _repository;
private readonly ILogger<UserSessionOpener> _logger;
private readonly JwtRefreshOption _options;
private readonly IJwtService _jwtService;
public UserSessionOpener(
IUserSessionsRepository repository,
IJwtService jwtService,
IOptions<JwtRefreshOption> options,
ILogger<UserSessionOpener> logger)
{
_jwtService = jwtService;
_repository = repository;
_logger = logger;
_options = options.Value;
}
public async Task<RefreshResult> OpenSessionAsync(User user, string deviceInfo)
{
_logger.LogInformation($"Opening session for user {user.Id} on device '{deviceInfo}'");
try
{
var sessions = await _repository.GetByUserIdAsync(user.Id);
var session = sessions.FirstOrDefault(s => s.DeviceInfo == deviceInfo);
if (session is not null)
{
var newExpiresAt = DateTime.UtcNow.AddDays(_options.RefreshTokenLifetimeDays);
var accessToken = await _jwtService.GenerateAccessTokenAsync(user, session.Id);
var newRefreshToken = await _jwtService.GenerateRefreshTokenAsync(user);
session.RefreshToken = newRefreshToken;
session.ExpiresAt = newExpiresAt;
session.CreatedAt = DateTime.UtcNow;
session.IsRevoked = false;
await _repository.UpdateAsync(session);
_logger.LogInformation($"Updated session for user {user.Id} on device '{deviceInfo}'");
return new RefreshResult(session.RefreshToken, accessToken);
}
return await OpenNewSession();
}
catch (NotFoundByKeyException<Guid> ex)
{
return await OpenNewSession();
}
async Task<RefreshResult> OpenNewSession()
{
var sessionId = Guid.NewGuid();
var accessToken = await _jwtService.GenerateAccessTokenAsync(user, sessionId);
var refreshToken = await _jwtService.GenerateRefreshTokenAsync(user);
var newSession = new UserSession
{
Id = sessionId,
UserId = user.Id,
DeviceInfo = deviceInfo,
RefreshToken = refreshToken,
CreatedAt = DateTime.UtcNow,
ExpiresAt = DateTime.UtcNow.AddDays(_options.RefreshTokenLifetimeDays),
IsRevoked = false
};
await _repository.AddAsync(newSession);
_logger.LogInformation($"Created new session {sessionId} for user {user.Id} on device '{deviceInfo}'");
return new RefreshResult(refreshToken, accessToken);
}
}
_jwtService = jwtService;
_repository = repository;
_jwtTokenHasher = jwtTokenHasher;
_logger = logger;
_options = options.Value;
}
}
public async Task<RefreshResult> OpenSessionAsync(User user, string deviceInfo)
{
_logger.LogInformation($"Opening session for user {user.Id} on device '{deviceInfo}'");
try
{
var sessions = await _repository.GetByUserIdAsync(user.Id);
var existingSession = sessions.FirstOrDefault(s => s.DeviceInfo == deviceInfo);
if (existingSession is not null)
return await UpdateExistingSessionAsync(user, deviceInfo, existingSession);
}
catch (NotFoundByKeyException<Guid> ex)
{
}
return await CreateNewSessionAsync(user, deviceInfo);
}
private async Task<RefreshResult> UpdateExistingSessionAsync(User user, string deviceInfo, UserSession session)
{
var (accessToken, refreshToken) = await GenerateTokensAsync(user, session.Id);
var newTokenHash = _jwtTokenHasher.HashToken(refreshToken);
var newExpiresAt = DateTime.UtcNow.AddDays(_options.RefreshTokenLifetimeDays);
session.RefreshTokenHash = newTokenHash;
session.ExpiresAt = newExpiresAt;
session.CreatedAt = DateTime.UtcNow;
session.IsRevoked = false;
await _repository.UpdateAsync(session);
_logger.LogInformation($"Updated session for user {user.Id} on device '{deviceInfo}'");
return new RefreshResult(refreshToken, accessToken);
}
private async Task<RefreshResult> CreateNewSessionAsync(User user, string deviceInfo)
{
var sessionId = Guid.NewGuid();
var (accessToken, refreshToken) = await GenerateTokensAsync(user, sessionId);
var refreshTokenHash = _jwtTokenHasher.HashToken(refreshToken);
var newSession = new UserSession
{
Id = sessionId,
UserId = user.Id,
DeviceInfo = deviceInfo,
RefreshTokenHash = refreshTokenHash,
CreatedAt = DateTime.UtcNow,
ExpiresAt = DateTime.UtcNow.AddDays(_options.RefreshTokenLifetimeDays),
IsRevoked = false
};
await _repository.AddAsync(newSession);
_logger.LogInformation($"Created new session {sessionId} for user {user.Id} on device '{deviceInfo}'");
return new RefreshResult(refreshToken, accessToken);
}
private async Task<(string accessToken, string refreshToken)> GenerateTokensAsync(User user, Guid sessionId)
{
var accessToken = await _jwtService.GenerateAccessTokenAsync(user, sessionId);
var refreshToken = await _jwtService.GenerateRefreshTokenAsync(user);
return (accessToken, refreshToken);
}
}
@@ -36,7 +36,7 @@ public class UserSessionRefresher : IUserSessionRefresher
{
try
{
var session = await _sessionsRepository.GetByRefreshTokenAsync(refreshToken);
var session = await _sessionsRepository.GetByHashedRefreshTokenAsync(refreshToken);
if (session.IsRevoked || session.ExpiresAt <= DateTime.UtcNow)
throw new UnauthorizedAccessException("Refresh token is invalid or expired");
@@ -55,7 +55,7 @@ public class UserSessionRefresher : IUserSessionRefresher
var newSession = new UserSession
{
UserId = user.Id,
RefreshToken = newRefreshToken,
RefreshTokenHash = newRefreshToken,
DeviceInfo = session.DeviceInfo,
CreatedAt = DateTime.UtcNow,
ExpiresAt = DateTime.UtcNow.AddDays(_options.RefreshTokenLifetimeDays)