Files
vtuber-awards/Backend/Endpoints/AuthDemoLoginEndpoints.cs
T

183 lines
7.3 KiB
C#

using Backend.Common;
using Backend.Contracts;
using Backend.Data;
using Backend.Domain;
using Backend.Security;
using Backend.Services;
using Microsoft.EntityFrameworkCore;
namespace Backend.Endpoints;
public static partial class AuthEndpoints
{
private static async Task<IResult> DemoLogin(
HttpContext context,
AwardsDbContext db,
IConfiguration configuration,
DemoLoginRequest request,
IUserSessionService userSessionService,
IRiskFlagService riskFlagService,
IRiskRuleService riskRuleService)
{
var login = request.Login?.Trim() ?? request.Email?.Trim() ?? string.Empty;
var password = request.Password ?? string.Empty;
var settings = await db.SiteSettings.AsNoTracking().FirstOrDefaultAsync(item => item.Id == 1, context.RequestAborted);
var databaseDemoConfigured = settings is not null
&& (settings.DemoLoginManagedByDatabase || HasDatabaseDemoCredentials(settings));
string twitchUserId;
string displayName;
bool credentialsMatch;
if (databaseDemoConfigured && settings is not null)
{
if (!settings.DemoLoginEnabled)
{
return Results.NotFound();
}
if (!HasDatabaseDemoCredentials(settings)
|| string.IsNullOrWhiteSpace(settings.DemoLoginTwitchUserId)
|| string.IsNullOrWhiteSpace(settings.DemoLoginDisplayName))
{
return Results.Json(
new { message = "Demo login is not fully configured." },
statusCode: StatusCodes.Status503ServiceUnavailable);
}
credentialsMatch = LoginMatchesIdentifier(
login,
settings.DemoLoginEmail,
settings.DemoLoginTwitchUserId,
settings.DemoLoginDisplayName)
&& DemoCredentialHasher.VerifyPassword(password, settings.DemoLoginPasswordHash, settings.DemoLoginPasswordSalt);
twitchUserId = settings.DemoLoginTwitchUserId.Trim();
displayName = settings.DemoLoginDisplayName.Trim();
}
else
{
if (!IsDemoLoginEnabled(configuration))
{
return Results.NotFound();
}
var configuredLogin = ReadDemoLoginIdentifier(configuration);
var configuredEmail = ReadDemoSetting(configuration, "Email", "VTSA_DEMO_ADMIN_EMAIL");
var configuredPassword = ReadDemoSetting(configuration, "Password", "VTSA_DEMO_ADMIN_PASSWORD");
twitchUserId = ReadDemoSetting(configuration, "TwitchUserId", "VTSA_DEMO_ADMIN_TWITCH_ID");
displayName = ReadDemoSetting(configuration, "DisplayName", "VTSA_DEMO_ADMIN_DISPLAY_NAME");
if (string.IsNullOrWhiteSpace(configuredLogin)
|| string.IsNullOrWhiteSpace(configuredPassword)
|| string.IsNullOrWhiteSpace(twitchUserId)
|| string.IsNullOrWhiteSpace(displayName))
{
return Results.Json(
new { message = "Demo login is not fully configured." },
statusCode: StatusCodes.Status503ServiceUnavailable);
}
credentialsMatch = LoginMatchesIdentifier(
login,
configuredLogin,
configuredEmail,
twitchUserId,
displayName)
&& DemoCredentialHasher.FixedTimePlainTextEquals(password, configuredPassword);
twitchUserId = twitchUserId.Trim();
displayName = displayName.Trim();
}
if (!credentialsMatch)
{
return Results.Unauthorized();
}
var requestMetadata = RequestMetadataReader.Read(context);
var session = await userSessionService.CreateSessionAsync(
twitchUserId,
displayName,
AdminRoles.Owner,
requestMetadata,
context.RequestAborted);
var rapidDemoLoginRule = await riskRuleService.GetRuleAsync("rapid_demo_login_ip", context.RequestAborted);
var recentSessionsFromIp = await userSessionService.CountRecentSessionsFromIpAsync(
requestMetadata.ClientIp,
DateTimeOffset.UtcNow.AddMinutes(-rapidDemoLoginRule.WindowMinutes),
context.RequestAborted);
if (rapidDemoLoginRule.Enabled && recentSessionsFromIp >= rapidDemoLoginRule.Threshold)
{
await riskFlagService.AddIfMissingAsync(
null,
session.TwitchUserId,
"login",
"rapid_demo_login_ip",
rapidDemoLoginRule.Severity,
"Mehrere Demo-Admin-Sessions wurden in kurzer Zeit von derselben IP erzeugt.",
requestMetadata,
new
{
recentSessionsFromIp,
threshold = rapidDemoLoginRule.Threshold,
windowMinutes = rapidDemoLoginRule.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(ToAuthSessionDto(session));
}
private static bool IsDemoLoginEnabled(IConfiguration configuration)
{
var rawValue = configuration["VTSA_DEMO_LOGIN_ENABLED"]
?? configuration["DemoAdmin:Enabled"];
return bool.TryParse(rawValue, out var enabled) && enabled;
}
private static string ReadDemoSetting(IConfiguration configuration, string key, string environmentKey) =>
configuration[environmentKey] ?? configuration[$"DemoAdmin:{key}"] ?? string.Empty;
private static string ReadDemoLoginIdentifier(IConfiguration configuration)
{
var configuredLogin = ReadDemoSetting(configuration, "Login", "VTSA_DEMO_ADMIN_LOGIN");
return string.IsNullOrWhiteSpace(configuredLogin)
? ReadDemoSetting(configuration, "Email", "VTSA_DEMO_ADMIN_EMAIL")
: configuredLogin;
}
private static bool HasDatabaseDemoCredentials(SiteSettings settings) =>
!string.IsNullOrWhiteSpace(settings.DemoLoginEmail)
&& !string.IsNullOrWhiteSpace(settings.DemoLoginPasswordHash)
&& !string.IsNullOrWhiteSpace(settings.DemoLoginPasswordSalt);
private static bool LoginMatchesIdentifier(string login, params string?[] validIdentifiers)
{
var normalizedLogin = NormalizeLoginIdentifier(login);
if (string.IsNullOrWhiteSpace(normalizedLogin))
{
return false;
}
return validIdentifiers
.Select(NormalizeLoginIdentifier)
.Any(identifier => string.Equals(normalizedLogin, identifier, StringComparison.OrdinalIgnoreCase));
}
private static string NormalizeLoginIdentifier(string? value) =>
(value ?? string.Empty).Trim().TrimStart('@');
}