diff --git a/pkgs/core/schemas/0030_utilities.sql b/pkgs/core/schemas/0030_utilities.sql index 04edbbcc1..2a72c5dab 100644 --- a/pkgs/core/schemas/0030_utilities.sql +++ b/pkgs/core/schemas/0030_utilities.sql @@ -1,5 +1,22 @@ -- Utility functions that don't depend on other entities +-- Detects if running in local Supabase CLI environment +-- by checking if JWT secret matches the known hardcoded local value. +-- Returns true only for exact match; defaults to false (production-safe). +create or replace function pgflow.is_local() +returns boolean +language sql +stable +parallel safe +set search_path = '' +as $$ + select coalesce( + current_setting('app.settings.jwt_secret', true) + = 'super-secret-jwt-token-with-at-least-32-characters-long', + false + ) +$$; + create or replace function pgflow.is_valid_slug( slug text ) diff --git a/pkgs/core/src/database-types.ts b/pkgs/core/src/database-types.ts index ea7af71c9..028d23bf5 100644 --- a/pkgs/core/src/database-types.ts +++ b/pkgs/core/src/database-types.ts @@ -483,6 +483,7 @@ export type Database = { } } get_run_with_states: { Args: { run_id: string }; Returns: Json } + is_local: { Args: never; Returns: boolean } is_valid_slug: { Args: { slug: string }; Returns: boolean } maybe_complete_run: { Args: { run_id: string }; Returns: undefined } poll_for_tasks: { diff --git a/pkgs/core/supabase/migrations/20251204115929_pgflow_temp_is_local.sql b/pkgs/core/supabase/migrations/20251204115929_pgflow_temp_is_local.sql new file mode 100644 index 000000000..d9585a323 --- /dev/null +++ b/pkgs/core/supabase/migrations/20251204115929_pgflow_temp_is_local.sql @@ -0,0 +1,8 @@ +-- Create "is_local" function +CREATE FUNCTION "pgflow"."is_local" () RETURNS boolean LANGUAGE sql STABLE PARALLEL SAFE SET "search_path" = '' AS $$ +select coalesce( + current_setting('app.settings.jwt_secret', true) + = 'super-secret-jwt-token-with-at-least-32-characters-long', + false + ) +$$; diff --git a/pkgs/core/supabase/migrations/atlas.sum b/pkgs/core/supabase/migrations/atlas.sum index b4de0441a..2467005c7 100644 --- a/pkgs/core/supabase/migrations/atlas.sum +++ b/pkgs/core/supabase/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:zJB8nH4leuvGPgSDItehBjrRGuE1kgEV5ucwUlzAknE= +h1:+t6j1CJcFBfV6SRbAz/7/mVgZeS9vmzWWC+b1/Mp3GA= 20250429164909_pgflow_initial.sql h1:I3n/tQIg5Q5nLg7RDoU3BzqHvFVjmumQxVNbXTPG15s= 20250517072017_pgflow_fix_poll_for_tasks_to_use_separate_statement_for_polling.sql h1:wTuXuwMxVniCr3ONCpodpVWJcHktoQZIbqMZ3sUHKMY= 20250609105135_pgflow_add_start_tasks_and_started_status.sql h1:ggGanW4Wyt8Kv6TWjnZ00/qVb3sm+/eFVDjGfT8qyPg= @@ -12,3 +12,4 @@ h1:zJB8nH4leuvGPgSDItehBjrRGuE1kgEV5ucwUlzAknE= 20251103222045_pgflow_fix_broadcast_order_and_timestamp_handling.sql h1:K/XnZpOmxfelsaNoJbR5HxhBrs/oW4aYja222h5cps4= 20251104080523_pgflow_upgrade_pgmq_1_5_1.sql h1:Fw7zpMWnjhAHQ0qBJAprAvGl7dJMd8ExNHg8aKvkzTg= 20251130000000_pgflow_auto_compilation.sql h1:qs+3qq1Vsyo0ETzbxDnmkVtSUa6XHkd/K9wF/3W46jM= +20251204115929_pgflow_temp_is_local.sql h1:arP+PC2OYI9ktAFx0+G9/w8zsaq/AbFWjLgK6YDujvQ= diff --git a/pkgs/core/supabase/tests/is_local/basic.test.sql b/pkgs/core/supabase/tests/is_local/basic.test.sql new file mode 100644 index 000000000..ab0372a4d --- /dev/null +++ b/pkgs/core/supabase/tests/is_local/basic.test.sql @@ -0,0 +1,29 @@ +begin; +select plan(3); +select pgflow_tests.reset_db(); + +-- TEST: Returns true when jwt_secret matches known local Supabase value +-- The local Supabase CLI always uses this exact hardcoded JWT secret +select set_config('app.settings.jwt_secret', 'super-secret-jwt-token-with-at-least-32-characters-long', true); +select ok( + pgflow.is_local(), + 'is_local() returns true when jwt_secret matches local Supabase value' +); + +-- TEST: Returns false when jwt_secret is empty/missing (simulates hosted Supabase after Nov 2024) +-- Note: set_config with NULL is ignored, so we use empty string to simulate missing value +select set_config('app.settings.jwt_secret', '', true); +select ok( + not pgflow.is_local(), + 'is_local() returns false when jwt_secret is empty/missing' +); + +-- TEST: Returns false when jwt_secret is a different value (self-hosted or custom) +select set_config('app.settings.jwt_secret', 'some-other-custom-jwt-secret-value', true); +select ok( + not pgflow.is_local(), + 'is_local() returns false when jwt_secret is a different value' +); + +select finish(); +rollback;