using Backend.Common; using Backend.Contracts; using Backend.Data; using Backend.Security; using Backend.Services; namespace Backend.Endpoints; public static partial class AuthEndpoints { private const int MaxTwitchUserIdLength = 64; private const int MaxDisplayNameLength = 80; private static async Task DevLogin( HttpContext context, IHostEnvironment environment, LoginRequest request, AwardsDbContext db, IUserSessionService userSessionService, IRiskFlagService riskFlagService, IRiskRuleService riskRuleService) { if (!environment.IsDevelopment()) { return Results.NotFound(); } var normalizedTwitchUserId = request.TwitchUserId?.Trim() ?? string.Empty; var normalizedDisplayName = request.DisplayName?.Trim() ?? string.Empty; var normalizedRole = AdminRoles.Normalize(request.Role); if (string.IsNullOrWhiteSpace(normalizedTwitchUserId) || normalizedTwitchUserId.Length > MaxTwitchUserIdLength) { return Results.BadRequest(new { message = $"Twitch user id is required and must stay below {MaxTwitchUserIdLength} characters." }); } if (!normalizedTwitchUserId.All(value => char.IsLetterOrDigit(value) || value is '_' or '-')) { return Results.BadRequest(new { message = "Twitch user id contains unsupported characters." }); } if (string.IsNullOrWhiteSpace(normalizedDisplayName) || normalizedDisplayName.Length > MaxDisplayNameLength) { return Results.BadRequest(new { message = $"Display name is required and must stay below {MaxDisplayNameLength} characters." }); } if (!AdminRoles.IsKnownRole(request.Role)) { return Results.BadRequest(new { message = "Role must be viewer, content_admin, admin or owner." }); } var requestMetadata = RequestMetadataReader.Read(context); var session = await userSessionService.CreateDevSessionAsync( request with { TwitchUserId = normalizedTwitchUserId, DisplayName = normalizedDisplayName, Role = normalizedRole, }, requestMetadata, context.RequestAborted); var rapidLoginRule = await riskRuleService.GetRuleAsync("rapid_login_ip", context.RequestAborted); var recentSessionsFromIp = await userSessionService.CountRecentSessionsFromIpAsync( requestMetadata.ClientIp, DateTimeOffset.UtcNow.AddMinutes(-rapidLoginRule.WindowMinutes), context.RequestAborted); if (rapidLoginRule.Enabled && recentSessionsFromIp >= rapidLoginRule.Threshold) { await riskFlagService.AddIfMissingAsync( null, session.TwitchUserId, "login", "rapid_login_ip", rapidLoginRule.Severity, "Mehrere neue Sessions wurden in kurzer Zeit von derselben IP erzeugt.", requestMetadata, new { recentSessionsFromIp, threshold = rapidLoginRule.Threshold, windowMinutes = rapidLoginRule.WindowMinutes, entityLinks = new[] { new { label = "Audit-Log öffnen", entityType = "session", entityId = session.TwitchUserId, to = $"/admin/users-logs?query={Uri.EscapeDataString(session.TwitchUserId)}", }, }, }, context.RequestAborted); await db.SaveChangesAsync(context.RequestAborted); } return Results.Ok(await ToAuthSessionDtoAsync(db, session, false, context.RequestAborted)); } }