183 lines
7.3 KiB
C#
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('@');
|
|
}
|