fix(web): address PR review critical issues for Discord OAuth
PR Checks / test-and-build (pull_request) Successful in 6m6s

- Add V019 migration: rename session_audit_log.actor_telegram_id → actor_external_user_id
- Add CSRF protection to Discord OAuth flow (state cookie with HttpOnly/Secure/Strict)
- Add Discord OAuth env vars to compose.yaml, deploy.yml, and .env.example
- Fix SQL COALESCE for nullable telegram_id in GetGroupManagersAsync and GetSessionParticipantsAsync

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 12:07:40 +03:00
parent 50f5307aac
commit 66dc53f12f
6 changed files with 54 additions and 4 deletions
+21 -2
View File
@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using Telegram.Bot;
using Npgsql;
@@ -184,9 +185,16 @@ app.MapPost("/auth/logout", async (HttpContext context) =>
});
// Discord OAuth endpoints
app.MapGet("/auth/discord", (DiscordAuthService discordAuth) =>
app.MapGet("/auth/discord", (HttpContext context, DiscordAuthService discordAuth) =>
{
var state = Guid.NewGuid().ToString("N");
context.Response.Cookies.Append("__DiscordOAuthState", state, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
MaxAge = TimeSpan.FromMinutes(5)
});
var url = discordAuth.BuildAuthorizeUrl(state);
return Results.Redirect(url);
});
@@ -197,8 +205,19 @@ app.MapGet("/auth/discord/callback", async (
ISessionStore sessionStore) =>
{
var code = context.Request.Query["code"].ToString();
if (string.IsNullOrWhiteSpace(code))
var state = context.Request.Query["state"].ToString();
var storedState = context.Request.Cookies["__DiscordOAuthState"];
context.Response.Cookies.Delete("__DiscordOAuthState");
if (string.IsNullOrWhiteSpace(code) ||
string.IsNullOrWhiteSpace(state) ||
!CryptographicOperations.FixedTimeEquals(
System.Text.Encoding.UTF8.GetBytes(state),
System.Text.Encoding.UTF8.GetBytes(storedState ?? string.Empty)))
{
return Results.Redirect("/login?error=auth_failed");
}
var user = await discordAuth.ExchangeCodeAsync(code);
if (user is null)