/
afanasevn
/
RedisLab
Обзор
Документация
Войти
/
afanasevn
/
RedisLab
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/RedisLab.Infrastructure/Auth/AuthService.cs
97 строк
3 KB
IBS\NAfanasev
Update solution commit
24 июн 2026, 19:46
24 июн 2026, 19:46
40761b1
Код
Авторство
О чём код?
using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using RedisLab.Application.Abstractions; using RedisLab.Application.Contracts.Auth; using RedisLab.Infrastructure.Persistence; namespace RedisLab.Infrastructure.Auth; /// <summary> /// Аутентификация через PostgreSQL, JWT и rate limit неудачных входов. /// </summary> public sealed class AuthService( AppDbContext db, ILoginRateLimiter loginRateLimiter, IOptions<JwtSettings> jwtOptions) : IAuthService { private readonly JwtSettings _jwt = jwtOptions.Value; /// <inheritdoc /> public async Task<LoginAttemptResult> LoginAsync( LoginRequest request, string clientIp, CancellationToken ct) { if (await loginRateLimiter.IsBlockedAsync(clientIp, ct)) { return new LoginAttemptResult(null, LoginFailureReason.RateLimited); } var normalizedEmail = request.Email.Trim().ToLowerInvariant(); var user = await db.Users .AsNoTracking() .FirstOrDefaultAsync(x => x.Email == normalizedEmail, ct); if (user is null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) { await loginRateLimiter.RecordFailedAttemptAsync(clientIp, ct); return new LoginAttemptResult(null, LoginFailureReason.InvalidCredentials); } var token = CreateToken(user.Id, user.Email, user.DisplayName, user.Role.ToString()); var profile = MapProfile(user); return new LoginAttemptResult(new LoginResponse(token, profile), null); } /// <inheritdoc /> public async Task<UserProfileDto?> GetProfileAsync( Guid userId, CancellationToken ct) { var user = await db.Users .AsNoTracking() .FirstOrDefaultAsync(x => x.Id == userId, ct); return user is null ? null : MapProfile(user); } /// <summary> /// Формирует JWT с claim роли для авторизации API и SignalR. /// </summary> private string CreateToken( Guid userId, string email, string displayName, string role) { var claims = new List<Claim> { new(ClaimTypes.NameIdentifier, userId.ToString()), new(JwtRegisteredClaimNames.Sub, userId.ToString()), new(JwtRegisteredClaimNames.Email, email), new(ClaimTypes.Name, displayName), new(ClaimTypes.Role, role), }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.Key)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _jwt.Issuer, audience: _jwt.Audience, claims: claims, expires: DateTime.UtcNow.AddHours(_jwt.ExpirationHours), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token); } private static UserProfileDto MapProfile(Domain.Entities.User user) => new(user.Id, user.Email, user.DisplayName, user.Role.ToString()); }