Resynchronise schema.sql avec l'anti-triche par signalement d'Alexandre
submit_icarus_score/submit_melon_score (appliquées directement en base, jamais commitées) sont récupérées mot pour mot via pg_get_functiondef : signalement dans game_cheat_flags plutôt que rejet serveur, le score reste toujours accepté. L'ancienne couche par rejet strict (icarus_runs/start_icarus_run) devenue orpheline est explicitement supprimée plutôt que laissée à traîner. CLAUDE.md documente le nouveau mécanisme (V12) et n'a plus aucune mention de l'ancien. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+370
-74
@@ -579,53 +579,53 @@ create policy "icarus_scores readable by authenticated"
|
||||
-- événement, pas de notion de jour comme pour la Course du Char).
|
||||
alter table public.settings add column if not exists icarus_points_awarded boolean not null default false;
|
||||
|
||||
-- Deuxième couche, orthogonale à la borne fixe ci-dessus (audit de
|
||||
-- sécurité) : le jeu n'a aucune vérification de gameplay (génération
|
||||
-- procédurale entièrement côté client, voir V6/§3bis) — un score
|
||||
-- jusqu'à 500 reste soumettable tel quel sans avoir vraiment joué. Cette
|
||||
-- table fait tenir un compte du temps RÉEL écoulé (horloge du serveur,
|
||||
-- jamais une durée envoyée par le client — sinon aussi falsifiable que le
|
||||
-- score lui-même) entre le début d'une partie et sa soumission, pour
|
||||
-- rejeter un score incohérent avec le temps réellement passé. Aucun accès
|
||||
-- direct côté client (pas de grant) : seules les deux RPC ci-dessous
|
||||
-- (SECURITY DEFINER) la lisent/écrivent.
|
||||
create table if not exists public.icarus_runs (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
user_id uuid not null references public.profiles (id) on delete cascade,
|
||||
started_at timestamptz not null default now()
|
||||
);
|
||||
-- Anti-triche par signalement (Alexandre, appliqué directement en base —
|
||||
-- récupéré ici via pg_get_functiondef le 2026-08-25 pour que ce fichier
|
||||
-- redevienne la vraie source de vérité, voir game_cheat_flags en section
|
||||
-- 17). Remplace la couche par rejet serveur strict qui vivait ici
|
||||
-- (icarus_runs/start_icarus_run(), voir §3bis/V12 dans CLAUDE.md) : jamais
|
||||
-- de blocage, un score dont la télémétrie de partie (durée, nombre de
|
||||
-- battements d'aile — envoyée par le client dans p_run, voir
|
||||
-- IcarusRunTelemetry) est manquante, invalide, ou incohérente avec le
|
||||
-- rythme maximal du jeu déclenche seulement un signalement dans
|
||||
-- game_cheat_flags pour vérification manuelle par un Archonte sur /admin.
|
||||
-- Nettoyage de l'ancienne couche, devenue orpheline (plus aucun code
|
||||
-- client ne l'appelle) :
|
||||
drop function if exists public.submit_icarus_score(integer, uuid);
|
||||
drop function if exists public.start_icarus_run();
|
||||
drop table if exists public.icarus_runs;
|
||||
|
||||
alter table public.icarus_runs enable row level security;
|
||||
-- Marqueur d'idempotence pour l'attribution automatique des gloires (un seul
|
||||
-- événement, pas de notion de jour comme pour la Course du Char).
|
||||
alter table public.settings add column if not exists icarus_points_awarded boolean not null default false;
|
||||
|
||||
create or replace function public.start_icarus_run()
|
||||
returns uuid
|
||||
-- Surcharge à un seul paramètre : conservée pour compat (ancien client,
|
||||
-- appel RPC direct sans télémétrie), délègue à la version avec p_run à
|
||||
-- null — systématiquement signalée comme suspecte ci-dessous, mais jamais
|
||||
-- bloquée.
|
||||
create or replace function public.submit_icarus_score(p_score integer)
|
||||
returns public.icarus_scores
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_id uuid;
|
||||
v_row public.icarus_scores;
|
||||
begin
|
||||
if auth.uid() is null then
|
||||
raise exception 'authentication required';
|
||||
end if;
|
||||
select * into v_row
|
||||
from public.submit_icarus_score(p_score, null::jsonb);
|
||||
|
||||
insert into public.icarus_runs (user_id) values (auth.uid()) returning id into v_id;
|
||||
return v_id;
|
||||
return v_row;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.start_icarus_run() to authenticated;
|
||||
grant execute on function public.submit_icarus_score(integer) to authenticated;
|
||||
|
||||
-- Signature changée (ajout de p_run_id) : l'ancienne (integer) est
|
||||
-- explicitement supprimée, sinon create or replace créerait une 2e
|
||||
-- surcharge au lieu de remplacer (même remarque que post_wall_note).
|
||||
drop function if exists public.submit_icarus_score(integer);
|
||||
|
||||
-- Seul point d'entrée pour soumettre un score. Une fois la date du Tribunal
|
||||
-- atteinte, les scores sont figés : la RPC ne fait plus rien (retourne le
|
||||
-- record existant sans le modifier) plutôt que d'échouer bruyamment.
|
||||
create or replace function public.submit_icarus_score(p_score integer, p_run_id uuid)
|
||||
-- Seul vrai point d'entrée utilisé par le client actuel. Une fois la date
|
||||
-- du Tribunal atteinte, les scores sont figés : la RPC ne fait plus rien
|
||||
-- (retourne le record existant sans le modifier) plutôt que d'échouer
|
||||
-- bruyamment — mais le signalement, lui, a déjà eu lieu avant ce point.
|
||||
create or replace function public.submit_icarus_score(p_score integer, p_run jsonb)
|
||||
returns public.icarus_scores
|
||||
language plpgsql
|
||||
security definer
|
||||
@@ -634,20 +634,10 @@ as $$
|
||||
declare
|
||||
v_tribunal_date timestamptz;
|
||||
v_row public.icarus_scores;
|
||||
v_started_at timestamptz;
|
||||
v_elapsed_seconds double precision;
|
||||
-- Temps minimal réel pour passer une colonne, au rythme le plus rapide
|
||||
-- jamais atteignable dans le jeu : vitesse de défilement pleinement
|
||||
-- montée en difficulté (FORWARD_SPEED × SPEED_MAX_MULTIPLIER) ET
|
||||
-- bouclier du boost actif en permanence (× BOOST_SPEED_MULTIPLIER en
|
||||
-- plus) — un cas déjà irréaliste en soi (le bouclier n'est ni continu
|
||||
-- ni permanent), donc une marge de sécurité généreuse avant même le
|
||||
-- ×0.9 ci-dessous. Doit rester aligné avec COLUMN_SPACING/FORWARD_SPEED/
|
||||
-- SPEED_MAX_MULTIPLIER/BOOST_SPEED_MULTIPLIER (src/lib/icarus/constants.ts).
|
||||
-- 210 / (130 × 1.6 × 1.7) ≈ 0.594s, encore réduit de 10% (marge contre
|
||||
-- le jitter d'arrondi de la boucle de jeu) : jamais assez strict pour
|
||||
-- rejeter un score légitime, seulement pour rejeter l'impossible.
|
||||
c_min_seconds_per_column constant double precision := 0.53;
|
||||
v_previous_best integer;
|
||||
v_duration_ms numeric;
|
||||
v_actions numeric;
|
||||
v_details jsonb;
|
||||
begin
|
||||
if auth.uid() is null then
|
||||
raise exception 'authentication required';
|
||||
@@ -661,28 +651,115 @@ begin
|
||||
raise exception 'invalid score';
|
||||
end if;
|
||||
|
||||
select tribunal_date into v_tribunal_date from public.settings where id = true;
|
||||
select best_score into v_previous_best
|
||||
from public.icarus_scores
|
||||
where user_id = auth.uid();
|
||||
|
||||
if v_tribunal_date is not null and now() >= v_tribunal_date then
|
||||
select * into v_row from public.icarus_scores where user_id = auth.uid();
|
||||
return v_row;
|
||||
if p_run is null
|
||||
or jsonb_typeof(p_run) is distinct from 'object'
|
||||
or (p_run ->> 'v') is distinct from '1' then
|
||||
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'icarus',
|
||||
p_score,
|
||||
'high',
|
||||
'missing_run_telemetry',
|
||||
'Score soumis sans télémétrie de partie (appel RPC direct ou ancien client).',
|
||||
jsonb_build_object('previous_best', v_previous_best)
|
||||
);
|
||||
|
||||
else
|
||||
if jsonb_typeof(p_run -> 'duration_ms') = 'number' then
|
||||
v_duration_ms := (p_run ->> 'duration_ms')::numeric;
|
||||
end if;
|
||||
|
||||
if jsonb_typeof(p_run -> 'actions') = 'number' then
|
||||
v_actions := (p_run ->> 'actions')::numeric;
|
||||
end if;
|
||||
|
||||
v_details := jsonb_build_object(
|
||||
'duration_ms', v_duration_ms,
|
||||
'actions', v_actions,
|
||||
'previous_best', v_previous_best
|
||||
);
|
||||
|
||||
if v_duration_ms is null
|
||||
or v_actions is null
|
||||
or v_duration_ms < 0
|
||||
or v_actions < 0 then
|
||||
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'icarus',
|
||||
p_score,
|
||||
'high',
|
||||
'invalid_run_telemetry',
|
||||
'La télémétrie de la partie est absente ou invalide.',
|
||||
v_details
|
||||
);
|
||||
|
||||
else
|
||||
if p_score >= 3 and v_duration_ms < p_score * 450 then
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'icarus',
|
||||
p_score,
|
||||
'high',
|
||||
'impossible_score_pace',
|
||||
'Le score a progressé plus vite que la vitesse maximale du jeu ne le permet.',
|
||||
v_details
|
||||
);
|
||||
end if;
|
||||
|
||||
if p_score > 0 and v_actions < 1 then
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'icarus',
|
||||
p_score,
|
||||
'high',
|
||||
'score_without_input',
|
||||
'Un score positif a été soumis sans aucun battement d''aile enregistré.',
|
||||
v_details
|
||||
);
|
||||
end if;
|
||||
|
||||
if v_duration_ms > 0
|
||||
and v_actions > (v_duration_ms / 1000) * 20 + 10 then
|
||||
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'icarus',
|
||||
p_score,
|
||||
'warning',
|
||||
'extreme_input_rate',
|
||||
'Cadence de battements anormalement élevée sur la partie.',
|
||||
v_details
|
||||
);
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
|
||||
if p_score > 0 then
|
||||
select started_at into v_started_at
|
||||
from public.icarus_runs
|
||||
where id = p_run_id and user_id = auth.uid();
|
||||
select tribunal_date into v_tribunal_date
|
||||
from public.settings
|
||||
where id = true;
|
||||
|
||||
if v_started_at is null then
|
||||
raise exception 'invalid run';
|
||||
end if;
|
||||
if v_tribunal_date is not null and now() >= v_tribunal_date then
|
||||
select * into v_row
|
||||
from public.icarus_scores
|
||||
where user_id = auth.uid();
|
||||
|
||||
v_elapsed_seconds := extract(epoch from (now() - v_started_at));
|
||||
if p_score > floor(v_elapsed_seconds / c_min_seconds_per_column) then
|
||||
raise exception 'score incohérent avec le temps de jeu écoulé';
|
||||
end if;
|
||||
|
||||
delete from public.icarus_runs where id = p_run_id;
|
||||
return v_row;
|
||||
end if;
|
||||
|
||||
insert into public.icarus_scores (user_id, best_score, updated_at)
|
||||
@@ -692,12 +769,15 @@ begin
|
||||
updated_at = now()
|
||||
where excluded.best_score > public.icarus_scores.best_score;
|
||||
|
||||
select * into v_row from public.icarus_scores where user_id = auth.uid();
|
||||
select * into v_row
|
||||
from public.icarus_scores
|
||||
where user_id = auth.uid();
|
||||
|
||||
return v_row;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.submit_icarus_score(integer, uuid) to authenticated;
|
||||
grant execute on function public.submit_icarus_score(integer, jsonb) to authenticated;
|
||||
|
||||
do $$
|
||||
begin
|
||||
@@ -1618,18 +1698,58 @@ create policy "melon_scores readable by authenticated"
|
||||
-- événement, comme icarus_points_awarded).
|
||||
alter table public.settings add column if not exists melon_points_awarded boolean not null default false;
|
||||
|
||||
-- Seul point d'entrée pour soumettre un score. Une fois la date du Tribunal
|
||||
-- atteinte, les scores sont figés : la RPC ne fait plus rien (retourne le
|
||||
-- record existant sans le modifier) plutôt que d'échouer bruyamment.
|
||||
-- Anti-triche par signalement (Alexandre, appliqué directement en base —
|
||||
-- récupéré ici via pg_get_functiondef le 2026-08-25, même mécanisme que
|
||||
-- submit_icarus_score plus haut, voir §3bis/V12 dans CLAUDE.md et
|
||||
-- game_cheat_flags en section 17) : jamais de blocage, une partie dont la
|
||||
-- télémétrie (durée, nombre de lâchers, nombre de fusions — envoyée par le
|
||||
-- client dans p_run, voir MelonRunTelemetry) est manquante, invalide, ou
|
||||
-- incohérente avec ce que la physique du jeu permet (cooldown de lâcher,
|
||||
-- nombre de fusions borné par le nombre de lâchers, score borné par le
|
||||
-- gain maximal théorique par fusion) déclenche un signalement dans
|
||||
-- game_cheat_flags pour vérification manuelle par un Archonte sur /admin.
|
||||
--
|
||||
-- Surcharge à un seul paramètre : conservée pour compat, délègue à la
|
||||
-- version avec p_run à null — systématiquement signalée comme suspecte
|
||||
-- ci-dessous, mais jamais bloquée.
|
||||
create or replace function public.submit_melon_score(p_score integer)
|
||||
returns public.melon_scores
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_row public.melon_scores;
|
||||
begin
|
||||
select * into v_row
|
||||
from public.submit_melon_score(p_score, null::jsonb);
|
||||
|
||||
return v_row;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.submit_melon_score(integer) to authenticated;
|
||||
|
||||
-- Seul vrai point d'entrée utilisé par le client actuel. Une fois la date
|
||||
-- du Tribunal atteinte, les scores sont figés : la RPC ne fait plus rien
|
||||
-- (retourne le record existant sans le modifier) plutôt que d'échouer
|
||||
-- bruyamment — mais le signalement, lui, a déjà eu lieu avant ce point.
|
||||
create or replace function public.submit_melon_score(p_score integer, p_run jsonb)
|
||||
returns public.melon_scores
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_tribunal_date timestamptz;
|
||||
v_row public.melon_scores;
|
||||
v_previous_best integer;
|
||||
v_duration_ms numeric;
|
||||
v_actions numeric;
|
||||
v_merges numeric;
|
||||
v_citizen_count integer;
|
||||
v_max_tier integer;
|
||||
v_details jsonb;
|
||||
begin
|
||||
if auth.uid() is null then
|
||||
raise exception 'authentication required';
|
||||
@@ -1643,10 +1763,143 @@ begin
|
||||
raise exception 'invalid score';
|
||||
end if;
|
||||
|
||||
select tribunal_date into v_tribunal_date from public.settings where id = true;
|
||||
select best_score into v_previous_best
|
||||
from public.melon_scores
|
||||
where user_id = auth.uid();
|
||||
|
||||
select count(*)::integer into v_citizen_count
|
||||
from public.profiles
|
||||
where role = 'public';
|
||||
|
||||
v_max_tier := greatest(v_citizen_count - 1, 0);
|
||||
|
||||
if p_run is null
|
||||
or jsonb_typeof(p_run) is distinct from 'object'
|
||||
or (p_run ->> 'v') is distinct from '1' then
|
||||
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'melon',
|
||||
p_score,
|
||||
'high',
|
||||
'missing_run_telemetry',
|
||||
'Score soumis sans télémétrie de partie (appel RPC direct ou ancien client).',
|
||||
jsonb_build_object('previous_best', v_previous_best)
|
||||
);
|
||||
|
||||
else
|
||||
if jsonb_typeof(p_run -> 'duration_ms') = 'number' then
|
||||
v_duration_ms := (p_run ->> 'duration_ms')::numeric;
|
||||
end if;
|
||||
|
||||
if jsonb_typeof(p_run -> 'actions') = 'number' then
|
||||
v_actions := (p_run ->> 'actions')::numeric;
|
||||
end if;
|
||||
|
||||
if jsonb_typeof(p_run -> 'merges') = 'number' then
|
||||
v_merges := (p_run ->> 'merges')::numeric;
|
||||
end if;
|
||||
|
||||
v_details := jsonb_build_object(
|
||||
'duration_ms', v_duration_ms,
|
||||
'actions', v_actions,
|
||||
'merges', v_merges,
|
||||
'previous_best', v_previous_best
|
||||
);
|
||||
|
||||
if v_duration_ms is null
|
||||
or v_actions is null
|
||||
or v_merges is null
|
||||
or v_duration_ms < 0
|
||||
or v_actions < 0
|
||||
or v_merges < 0 then
|
||||
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'melon',
|
||||
p_score,
|
||||
'high',
|
||||
'invalid_run_telemetry',
|
||||
'La télémétrie de la partie est absente ou invalide.',
|
||||
v_details
|
||||
);
|
||||
|
||||
else
|
||||
if p_score > 0 and (v_actions < 2 or v_merges < 1) then
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'melon',
|
||||
p_score,
|
||||
'high',
|
||||
'score_without_required_moves',
|
||||
'Un score positif nécessite au moins deux lâchers et une fusion.',
|
||||
v_details
|
||||
);
|
||||
end if;
|
||||
|
||||
if v_actions >= 2
|
||||
and v_duration_ms < (v_actions - 1) * 400 then
|
||||
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'melon',
|
||||
p_score,
|
||||
'high',
|
||||
'impossible_drop_rate',
|
||||
'Les pièces ont été lâchées plus vite que le délai minimal du jeu ne le permet.',
|
||||
v_details
|
||||
);
|
||||
end if;
|
||||
|
||||
if v_merges > greatest(v_actions - 1, 0) then
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'melon',
|
||||
p_score,
|
||||
'high',
|
||||
'impossible_merge_count',
|
||||
'Le nombre de fusions dépasse ce que le nombre de pièces lâchées permet.',
|
||||
v_details
|
||||
);
|
||||
end if;
|
||||
|
||||
if v_merges > 0
|
||||
and p_score > v_merges * greatest(v_max_tier * 2, 2) then
|
||||
|
||||
insert into public.game_cheat_flags
|
||||
(user_id, game, score, severity, trigger_code, reason, details)
|
||||
values (
|
||||
auth.uid(),
|
||||
'melon',
|
||||
p_score,
|
||||
'high',
|
||||
'impossible_score_from_merges',
|
||||
'Le score dépasse le maximum théorique permis par les fusions enregistrées.',
|
||||
v_details
|
||||
);
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
|
||||
select tribunal_date into v_tribunal_date
|
||||
from public.settings
|
||||
where id = true;
|
||||
|
||||
if v_tribunal_date is not null and now() >= v_tribunal_date then
|
||||
select * into v_row from public.melon_scores where user_id = auth.uid();
|
||||
select * into v_row
|
||||
from public.melon_scores
|
||||
where user_id = auth.uid();
|
||||
|
||||
return v_row;
|
||||
end if;
|
||||
|
||||
@@ -1657,12 +1910,15 @@ begin
|
||||
updated_at = now()
|
||||
where excluded.best_score > public.melon_scores.best_score;
|
||||
|
||||
select * into v_row from public.melon_scores where user_id = auth.uid();
|
||||
select * into v_row
|
||||
from public.melon_scores
|
||||
where user_id = auth.uid();
|
||||
|
||||
return v_row;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.submit_melon_score(integer) to authenticated;
|
||||
grant execute on function public.submit_melon_score(integer, jsonb) to authenticated;
|
||||
|
||||
do $$
|
||||
begin
|
||||
@@ -1738,3 +1994,43 @@ exception
|
||||
when others then
|
||||
raise notice 'pg_cron indisponible : active l''extension via le Dashboard Supabase (Database → Extensions → pg_cron), puis ré-exécute ce script pour planifier l''attribution automatique des gloires de La Corne d''Abondance.';
|
||||
end $$;
|
||||
|
||||
-- 17. Anti-triche par signalement (Alexandre) --------------------------------
|
||||
-- Remplace icarus_runs (ancienne section 11, supprimée) côté client : plutôt
|
||||
-- qu'un rejet serveur strict basé sur le temps réellement écoulé, cette
|
||||
-- table journalise les parties suspectes (durée/nombre d'actions/fusions
|
||||
-- incohérents avec le score, calculés côté client et envoyés dans p_run à
|
||||
-- submit_icarus_score/submit_melon_score, section 11/16) pour qu'un
|
||||
-- Archonte les vérifie à la main sur /admin (AdminCheatFlags) — les scores
|
||||
-- concernés restent acceptés, rien n'est jamais bloqué automatiquement.
|
||||
--
|
||||
-- Table, policy RLS et logique d'écriture (submit_icarus_score/
|
||||
-- submit_melon_score) toutes récupérées directement depuis Supabase
|
||||
-- (dump de schéma + pg_policies + pg_get_functiondef, 2026-08-25) — ce
|
||||
-- fichier est de nouveau la vraie source de vérité pour cette
|
||||
-- fonctionnalité, contrairement à l'état précédent.
|
||||
create table if not exists public.game_cheat_flags (
|
||||
id bigint generated always as identity primary key,
|
||||
user_id uuid not null references public.profiles (id),
|
||||
game text not null check (game in ('icarus', 'melon')),
|
||||
score integer not null,
|
||||
severity text not null check (severity in ('warning', 'high')),
|
||||
trigger_code text not null,
|
||||
reason text not null,
|
||||
details jsonb not null default '{}'::jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
alter table public.game_cheat_flags enable row level security;
|
||||
|
||||
-- Pas de grant insert/update/delete à authenticated : comme points_log/
|
||||
-- icarus_scores, seules les fonctions SECURITY DEFINER submit_icarus_score/
|
||||
-- submit_melon_score (section 11/16) peuvent y écrire.
|
||||
revoke insert, update, delete on public.game_cheat_flags from authenticated, anon;
|
||||
grant select on public.game_cheat_flags to authenticated;
|
||||
|
||||
drop policy if exists "game_cheat_flags readable by judges" on public.game_cheat_flags;
|
||||
create policy "game_cheat_flags readable by judges"
|
||||
on public.game_cheat_flags for select
|
||||
to authenticated
|
||||
using (exists (select 1 from public.profiles p where p.id = auth.uid() and p.role = 'judge'));
|
||||
|
||||
Reference in New Issue
Block a user