5e3028e470
- Change cookie auth SameSite from Strict to Lax so Discord OAuth callback can see existing Telegram auth session and perform linking instead of creating a new standalone Discord session (root cause of broken linking). - Add linking logic to /auth/telegram endpoint for Discord→Telegram linking. - Add Telegram Login Widget in Profile.razor for Discord users. - Add CookieAuthOptionsTests to verify Lax SameSite configuration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
192 lines
6.2 KiB
Plaintext
192 lines
6.2 KiB
Plaintext
@page "/profile"
|
|
@using Microsoft.AspNetCore.Authorization
|
|
@using Microsoft.AspNetCore.Components.Authorization
|
|
@using Microsoft.Extensions.Configuration
|
|
@attribute [Authorize]
|
|
@inject ISessionStore SessionStore
|
|
@inject IConfiguration Configuration
|
|
@inject NavigationManager Navigation
|
|
|
|
<PageTitle>Профиль — GM-Relay</PageTitle>
|
|
|
|
<div class="profile-container">
|
|
<h1 class="page-title">Профиль</h1>
|
|
|
|
@if (identities is null)
|
|
{
|
|
<p class="loading-text">Загрузка...</p>
|
|
}
|
|
else if (identities.Count == 0)
|
|
{
|
|
<div class="profile-card">
|
|
<p>Связанные аккаунты не найдены.</p>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="profile-card">
|
|
<h2 class="section-title">Связанные аккаунты</h2>
|
|
<ul class="identity-list">
|
|
@foreach (var id in identities)
|
|
{
|
|
<li class="identity-item">
|
|
<div class="identity-info">
|
|
<span class="identity-platform">@id.Platform</span>
|
|
<span class="identity-name">@id.DisplayName</span>
|
|
</div>
|
|
@if (id.Platform != currentPlatform || id.ExternalUserId != currentExternalUserId)
|
|
{
|
|
<button class="btn btn-secondary btn-small"
|
|
@onclick="() => Unlink(id.Platform, id.ExternalUserId)"
|
|
disabled="@isUnlinking">
|
|
Отвязать
|
|
</button>
|
|
}
|
|
else
|
|
{
|
|
<span class="identity-badge">Текущий</span>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
</div>
|
|
}
|
|
|
|
<div class="profile-card">
|
|
<h2 class="section-title">Добавить аккаунт</h2>
|
|
@if (!HasLinkedPlatform("Discord"))
|
|
{
|
|
<a href="/auth/discord" class="btn btn-primary">
|
|
Привязать Discord
|
|
</a>
|
|
}
|
|
else
|
|
{
|
|
<p class="muted-text">Discord уже привязан.</p>
|
|
}
|
|
|
|
@if (currentPlatform == "Discord" && !HasLinkedPlatform("Telegram"))
|
|
{
|
|
var botUsername = Configuration["Telegram__BotUsername"] ?? Configuration["Telegram:BotUsername"];
|
|
if (!string.IsNullOrWhiteSpace(botUsername))
|
|
{
|
|
var authUrl = new Uri(new Uri(Navigation.BaseUri), "auth/telegram").ToString();
|
|
var widgetHtml = $"<script async src=\"https://telegram.org/js/telegram-widget.js?22\" data-telegram-login=\"{botUsername}\" data-size=\"large\" data-auth-url=\"{authUrl}\" data-request-access=\"write\"></script>";
|
|
<div class="telegram-widget-wrapper">
|
|
@((MarkupString)widgetHtml)
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
{
|
|
<div class="alert alert-error">@errorMessage</div>
|
|
}
|
|
|
|
@if (!string.IsNullOrWhiteSpace(successMessage))
|
|
{
|
|
<div class="alert alert-success">@successMessage</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
private List<LinkedIdentity>? identities;
|
|
private string? currentPlatform;
|
|
private string? currentExternalUserId;
|
|
private bool isUnlinking;
|
|
private string? errorMessage;
|
|
private string? successMessage;
|
|
|
|
[CascadingParameter]
|
|
private Task<AuthenticationState>? AuthenticationStateTask { get; set; }
|
|
|
|
[SupplyParameterFromQuery]
|
|
public string? Linked { get; set; }
|
|
|
|
[SupplyParameterFromQuery(Name = "link_error")]
|
|
public string? LinkError { get; set; }
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
if (AuthenticationStateTask is not null)
|
|
{
|
|
var authState = await AuthenticationStateTask;
|
|
var user = authState.User;
|
|
if (user.TryGetPlatformIdentity(out var plat, out var extId))
|
|
{
|
|
currentPlatform = plat;
|
|
currentExternalUserId = extId;
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(Linked))
|
|
{
|
|
successMessage = $"{Linked} аккаунт успешно привязан!";
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(LinkError))
|
|
{
|
|
errorMessage = $"Ошибка привязки: {Uri.UnescapeDataString(LinkError)}";
|
|
}
|
|
|
|
await LoadIdentities();
|
|
}
|
|
|
|
private async Task LoadIdentities()
|
|
{
|
|
try
|
|
{
|
|
if (currentPlatform is not null && currentExternalUserId is not null)
|
|
{
|
|
identities = await SessionStore.GetLinkedIdentitiesAsync(currentPlatform, currentExternalUserId);
|
|
}
|
|
else
|
|
{
|
|
identities = [];
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Не удалось загрузить аккаунты: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private bool HasLinkedPlatform(string platform)
|
|
{
|
|
return identities?.Any(i => i.Platform == platform) ?? false;
|
|
}
|
|
|
|
private async Task Unlink(string platform, string externalUserId)
|
|
{
|
|
isUnlinking = true;
|
|
errorMessage = null;
|
|
successMessage = null;
|
|
|
|
try
|
|
{
|
|
if (currentPlatform is null || currentExternalUserId is null)
|
|
{
|
|
errorMessage = "Не удалось определить текущего пользователя.";
|
|
return;
|
|
}
|
|
|
|
await SessionStore.UnlinkIdentityAsync(currentPlatform, currentExternalUserId, platform, externalUserId);
|
|
successMessage = $"{platform} аккаунт отвязан.";
|
|
await LoadIdentities();
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
errorMessage = $"Ошибка отвязки: {ex.Message}";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Ошибка отвязки: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
isUnlinking = false;
|
|
}
|
|
}
|
|
}
|