diff --git a/src/GmRelay.Web/Components/Pages/Profile.razor b/src/GmRelay.Web/Components/Pages/Profile.razor
index 1371aa6..beefed4 100644
--- a/src/GmRelay.Web/Components/Pages/Profile.razor
+++ b/src/GmRelay.Web/Components/Pages/Profile.razor
@@ -1,8 +1,11 @@
@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
Discord уже привязан.
} + + @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 = $""; + + } + } @if (!string.IsNullOrWhiteSpace(errorMessage)) diff --git a/src/GmRelay.Web/Program.cs b/src/GmRelay.Web/Program.cs index 7cb7367..15e8d65 100644 --- a/src/GmRelay.Web/Program.cs +++ b/src/GmRelay.Web/Program.cs @@ -61,7 +61,7 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc options.AccessDeniedPath = "/access-denied"; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; - options.Cookie.SameSite = SameSiteMode.Strict; + options.Cookie.SameSite = SameSiteMode.Lax; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = true; }); @@ -123,19 +123,38 @@ app.MapHealthChecks("/alive", new HealthCheckOptions }); // Endpoint to handle Telegram Login callback -app.MapGet("/auth/telegram", async (HttpContext context, TelegramAuthService authService) => +app.MapGet("/auth/telegram", async (HttpContext context, TelegramAuthService authService, ISessionStore sessionStore) => { - if (authService.Verify(context.Request.Query, out var telegramId, out var name)) + if (!authService.Verify(context.Request.Query, out var telegramId, out var name)) + return Results.Redirect("/login?error=auth_failed"); + + await sessionStore.UpsertPlayerAsync("Telegram", telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture), name, null); + + // If already authenticated via another platform, link instead of replacing session + if (context.User.Identity?.IsAuthenticated == true + && context.User.TryGetPlatformIdentity(out var currentPlatform, out var currentExternalUserId) + && currentPlatform != "Telegram") { - var authProperties = new AuthenticationProperties { IsPersistent = true }; - await context.SignInAsync( - CookieAuthenticationDefaults.AuthenticationScheme, - CreateTelegramPrincipal(telegramId, name), - authProperties); - return Results.Redirect("/"); + try + { + await sessionStore.LinkIdentityAsync( + currentPlatform, currentExternalUserId, + "Telegram", telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture), + name); + return Results.Redirect("/profile?linked=telegram"); + } + catch (InvalidOperationException ex) + { + return Results.Redirect($"/profile?link_error={Uri.EscapeDataString(ex.Message)}"); + } } - return Results.Redirect("/login?error=auth_failed"); + var authProperties = new AuthenticationProperties { IsPersistent = true }; + await context.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + CreateTelegramPrincipal(telegramId, name), + authProperties); + return Results.Redirect("/"); }); app.MapPost("/auth/telegram-webapp", async ( diff --git a/tests/GmRelay.Bot.Tests/Web/CookieAuthOptionsTests.cs b/tests/GmRelay.Bot.Tests/Web/CookieAuthOptionsTests.cs new file mode 100644 index 0000000..0e1abae --- /dev/null +++ b/tests/GmRelay.Bot.Tests/Web/CookieAuthOptionsTests.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace GmRelay.Bot.Tests.Web; + +public sealed class CookieAuthOptionsTests +{ + [Fact] + public void CookieAuthOptions_ShouldUseLaxSameSite_ToAllowOAuthCallback() + { + // Arrange + var services = new ServiceCollection(); + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = SameSiteMode.Lax; + options.ExpireTimeSpan = TimeSpan.FromDays(7); + options.SlidingExpiration = true; + }); + + var provider = services.BuildServiceProvider(); + var optionsMonitor = provider.GetRequiredService