fix(web): address PR review critical issues for Discord OAuth
PR Checks / test-and-build (pull_request) Successful in 6m6s
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:
@@ -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)
|
||||
|
||||
@@ -179,7 +179,7 @@ public sealed class SessionService(
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
return (await conn.QueryAsync<WebGroupManager>(
|
||||
"""
|
||||
SELECT p.telegram_id AS TelegramId,
|
||||
SELECT COALESCE(p.telegram_id, 0) AS TelegramId,
|
||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
||||
p.display_name AS DisplayName,
|
||||
p.telegram_username AS TelegramUsername,
|
||||
@@ -641,7 +641,7 @@ public sealed class SessionService(
|
||||
return (await conn.QueryAsync<WebParticipant>(
|
||||
"""
|
||||
SELECT sp.id AS Id,
|
||||
p.telegram_id AS TelegramId,
|
||||
COALESCE(p.telegram_id, 0) AS TelegramId,
|
||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
||||
p.display_name AS DisplayName,
|
||||
p.telegram_username AS TelegramUsername,
|
||||
|
||||
Reference in New Issue
Block a user