Refine admin workflows and team access
This commit is contained in:
@@ -13,6 +13,7 @@ namespace Backend.Endpoints;
|
||||
public static class AdminTeamEndpoints
|
||||
{
|
||||
private const int MinPasswordLength = 10;
|
||||
private static readonly TimeSpan OnlineActivityWindow = TimeSpan.FromMinutes(5);
|
||||
private const string PersonalOwnerLogin = "jayuhime";
|
||||
private static readonly string[] PersonalCreatorLogins = ["sleepy_bao"];
|
||||
|
||||
@@ -79,11 +80,21 @@ public static class AdminTeamEndpoints
|
||||
.OrderByDescending(item => item.Role == AdminRoles.Owner)
|
||||
.ThenBy(item => item.Role)
|
||||
.ThenBy(item => item.DisplayName)
|
||||
.Select(item => ToMemberDto(item))
|
||||
.ToArrayAsync();
|
||||
var sessionKeys = members
|
||||
.SelectMany(MemberSessionKeys)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var sessions = sessionKeys.Length == 0
|
||||
? []
|
||||
: await db.UserSessions
|
||||
.AsNoTracking()
|
||||
.Where(item => sessionKeys.Contains(item.TwitchUserId))
|
||||
.ToArrayAsync();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
return Results.Ok(new AdminTeamResponse(
|
||||
members,
|
||||
members.Select(member => ToMemberDto(member, sessions, now)),
|
||||
await BuildRoleDtosAsync(db),
|
||||
PermissionCatalog));
|
||||
}
|
||||
@@ -322,10 +333,15 @@ public static class AdminTeamEndpoints
|
||||
IAdminAuditService adminAuditService)
|
||||
{
|
||||
var session = AdminEndpointConventions.CurrentSession(context);
|
||||
if (!AdminRoles.IsPrivilegedFullControlRole(session.Role))
|
||||
{
|
||||
return Results.Json(new { message = "Rollenberechtigungen können nur von Owner oder Creator geändert werden." }, statusCode: StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
var allowedPermissions = AdminPermissionCatalog.AllPermissionKeys.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
var allowedRoles = DefaultRoles.Select(item => item.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
var existingRows = await db.TeamRolePermissions.ToDictionaryAsync(item => item.Role, StringComparer.OrdinalIgnoreCase, context.RequestAborted);
|
||||
var roleChanges = new List<object>();
|
||||
|
||||
foreach (var roleUpdate in request.Roles ?? [])
|
||||
{
|
||||
@@ -339,14 +355,18 @@ public static class AdminTeamEndpoints
|
||||
.Select(item => item.Trim())
|
||||
.Where(item => allowedPermissions.Contains(item))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(item => item)
|
||||
.ToArray();
|
||||
permissionKeys = AdminPermissionCatalog.NormalizePermissionKeys(role, permissionKeys);
|
||||
|
||||
if (AdminRoles.IsPrivilegedFullControlRole(role) && permissionKeys.Length != AdminPermissionCatalog.AllPermissionKeys.Length)
|
||||
{
|
||||
return Results.BadRequest(new { message = "Owner und Creator müssen alle Berechtigungen behalten." });
|
||||
}
|
||||
|
||||
var beforePermissions = existingRows.TryGetValue(role, out var existingRow)
|
||||
? AdminPermissionCatalog.NormalizePermissionKeys(role, ReadPermissionKeys(existingRow.PermissionsJson, AdminPermissionCatalog.DefaultPermissionKeys(role)))
|
||||
: AdminPermissionCatalog.NormalizePermissionKeys(role, AdminPermissionCatalog.DefaultPermissionKeys(role));
|
||||
|
||||
if (!existingRows.TryGetValue(role, out var row))
|
||||
{
|
||||
row = new TeamRolePermission { Role = role };
|
||||
@@ -356,6 +376,18 @@ public static class AdminTeamEndpoints
|
||||
row.PermissionsJson = JsonSerializer.Serialize(permissionKeys);
|
||||
row.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
row.UpdatedByTwitchId = session.TwitchUserId;
|
||||
|
||||
if (!beforePermissions.SequenceEqual(permissionKeys, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
roleChanges.Add(new
|
||||
{
|
||||
role,
|
||||
before = beforePermissions,
|
||||
after = permissionKeys,
|
||||
added = permissionKeys.Except(beforePermissions, StringComparer.OrdinalIgnoreCase).ToArray(),
|
||||
removed = beforePermissions.Except(permissionKeys, StringComparer.OrdinalIgnoreCase).ToArray(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
adminAuditService.AddEntry(
|
||||
@@ -364,7 +396,7 @@ public static class AdminTeamEndpoints
|
||||
"team-role",
|
||||
"matrix",
|
||||
"Team-Rollenberechtigungen wurden gespeichert.",
|
||||
new { roleCount = request.Roles?.Length ?? 0 },
|
||||
new { roleCount = request.Roles?.Length ?? 0, changes = roleChanges },
|
||||
RequestMetadataReader.Read(context));
|
||||
|
||||
await db.SaveChangesAsync(context.RequestAborted);
|
||||
@@ -384,18 +416,30 @@ public static class AdminTeamEndpoints
|
||||
? ReadPermissionKeys(json, role.PermissionKeys)
|
||||
: role.PermissionKeys;
|
||||
|
||||
if (AdminRoles.IsPrivilegedFullControlRole(role.Key))
|
||||
{
|
||||
permissions = AdminPermissionCatalog.AllPermissionKeys;
|
||||
}
|
||||
permissions = AdminPermissionCatalog.NormalizePermissionKeys(role.Key, permissions);
|
||||
|
||||
return role with { PermissionKeys = permissions };
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static AdminTeamMemberDto ToMemberDto(TeamMember member) =>
|
||||
new(
|
||||
private static AdminTeamMemberDto ToMemberDto(
|
||||
TeamMember member,
|
||||
IReadOnlyCollection<UserSession>? sessions = null,
|
||||
DateTimeOffset? now = null)
|
||||
{
|
||||
var memberSessions = sessions?
|
||||
.Where(session => MemberSessionKeys(member).Contains(session.TwitchUserId, StringComparer.OrdinalIgnoreCase))
|
||||
.ToArray() ?? [];
|
||||
var lastSeenAt = memberSessions
|
||||
.Select(session => (DateTimeOffset?)session.LastSeenAt)
|
||||
.Max() ?? member.LastLoginAt;
|
||||
var isOnline = member.IsActive
|
||||
&& memberSessions.Any(session =>
|
||||
session.IsActive
|
||||
&& session.LastSeenAt >= (now ?? DateTimeOffset.UtcNow).Subtract(OnlineActivityWindow));
|
||||
|
||||
return new(
|
||||
member.Id,
|
||||
member.Login,
|
||||
member.DisplayName,
|
||||
@@ -407,8 +451,21 @@ public static class AdminTeamEndpoints
|
||||
member.CreatedAt,
|
||||
member.UpdatedAt,
|
||||
member.LastLoginAt,
|
||||
lastSeenAt,
|
||||
isOnline,
|
||||
member.TwitchBoundAt,
|
||||
member.PasswordResetAt);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> MemberSessionKeys(TeamMember member)
|
||||
{
|
||||
yield return $"team:{member.Login}";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(member.BoundTwitchUserId))
|
||||
{
|
||||
yield return member.BoundTwitchUserId;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ReadPermissionKeys(string json, IEnumerable<string> fallback)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user