fix: close web access to foreign groups and sessions
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
namespace GmRelay.Web.Services;
|
||||
|
||||
public sealed class AuthorizedSessionService(ISessionStore sessionStore)
|
||||
{
|
||||
public Task<List<WebGameGroup>> GetGroupsForGmAsync(long gmId) =>
|
||||
sessionStore.GetGroupsForGmAsync(gmId);
|
||||
|
||||
public async Task<List<WebSession>?> GetUpcomingSessionsForGmAsync(Guid groupId, long gmId)
|
||||
{
|
||||
if (!await GroupBelongsToGmAsync(groupId, gmId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await sessionStore.GetUpcomingSessionsAsync(groupId);
|
||||
}
|
||||
|
||||
public async Task<WebSession?> GetSessionForGmAsync(Guid sessionId, long gmId)
|
||||
{
|
||||
var session = await sessionStore.GetSessionAsync(sessionId);
|
||||
if (session is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await GroupBelongsToGmAsync(session.GroupId, gmId) ? session : null;
|
||||
}
|
||||
|
||||
public async Task UpdateSessionForGmAsync(Guid sessionId, long gmId, string title, DateTime scheduledAt, string joinLink)
|
||||
{
|
||||
var session = await GetSessionForGmAsync(sessionId, gmId);
|
||||
if (session is null)
|
||||
{
|
||||
throw new SessionAccessDeniedException(sessionId, gmId);
|
||||
}
|
||||
|
||||
await sessionStore.UpdateSessionAsync(sessionId, session.GroupId, title, scheduledAt, joinLink);
|
||||
}
|
||||
|
||||
private async Task<bool> GroupBelongsToGmAsync(Guid groupId, long gmId)
|
||||
{
|
||||
var group = await sessionStore.GetGroupAsync(groupId);
|
||||
return group?.GmTelegramId == gmId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace GmRelay.Web.Services;
|
||||
|
||||
public static class ClaimsPrincipalExtensions
|
||||
{
|
||||
public static bool TryGetTelegramId(this ClaimsPrincipal user, out long telegramId) =>
|
||||
long.TryParse(user.FindFirst("TelegramId")?.Value, out telegramId);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GmRelay.Web.Services;
|
||||
|
||||
public interface ISessionStore
|
||||
{
|
||||
Task<List<WebGameGroup>> GetGroupsForGmAsync(long gmId);
|
||||
Task<WebGameGroup?> GetGroupAsync(Guid groupId);
|
||||
Task<List<WebSession>> GetUpcomingSessionsAsync(Guid groupId);
|
||||
Task<WebSession?> GetSessionAsync(Guid sessionId);
|
||||
Task UpdateSessionAsync(Guid sessionId, Guid groupId, string title, DateTime scheduledAt, string joinLink);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace GmRelay.Web.Services;
|
||||
|
||||
public sealed class SessionAccessDeniedException(Guid sessionId, long gmId)
|
||||
: InvalidOperationException($"Session '{sessionId}' is not accessible for GM '{gmId}'.");
|
||||
@@ -12,7 +12,7 @@ public sealed record WebSession(Guid Id, Guid GroupId, string Title, DateTime Sc
|
||||
public sealed class SessionService(
|
||||
NpgsqlDataSource dataSource,
|
||||
ITelegramBotClient bot,
|
||||
ILogger<SessionService> logger)
|
||||
ILogger<SessionService> logger) : ISessionStore
|
||||
{
|
||||
public async Task<List<WebGameGroup>> GetGroupsForGmAsync(long gmId)
|
||||
{
|
||||
@@ -22,11 +22,19 @@ public sealed class SessionService(
|
||||
new { GmId = gmId })).ToList();
|
||||
}
|
||||
|
||||
public async Task<WebGameGroup?> GetGroupAsync(Guid groupId)
|
||||
{
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
return await conn.QuerySingleOrDefaultAsync<WebGameGroup>(
|
||||
"SELECT id, telegram_chat_id AS TelegramChatId, name, gm_telegram_id AS GmTelegramId FROM game_groups WHERE id = @GroupId",
|
||||
new { GroupId = groupId });
|
||||
}
|
||||
|
||||
public async Task<List<WebSession>> GetUpcomingSessionsAsync(Guid groupId)
|
||||
{
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
return (await conn.QueryAsync<WebSession>(
|
||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||
g.telegram_chat_id AS TelegramChatId
|
||||
FROM sessions s
|
||||
@@ -40,7 +48,7 @@ public sealed class SessionService(
|
||||
{
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
return await conn.QuerySingleOrDefaultAsync<WebSession>(
|
||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||
g.telegram_chat_id AS TelegramChatId
|
||||
FROM sessions s
|
||||
@@ -49,29 +57,41 @@ public sealed class SessionService(
|
||||
new { SessionId = sessionId });
|
||||
}
|
||||
|
||||
public async Task UpdateSessionAsync(Guid sessionId, string title, DateTime scheduledAt, string joinLink)
|
||||
public async Task UpdateSessionAsync(Guid sessionId, Guid groupId, string title, DateTime scheduledAt, string joinLink)
|
||||
{
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
await using var transaction = await conn.BeginTransactionAsync();
|
||||
|
||||
// 1. Fetch current session with all required columns for WebSession record
|
||||
var oldSession = await conn.QuerySingleAsync<WebSession>(
|
||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||
var oldSession = await conn.QuerySingleOrDefaultAsync<WebSession>(
|
||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||
g.telegram_chat_id AS TelegramChatId
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
WHERE s.id = @Id",
|
||||
new { Id = sessionId }, transaction);
|
||||
|
||||
// 2. Update Session
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE sessions SET title = @Title, scheduled_at = @ScheduledAt, join_link = @JoinLink, updated_at = now()
|
||||
WHERE id = @Id",
|
||||
new { Id = sessionId, Title = title, ScheduledAt = scheduledAt, JoinLink = joinLink },
|
||||
WHERE s.id = @Id AND s.group_id = @GroupId",
|
||||
new { Id = sessionId, GroupId = groupId },
|
||||
transaction);
|
||||
|
||||
// 3. Update all sessions in the same batch with new title (optional, usually batch shares title)
|
||||
if (oldSession is null)
|
||||
{
|
||||
throw new SessionAccessDeniedException(sessionId, 0);
|
||||
}
|
||||
|
||||
var updatedRows = await conn.ExecuteAsync(
|
||||
@"UPDATE sessions
|
||||
SET title = @Title,
|
||||
scheduled_at = @ScheduledAt,
|
||||
join_link = @JoinLink,
|
||||
updated_at = now()
|
||||
WHERE id = @Id AND group_id = @GroupId",
|
||||
new { Id = sessionId, GroupId = groupId, Title = title, ScheduledAt = scheduledAt, JoinLink = joinLink },
|
||||
transaction);
|
||||
|
||||
if (updatedRows == 0)
|
||||
{
|
||||
throw new SessionAccessDeniedException(sessionId, 0);
|
||||
}
|
||||
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE sessions SET title = @Title WHERE batch_id = @BatchId",
|
||||
new { Title = title, BatchId = oldSession.BatchId },
|
||||
@@ -79,7 +99,6 @@ public sealed class SessionService(
|
||||
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// 4. Send Telegram Notification
|
||||
var timeChanged = oldSession.ScheduledAt != scheduledAt;
|
||||
var notification = $"🔄 <b>Мастер обновил игру!</b>\n\n" +
|
||||
$"📌 <b>{System.Net.WebUtility.HtmlEncode(title)}</b>\n" +
|
||||
@@ -87,7 +106,6 @@ public sealed class SessionService(
|
||||
|
||||
await bot.SendMessage(oldSession.TelegramChatId, notification, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
||||
|
||||
// 5. Update Original Batch Message
|
||||
if (oldSession.BatchMessageId.HasValue)
|
||||
{
|
||||
await TryUpdateBatchMessageAsync(oldSession.BatchId, oldSession.TelegramChatId, oldSession.BatchMessageId.Value, title);
|
||||
@@ -124,7 +142,6 @@ public sealed class SessionService(
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log but don't throw — message may be too old or have same content
|
||||
logger.LogWarning(ex, "Failed to update batch message {MessageId} in chat {ChatId}", messageId, chatId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user