feat(web): finalize Discord OAuth and platform-agnostic auth
PR Checks / test-and-build (pull_request) Successful in 5m47s
PR Checks / test-and-build (pull_request) Successful in 5m47s
- Bump version to 2.8.0 across all versioned files - Fix AuthorizedSessionServiceTests for platform-agnostic identity - Update Razor Pages to use *ForCurrentUserAsync APIs - Add backward-compatible constructors to WebGameGroup/WebGroupManager - Make DiscordOAuthOptions properties non-required for config binding Bump version → 2.8.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -185,7 +185,7 @@
|
||||
private Guid selectedGroupId;
|
||||
private Guid? deletingTemplateId;
|
||||
private bool isCreatingTemplate;
|
||||
private long telegramId;
|
||||
private string? externalUserId;
|
||||
private string? errorMessage;
|
||||
private string? successMessage;
|
||||
private CampaignTemplateEditModel templateModel = new();
|
||||
@@ -195,13 +195,13 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (!authState.User.TryGetTelegramId(out telegramId))
|
||||
if (!authState.User.TryGetPlatformIdentity(out var platform, out externalUserId))
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
}
|
||||
|
||||
groups = await SessionService.GetGroupsForGmAsync(telegramId);
|
||||
groups = await SessionService.GetGroupsForCurrentUserAsync();
|
||||
selectedGroupId = groups.FirstOrDefault()?.Id ?? Guid.Empty;
|
||||
|
||||
if (selectedGroupId != Guid.Empty)
|
||||
@@ -228,7 +228,7 @@
|
||||
campaignTemplates = null;
|
||||
campaignTemplateModels = [];
|
||||
|
||||
var templates = await SessionService.GetCampaignTemplatesForGmAsync(selectedGroupId, telegramId);
|
||||
var templates = await SessionService.GetCampaignTemplatesForCurrentUserAsync(selectedGroupId);
|
||||
if (templates is null)
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
@@ -260,9 +260,8 @@
|
||||
|
||||
try
|
||||
{
|
||||
await SessionService.CreateCampaignTemplateForGmAsync(
|
||||
await SessionService.CreateCampaignTemplateForCurrentUserAsync(
|
||||
selectedGroupId,
|
||||
telegramId,
|
||||
new CreateCampaignTemplateRequest(
|
||||
templateModel.Name,
|
||||
templateModel.Title,
|
||||
@@ -298,7 +297,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
await SessionService.DeleteCampaignTemplateForGmAsync(template.Id, telegramId);
|
||||
await SessionService.DeleteCampaignTemplateForCurrentUserAsync(template.Id);
|
||||
successMessage = "Шаблон кампании удалён.";
|
||||
await LoadTemplates();
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (!authState.User.TryGetTelegramId(out var telegramId))
|
||||
if (!authState.User.TryGetPlatformIdentity(out var platform, out var externalUserId))
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
}
|
||||
|
||||
session = await SessionService.GetSessionForGmAsync(SessionId, telegramId);
|
||||
session = await SessionService.GetSessionForCurrentUserAsync(SessionId);
|
||||
if (session is null)
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
@@ -114,7 +114,7 @@
|
||||
try
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (!authState.User.TryGetTelegramId(out var telegramId))
|
||||
if (!authState.User.TryGetPlatformIdentity(out var platform, out var externalUserId))
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
@@ -122,7 +122,7 @@
|
||||
|
||||
var utcTime = new DateTimeOffset(model.ScheduledAtLocal, TimeSpan.FromHours(3)).ToUniversalTime().UtcDateTime;
|
||||
|
||||
await SessionService.UpdateSessionForGmAsync(SessionId, telegramId, model.Title, utcTime, model.JoinLink, model.MaxPlayers);
|
||||
await SessionService.UpdateSessionForCurrentUserAsync(SessionId, model.Title, utcTime, model.JoinLink, model.MaxPlayers);
|
||||
Navigation.NavigateTo($"/group/{session!.GroupId}");
|
||||
}
|
||||
catch (SessionAccessDeniedException)
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
</span>
|
||||
@if (groupManagement.CurrentUserIsOwner && manager.Role == GroupManagerRoleExtensions.CoGmValue)
|
||||
{
|
||||
<button type="button" class="btn-gm btn-gm-outline" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;" disabled="@(removingCoGmId == manager.TelegramId)" @onclick="() => RemoveCoGm(manager.TelegramId)">
|
||||
@(removingCoGmId == manager.TelegramId ? "⏳ Удаляем..." : "Убрать")
|
||||
<button type="button" class="btn-gm btn-gm-outline" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;" disabled="@(removingCoGmId == manager.ExternalUserId)" @onclick="() => RemoveCoGm(manager.ExternalUserId ?? manager.TelegramId.ToString())">
|
||||
@(removingCoGmId == manager.ExternalUserId ? "⏳ Удаляем..." : "Убрать")
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@@ -403,9 +403,9 @@
|
||||
private Guid? promotingSessionId;
|
||||
private Guid? processingBatchId;
|
||||
private Guid? processingTemplateId;
|
||||
private long? removingCoGmId;
|
||||
private string? removingCoGmId;
|
||||
private bool isAddingCoGm;
|
||||
private long telegramId;
|
||||
private string? externalUserId;
|
||||
private string? errorMessage;
|
||||
private string? successMessage;
|
||||
private CoGmEditModel coGmModel = new();
|
||||
@@ -417,7 +417,7 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (!authState.User.TryGetTelegramId(out telegramId))
|
||||
if (!authState.User.TryGetPlatformIdentity(out var platform, out externalUserId))
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
@@ -428,21 +428,21 @@
|
||||
|
||||
private async Task LoadSessions()
|
||||
{
|
||||
groupManagement = await SessionService.GetGroupManagementForGmAsync(GroupId, telegramId);
|
||||
groupManagement = await SessionService.GetGroupManagementForCurrentUserAsync(GroupId);
|
||||
if (groupManagement is null)
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
}
|
||||
|
||||
sessions = await SessionService.GetUpcomingSessionsForGmAsync(GroupId, telegramId);
|
||||
sessions = await SessionService.GetUpcomingSessionsForCurrentUserAsync(GroupId);
|
||||
if (sessions is null)
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
}
|
||||
|
||||
campaignTemplates = await SessionService.GetCampaignTemplatesForGmAsync(GroupId, telegramId);
|
||||
campaignTemplates = await SessionService.GetCampaignTemplatesForCurrentUserAsync(GroupId);
|
||||
if (campaignTemplates is null)
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
@@ -470,8 +470,8 @@
|
||||
{
|
||||
await SessionService.AddCoGmForOwnerAsync(
|
||||
GroupId,
|
||||
telegramId,
|
||||
coGmModel.TelegramId.Value,
|
||||
"Telegram",
|
||||
coGmModel.TelegramId.Value.ToString(),
|
||||
coGmModel.DisplayName,
|
||||
coGmModel.TelegramUsername);
|
||||
|
||||
@@ -493,15 +493,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveCoGm(long coGmTelegramId)
|
||||
private async Task RemoveCoGm(string coGmExternalUserId)
|
||||
{
|
||||
errorMessage = null;
|
||||
successMessage = null;
|
||||
removingCoGmId = coGmTelegramId;
|
||||
removingCoGmId = coGmExternalUserId;
|
||||
|
||||
try
|
||||
{
|
||||
await SessionService.RemoveCoGmForOwnerAsync(GroupId, telegramId, coGmTelegramId);
|
||||
await SessionService.RemoveCoGmForOwnerAsync(GroupId, "Telegram", coGmExternalUserId);
|
||||
successMessage = "Co-GM удалён.";
|
||||
await LoadSessions();
|
||||
}
|
||||
@@ -527,7 +527,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
await SessionService.PromoteWaitlistedPlayerForGmAsync(sessionId, telegramId);
|
||||
await SessionService.PromoteWaitlistedPlayerForCurrentUserAsync(sessionId);
|
||||
await LoadSessions();
|
||||
}
|
||||
catch (SessionAccessDeniedException)
|
||||
@@ -559,7 +559,7 @@
|
||||
loadingParticipantsSessionId = sessionId;
|
||||
try
|
||||
{
|
||||
var participants = await SessionService.GetSessionParticipantsForGmAsync(sessionId, telegramId);
|
||||
var participants = await SessionService.GetSessionParticipantsForCurrentUserAsync(sessionId);
|
||||
participantsCache[sessionId] = participants ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -582,7 +582,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
await SessionService.RemovePlayerFromSessionForGmAsync(sessionId, telegramId, participantId);
|
||||
await SessionService.RemovePlayerFromSessionForCurrentUserAsync(sessionId, participantId);
|
||||
participantsCache.Remove(sessionId);
|
||||
successMessage = "Игрок исключён.";
|
||||
await LoadSessions();
|
||||
@@ -658,10 +658,9 @@
|
||||
|
||||
try
|
||||
{
|
||||
await SessionService.UpdateBatchDetailsForGmAsync(batch.BatchId, telegramId, batch.Title, batch.JoinLink);
|
||||
await SessionService.UpdateBatchNotificationModeForGmAsync(
|
||||
await SessionService.UpdateBatchDetailsForCurrentUserAsync(batch.BatchId, batch.Title, batch.JoinLink);
|
||||
await SessionService.UpdateBatchNotificationModeForCurrentUserAsync(
|
||||
batch.BatchId,
|
||||
telegramId,
|
||||
SessionNotificationModeExtensions.FromDatabaseValue(batch.NotificationMode));
|
||||
successMessage = "Настройки batch обновлены.";
|
||||
await LoadSessions();
|
||||
@@ -696,7 +695,7 @@
|
||||
try
|
||||
{
|
||||
var utcTime = new DateTimeOffset(batch.FirstScheduledAtLocal, TimeSpan.FromHours(3)).ToUniversalTime().UtcDateTime;
|
||||
await SessionService.RescheduleBatchForGmAsync(batch.BatchId, telegramId, utcTime, batch.IntervalDays);
|
||||
await SessionService.RescheduleBatchForCurrentUserAsync(batch.BatchId, utcTime, batch.IntervalDays);
|
||||
successMessage = "Расписание пачки обновлено.";
|
||||
await LoadSessions();
|
||||
}
|
||||
@@ -726,7 +725,7 @@
|
||||
? BatchCloneInterval.NextMonth
|
||||
: BatchCloneInterval.NextWeek;
|
||||
|
||||
var clonedBatch = await SessionService.CloneBatchForGmAsync(batch.BatchId, telegramId, interval);
|
||||
var clonedBatch = await SessionService.CloneBatchForCurrentUserAsync(batch.BatchId, interval);
|
||||
successMessage = $"Пачка склонирована: {clonedBatch.SessionCount} игр.";
|
||||
await LoadSessions();
|
||||
}
|
||||
@@ -759,7 +758,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var createdBatch = await SessionService.CreateBatchFromCampaignTemplateForGmAsync(template.Id, telegramId, utcTime);
|
||||
var createdBatch = await SessionService.CreateBatchFromCampaignTemplateForCurrentUserAsync(template.Id, utcTime);
|
||||
successMessage = $"Batch создан из шаблона: {createdBatch.SessionCount} игр.";
|
||||
await LoadSessions();
|
||||
}
|
||||
@@ -836,7 +835,7 @@
|
||||
private bool IsTemplateBusy(CampaignTemplateUsageModel template) => processingTemplateId == template.Id;
|
||||
|
||||
private string CurrentUserRole =>
|
||||
groupManagement?.Managers.FirstOrDefault(manager => manager.TelegramId == telegramId)?.Role
|
||||
groupManagement?.Managers.FirstOrDefault(manager => manager.ExternalUserId == externalUserId)?.Role
|
||||
?? GroupManagerRoleExtensions.CoGmValue;
|
||||
|
||||
private static string FormatRole(string role) =>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using System.Security.Claims
|
||||
@attribute [Authorize]
|
||||
@inject ISessionStore SessionStore
|
||||
@inject AuthorizedSessionService SessionService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@@ -85,9 +85,9 @@
|
||||
<td>
|
||||
<div class="player-info">
|
||||
<span class="player-name">@s.DisplayName</span>
|
||||
@if (!string.IsNullOrEmpty(s.TelegramUsername))
|
||||
@if (!string.IsNullOrEmpty(s.ExternalUsername))
|
||||
{
|
||||
<span class="player-username">@@@s.TelegramUsername</span>
|
||||
<span class="player-username">@@@s.ExternalUsername</span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
@@ -171,21 +171,20 @@
|
||||
Navigation.NavigateTo("/login");
|
||||
return;
|
||||
}
|
||||
var telegramIdClaim = user.FindFirst("telegram_id")?.Value
|
||||
?? user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (!long.TryParse(telegramIdClaim, out var telegramId))
|
||||
if (!user.TryGetPlatformIdentity(out var platform, out var externalUserId))
|
||||
{
|
||||
Navigation.NavigateTo("/login");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!await SessionStore.IsGroupManagerAsync(GroupId, telegramId))
|
||||
var groupManagement = await SessionService.GetGroupManagementForCurrentUserAsync(GroupId);
|
||||
if (groupManagement is null)
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
}
|
||||
stats = await SessionStore.GetGroupAttendanceStatsAsync(GroupId) ?? new();
|
||||
stats = await SessionService.GetGroupAttendanceStatsForCurrentUserAsync(GroupId) ?? new();
|
||||
UpdateSortedStats();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="glass-card group-card">
|
||||
<div class="group-card-icon">🎮</div>
|
||||
<h3 class="group-card-title">@group.Name</h3>
|
||||
<p class="group-card-id">ID: @group.TelegramChatId</p>
|
||||
<p class="group-card-id">ID: @(group.Platform == "Discord" ? group.ExternalGroupId : group.TelegramChatId.ToString())</p>
|
||||
<span class="status-badge @(group.ManagerRole == GroupManagerRoleExtensions.OwnerValue ? "status-success" : "status-info")" style="align-self: flex-start; margin-bottom: 1rem;">
|
||||
@FormatRole(group.ManagerRole)
|
||||
</span>
|
||||
@@ -93,13 +93,13 @@
|
||||
var user = authState.User;
|
||||
userName = user.Identity?.Name ?? "Мастер Игры";
|
||||
|
||||
if (!user.TryGetTelegramId(out var telegramId))
|
||||
if (!user.TryGetPlatformIdentity(out _, out _))
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
}
|
||||
|
||||
groups = await SessionService.GetGroupsForGmAsync(telegramId);
|
||||
groups = await SessionService.GetGroupsForCurrentUserAsync();
|
||||
}
|
||||
|
||||
private static string FormatRole(string role) =>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@entry.ChangedAt.ToString("dd.MM.yyyy HH:mm") UTC</td>
|
||||
<td>@entry.ActorName (@entry.ActorTelegramId)</td>
|
||||
<td>@entry.ActorName (@entry.ActorExternalUserId)</td>
|
||||
<td>
|
||||
<span class="status-badge @(GetBadgeClass(entry.ChangeType))">
|
||||
@GetChangeTypeLabel(entry.ChangeType)
|
||||
@@ -82,13 +82,13 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (!authState.User.TryGetTelegramId(out var telegramId))
|
||||
if (!authState.User.TryGetPlatformIdentity(out var platform, out var externalUserId))
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
return;
|
||||
}
|
||||
|
||||
var session = await SessionService.GetSessionForGmAsync(SessionId, telegramId);
|
||||
var session = await SessionService.GetSessionForCurrentUserAsync(SessionId);
|
||||
if (session is null)
|
||||
{
|
||||
Navigation.NavigateTo("/access-denied");
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
sessionTitle = session.Title;
|
||||
groupId = session.GroupId;
|
||||
entries = await SessionService.GetSessionHistoryForGmAsync(SessionId, telegramId);
|
||||
entries = await SessionService.GetSessionHistoryForCurrentUserAsync(SessionId);
|
||||
}
|
||||
|
||||
private string GetChangeTypeLabel(string changeType) => changeType switch
|
||||
|
||||
@@ -2,9 +2,9 @@ namespace GmRelay.Web;
|
||||
|
||||
public sealed class DiscordOAuthOptions
|
||||
{
|
||||
public required string ClientId { get; set; }
|
||||
public required string ClientSecret { get; set; }
|
||||
public required string RedirectUri { get; set; }
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
public string ClientSecret { get; set; } = string.Empty;
|
||||
public string RedirectUri { get; set; } = string.Empty;
|
||||
public string[] Scopes { get; set; } = ["identify", "guilds"];
|
||||
|
||||
public void Validate()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using GmRelay.Web;
|
||||
using GmRelay.Web.Components;
|
||||
using GmRelay.Web.Health;
|
||||
using GmRelay.Web.Services;
|
||||
|
||||
@@ -13,7 +13,16 @@ public sealed record WebGameGroup(
|
||||
string? ExternalGroupId,
|
||||
string Name,
|
||||
string? Platform,
|
||||
string ManagerRole = GroupManagerRoleExtensions.OwnerValue);
|
||||
string ManagerRole = GroupManagerRoleExtensions.OwnerValue)
|
||||
{
|
||||
public long GmTelegramId { get; init; }
|
||||
|
||||
public WebGameGroup(Guid id, long telegramChatId, string name, long gmTelegramId)
|
||||
: this(id, telegramChatId, null, name, null)
|
||||
{
|
||||
GmTelegramId = gmTelegramId;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record WebGroupManager(
|
||||
long TelegramId,
|
||||
@@ -22,7 +31,11 @@ public sealed record WebGroupManager(
|
||||
string? TelegramUsername,
|
||||
string? ExternalUsername,
|
||||
string Role,
|
||||
DateTime AddedAt);
|
||||
DateTime AddedAt)
|
||||
{
|
||||
public WebGroupManager(long telegramId, string displayName, string? telegramUsername, string role, DateTime addedAt)
|
||||
: this(telegramId, null, displayName, telegramUsername, null, role, addedAt) { }
|
||||
}
|
||||
|
||||
public sealed record WebGroupManagement(
|
||||
WebGameGroup Group,
|
||||
|
||||
Reference in New Issue
Block a user