Files
vtuber-awards/Backend/Endpoints/AuthTeamLoginEndpoints.cs
T
2026-06-26 18:09:29 +02:00

129 lines
5.0 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 const string TeamSessionPrefix = "team:";
private const int MinTeamPasswordLength = 10;
private static async Task<IResult> TeamLogin(
HttpContext context,
TeamLoginRequest request,
AwardsDbContext db,
IUserSessionService userSessionService)
{
var login = NormalizeTeamLogin(request.Login);
var password = request.Password ?? string.Empty;
if (string.IsNullOrWhiteSpace(login) || string.IsNullOrWhiteSpace(password))
{
return Results.Unauthorized();
}
var member = await db.TeamMembers.FirstOrDefaultAsync(item => item.Login == login, context.RequestAborted);
if (member is null
|| !member.IsActive
|| !DemoCredentialHasher.VerifyPassword(password, member.PasswordHash, member.PasswordSalt))
{
return Results.Unauthorized();
}
member.LastLoginAt = DateTimeOffset.UtcNow;
var session = await userSessionService.CreateSessionAsync(
BuildTeamSessionId(member.Login),
member.DisplayName,
member.Role,
RequestMetadataReader.Read(context),
context.RequestAborted);
await db.SaveChangesAsync(context.RequestAborted);
return Results.Ok(await ToAuthSessionDtoAsync(db, session, member.MustChangePassword, context.RequestAborted));
}
private static async Task<IResult> ChangePassword(
HttpContext context,
ChangePasswordRequest request,
AwardsDbContext db,
IUserSessionService userSessionService)
{
var session = await userSessionService.ResolveSessionAsync(context, context.RequestAborted);
if (session is null)
{
return Results.Unauthorized();
}
var member = await FindTeamMemberForSessionAsync(db, session, context.RequestAborted);
if (member is null || !member.IsActive)
{
return Results.BadRequest(new { message = "Dieser Account nutzt keinen aktiven Team-Login." });
}
var currentPassword = request.CurrentPassword ?? string.Empty;
var newPassword = request.NewPassword?.Trim() ?? string.Empty;
if (!DemoCredentialHasher.VerifyPassword(currentPassword, member.PasswordHash, member.PasswordSalt))
{
return Results.BadRequest(new { message = "Das aktuelle Passwort stimmt nicht." });
}
if (newPassword.Length < MinTeamPasswordLength)
{
return Results.BadRequest(new { message = $"Das neue Passwort muss mindestens {MinTeamPasswordLength} Zeichen lang sein." });
}
if (DemoCredentialHasher.FixedTimePlainTextEquals(currentPassword, newPassword))
{
return Results.BadRequest(new { message = "Das neue Passwort muss sich vom aktuellen Passwort unterscheiden." });
}
var credentials = DemoCredentialHasher.HashPassword(newPassword);
member.PasswordHash = credentials.Hash;
member.PasswordSalt = credentials.Salt;
member.MustChangePassword = false;
member.UpdatedAt = DateTimeOffset.UtcNow;
member.UpdatedByTwitchId = session.TwitchUserId;
session.DisplayName = member.DisplayName;
session.Role = AdminRoles.Normalize(member.Role);
await db.SaveChangesAsync(context.RequestAborted);
return Results.Ok(await ToAuthSessionDtoAsync(db, session, false, context.RequestAborted));
}
private static string BuildTeamSessionId(string login) =>
$"{TeamSessionPrefix}{login}";
internal static string ReadTeamLoginFromSession(string twitchUserId) =>
twitchUserId.StartsWith(TeamSessionPrefix, StringComparison.OrdinalIgnoreCase)
? NormalizeTeamLogin(twitchUserId[TeamSessionPrefix.Length..])
: string.Empty;
internal static async Task<TeamMember?> FindTeamMemberForSessionAsync(
AwardsDbContext db,
UserSession session,
CancellationToken cancellationToken = default)
{
var login = ReadTeamLoginFromSession(session.TwitchUserId);
if (!string.IsNullOrWhiteSpace(login))
{
return await db.TeamMembers.FirstOrDefaultAsync(item => item.Login == login, cancellationToken);
}
var twitchUserId = NormalizeTwitchUserId(session.TwitchUserId);
return string.IsNullOrWhiteSpace(twitchUserId)
? null
: await db.TeamMembers.FirstOrDefaultAsync(item => item.BoundTwitchUserId == twitchUserId, cancellationToken);
}
private static string NormalizeTeamLogin(string value) =>
value.Trim().TrimStart('@').ToLowerInvariant();
private static string NormalizeTwitchUserId(string? value) =>
(value ?? string.Empty).Trim().TrimStart('@').ToLowerInvariant();
}