Fix party slow mode across account sessions
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 8m22s
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 8m22s
This commit is contained in:
@@ -102,7 +102,7 @@ try {
|
|||||||
"Guest",
|
"Guest",
|
||||||
]);
|
]);
|
||||||
const roster = (await act(moderationRoom, 1)).players;
|
const roster = (await act(moderationRoom, 1)).players;
|
||||||
const friend = roster.find((p) => p.name === "Account friend");
|
let friend = roster.find((p) => p.name === "Account friend");
|
||||||
let guest = roster.find((p) => p.name === "Guest");
|
let guest = roster.find((p) => p.name === "Guest");
|
||||||
const community = async (who, action, payload = {}) =>
|
const community = async (who, action, payload = {}) =>
|
||||||
(
|
(
|
||||||
@@ -180,11 +180,33 @@ try {
|
|||||||
secret(3),
|
secret(3),
|
||||||
"Guest",
|
"Guest",
|
||||||
]);
|
]);
|
||||||
|
await community(1, "unadmin", { playerId: friend.id });
|
||||||
|
await chat(2, "Signed-in player", randomUUID());
|
||||||
|
await act(moderationRoom, 2, "leave");
|
||||||
|
await db.query("select public.join_multiplayer_room($1,$2,$3,$4)", [
|
||||||
|
moderationRoom,
|
||||||
|
secret(8),
|
||||||
|
secondAccount,
|
||||||
|
"Ignored",
|
||||||
|
]);
|
||||||
|
await assert.rejects(
|
||||||
|
chat(8, "Different browser", randomUUID()),
|
||||||
|
/slow mode/,
|
||||||
|
);
|
||||||
|
await db.query(
|
||||||
|
"update public.party_chat_account_cooldowns set last_message_at=clock_timestamp()-interval '11 seconds' where room_id=$1 and account_id=$2",
|
||||||
|
[moderationRoom, secondAccount],
|
||||||
|
);
|
||||||
|
await chat(8, "Allowed after the delay", randomUUID());
|
||||||
|
friend = (await act(moderationRoom, 1)).players.find(
|
||||||
|
(p) => p.name === "Account friend",
|
||||||
|
);
|
||||||
|
assert.ok(friend);
|
||||||
await community(1, "ban", { playerId: friend.id });
|
await community(1, "ban", { playerId: friend.id });
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
db.query("select public.join_multiplayer_room($1,$2,$3,$4)", [
|
db.query("select public.join_multiplayer_room($1,$2,$3,$4)", [
|
||||||
moderationRoom,
|
moderationRoom,
|
||||||
secret(8),
|
secret(9),
|
||||||
secondAccount,
|
secondAccount,
|
||||||
"Ignored",
|
"Ignored",
|
||||||
]),
|
]),
|
||||||
@@ -194,10 +216,14 @@ try {
|
|||||||
const privileges = (
|
const privileges = (
|
||||||
await db.query(`select
|
await db.query(`select
|
||||||
has_table_privilege('anon','public.party_chat_cooldowns','select') as anon_read,
|
has_table_privilege('anon','public.party_chat_cooldowns','select') as anon_read,
|
||||||
has_table_privilege('authenticated','public.party_chat_cooldowns','select') as authenticated_read`)
|
has_table_privilege('authenticated','public.party_chat_cooldowns','select') as authenticated_read,
|
||||||
|
has_table_privilege('anon','public.party_chat_account_cooldowns','select') as anon_account_read,
|
||||||
|
has_table_privilege('authenticated','public.party_chat_account_cooldowns','select') as authenticated_account_read`)
|
||||||
).rows[0];
|
).rows[0];
|
||||||
assert.equal(privileges.anon_read, false);
|
assert.equal(privileges.anon_read, false);
|
||||||
assert.equal(privileges.authenticated_read, false);
|
assert.equal(privileges.authenticated_read, false);
|
||||||
|
assert.equal(privileges.anon_account_read, false);
|
||||||
|
assert.equal(privileges.authenticated_account_read, false);
|
||||||
await db.query("delete from public.multiplayer_rooms where id=$1", [
|
await db.query("delete from public.multiplayer_rooms where id=$1", [
|
||||||
moderationRoom,
|
moderationRoom,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
-- Serialize the backfill with room joins, leaves, and chat writes.
|
||||||
|
lock table public.multiplayer_rooms in exclusive mode;
|
||||||
|
|
||||||
|
create table public.party_chat_account_cooldowns (
|
||||||
|
room_id uuid not null references public.multiplayer_rooms(id) on delete cascade,
|
||||||
|
account_id uuid not null references public.profiles(id) on delete cascade,
|
||||||
|
last_message_at timestamptz not null,
|
||||||
|
primary key(room_id, account_id)
|
||||||
|
);
|
||||||
|
create index party_chat_account_cooldowns_account on public.party_chat_account_cooldowns(account_id);
|
||||||
|
alter table public.party_chat_account_cooldowns enable row level security;
|
||||||
|
revoke all on public.party_chat_account_cooldowns from public, anon, authenticated;
|
||||||
|
grant all on public.party_chat_account_cooldowns to service_role;
|
||||||
|
|
||||||
|
-- Include the retained browser cooldown even when a player's messages were deleted.
|
||||||
|
insert into public.party_chat_account_cooldowns(room_id, account_id, last_message_at)
|
||||||
|
select p.room_id, p.account_id, max(c.last_message_at)
|
||||||
|
from public.multiplayer_players p
|
||||||
|
join public.party_chat_cooldowns c on c.room_id=p.room_id and c.access_hash=p.access_hash
|
||||||
|
where p.account_id is not null
|
||||||
|
group by p.room_id, p.account_id;
|
||||||
|
|
||||||
|
create or replace function public.multiplayer_chat(p_room uuid,p_access_hash text,p_text text default null,p_client_id uuid default null)
|
||||||
|
returns jsonb language plpgsql security invoker set search_path='' as $$
|
||||||
|
declare room public.multiplayer_rooms; player public.multiplayer_players; result jsonb; duplicate_message boolean;
|
||||||
|
begin
|
||||||
|
select * into room from public.multiplayer_rooms where id=p_room for update;
|
||||||
|
select * into player from public.multiplayer_players where room_id=p_room and access_hash=p_access_hash and not has_left;
|
||||||
|
duplicate_message := player.id is not null and p_client_id is not null and exists(
|
||||||
|
select 1 from public.multiplayer_messages where room_id=p_room and player_id=player.id and client_id=p_client_id
|
||||||
|
);
|
||||||
|
if player.id is not null and p_text is not null and room.chat_slow_seconds>0 and player.id<>room.host_player_id
|
||||||
|
and not exists(select 1 from public.party_moderators where room_id=p_room and player_id=player.id)
|
||||||
|
and not duplicate_message
|
||||||
|
and (exists(select 1 from public.party_chat_cooldowns where room_id=p_room and access_hash=p_access_hash
|
||||||
|
and last_message_at>clock_timestamp()-make_interval(secs=>room.chat_slow_seconds))
|
||||||
|
or (player.account_id is not null and exists(select 1 from public.party_chat_account_cooldowns
|
||||||
|
where room_id=p_room and account_id=player.account_id
|
||||||
|
and last_message_at>clock_timestamp()-make_interval(secs=>room.chat_slow_seconds))))
|
||||||
|
then raise exception 'Chat slow mode: wait before sending another message'; end if;
|
||||||
|
result:=public.multiplayer_chat_unchecked(p_room,p_access_hash,p_text,p_client_id);
|
||||||
|
if player.id is not null and p_text is not null and p_client_id is not null and not duplicate_message then
|
||||||
|
insert into public.party_chat_cooldowns(room_id,access_hash,last_message_at)
|
||||||
|
values(p_room,p_access_hash,clock_timestamp())
|
||||||
|
on conflict(room_id,access_hash) do update set last_message_at=excluded.last_message_at;
|
||||||
|
if player.account_id is not null then
|
||||||
|
insert into public.party_chat_account_cooldowns(room_id,account_id,last_message_at)
|
||||||
|
values(p_room,player.account_id,clock_timestamp())
|
||||||
|
on conflict(room_id,account_id) do update set last_message_at=excluded.last_message_at;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
return jsonb_set(result,'{messages}',coalesce((select jsonb_agg(msg || jsonb_build_object('tag',tag.tag) order by ord)
|
||||||
|
from jsonb_array_elements(result->'messages') with ordinality x(msg,ord)
|
||||||
|
left join public.party_tags tag on tag.room_id=p_room and tag.player_id=(msg->>'playerId')::uuid),'[]'::jsonb));
|
||||||
|
end; $$;
|
||||||
|
revoke all on function public.multiplayer_chat(uuid,text,text,uuid) from public,anon,authenticated;
|
||||||
|
grant execute on function public.multiplayer_chat(uuid,text,text,uuid) to service_role;
|
||||||
|
notify pgrst,'reload schema';
|
||||||
Reference in New Issue
Block a user