Refactor app architecture and clean local artifacts
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
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('@');
|
||||
}
|
||||
Reference in New Issue
Block a user