/
afanasevn
/
RedisLab
Обзор
Документация
Войти
/
afanasevn
/
RedisLab
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/RedisLab.Api/Controllers/AuthController.cs
99 строк
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 Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using RedisLab.Application.Abstractions; using RedisLab.Application.Contracts.Auth; namespace RedisLab.Api.Controllers; /// <summary> /// Аутентификация: вход и профиль текущего пользователя. /// </summary> [ApiController] [Route("api/auth")] public sealed class AuthController(IAuthService authService) : ControllerBase { private const string RateLimitError = "Слишком много попыток входа. Повторите через минуту."; /// <summary> /// Вход по email и паролю; rate limit по IP через Redis INCR. /// </summary> [HttpPost("login")] [AllowAnonymous] [ProducesResponseType(typeof(LoginResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status429TooManyRequests)] public async Task<IActionResult> Login( [FromBody] LoginRequest request, CancellationToken ct) { var clientIp = ResolveClientIp(HttpContext); var result = await authService.LoginAsync(request, clientIp, ct); if (result.FailureReason == LoginFailureReason.RateLimited) { return StatusCode(StatusCodes.Status429TooManyRequests, new { error = RateLimitError }); } if (result.Response is null) { return Unauthorized(new { error = "Неверный email или пароль." }); } return Ok(result.Response); } /// <summary> /// Профиль пользователя по JWT из заголовка Authorization. /// </summary> [HttpGet("me")] [Authorize] [ProducesResponseType(typeof(UserProfileDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task<IActionResult> Me(CancellationToken ct) { var userId = ResolveUserId(User); if (userId is null) { return Unauthorized(); } var profile = await authService.GetProfileAsync(userId.Value, ct); return profile is null ? Unauthorized() : Ok(profile); } /// <summary> /// Извлекает идентификатор пользователя из JWT (sub или NameIdentifier после MapInboundClaims). /// </summary> private static Guid? ResolveUserId(ClaimsPrincipal user) { foreach (var claimType in new[] { ClaimTypes.NameIdentifier, JwtRegisteredClaimNames.Sub }) { var value = user.FindFirstValue(claimType); if (Guid.TryParse(value, out var userId)) { return userId; } } return null; } /// <summary> /// Определяет IP клиента для ключа ratelimit:login:{ip}. /// </summary> private static string ResolveClientIp(HttpContext context) { var forwarded = context.Request.Headers["X-Forwarded-For"].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(forwarded)) { return forwarded.Split(',')[0].Trim(); } return context.Connection.RemoteIpAddress?.ToString() ?? "unknown"; } }