-- auth.uid() aus den Geschäftsfunktionen entfernen. -- -- Die Anwendung verbindet sich als eigene Rolle ohne BYPASSRLS. Diese Rolle -- hat kein Recht auf das Schema `auth` — und jede Funktion, die dort etwas -- aufruft, scheitert mit „permission denied for schema auth". -- -- Das trifft **jede schreibende Aktion**: Einstellen, Versetzen, Befördern, -- Austritt, Notizen, Planstellen. Aufgefallen ist es beim Nachstellen einer -- Neueinstellung; zuvor lief alles über eine Rolle, die das Schema sehen -- durfte. -- -- app_current_user_id() liefert dasselbe und funktioniert auf jedem -- PostgreSQL — es liest den transaktionslokalen Sitzungskontext, den lib/db -- setzt. create or replace function app_uid_ersetzen(p_funktion text) returns void language plpgsql set search_path = public, pg_temp as $$ declare v_def text; begin select pg_get_functiondef(p.oid) into v_def from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = p_funktion limit 1; if v_def is null then raise exception 'Funktion %() nicht gefunden.', p_funktion; end if; if position('auth.uid()' in v_def) = 0 then raise notice '%(): bereits umgestellt.', p_funktion; return; end if; execute replace(v_def, 'auth.uid()', 'app_current_user_id()'); raise notice '%(): umgestellt.', p_funktion; end; $$; select app_uid_ersetzen('add_employee_dependent'); select app_uid_ersetzen('add_employee_note'); select app_uid_ersetzen('adjust_karenz_return'); select app_uid_ersetzen('complete_employee_note'); select app_uid_ersetzen('create_position'); select app_uid_ersetzen('delete_employee_dependent'); select app_uid_ersetzen('delete_position'); select app_uid_ersetzen('hire_employee'); select app_uid_ersetzen('promote_employee'); select app_uid_ersetzen('record_karenz_return'); select app_uid_ersetzen('rehire_employee'); select app_uid_ersetzen('start_karenz'); select app_uid_ersetzen('terminate_employee'); select app_uid_ersetzen('transfer_employee'); drop function app_uid_ersetzen(text); -- ═══ Den Rückfall selbst absichern ═══════════════════════════════ -- app_current_user_id() behält seinen Übergangszweig auf auth.uid(), fängt -- aber bisher nur „Funktion fehlt". Für eine Rolle ohne Recht auf das Schema -- kommt stattdessen insufficient_privilege — und die Funktion warf, statt -- null zu liefern. Das fiel nicht auf, weil der Zweig nur ohne -- Sitzungskontext erreicht wird; genau dann soll aber „niemand angemeldet" -- herauskommen und kein Fehler. create or replace function app_current_user_id() returns uuid language plpgsql stable security definer set search_path = public, pg_temp as $$ declare v_id uuid; begin v_id := nullif(current_setting('app.user_id', true), '')::uuid; if v_id is not null then return v_id; end if; begin execute 'select auth.uid()' into v_id; exception when undefined_function or invalid_schema_name or undefined_table or insufficient_privilege then v_id := null; end; return v_id; end; $$; -- ═══ Gegenprobe ══════════════════════════════════════════════════ do $$ declare r record; begin for r in select p.proname from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.prokind = 'f' and p.proname <> 'app_current_user_id' and pg_get_functiondef(p.oid) like '%auth.uid()%' loop raise exception 'Funktion %() ruft weiterhin auth.uid() auf.', r.proname; end loop; -- Ohne Kontext muss die Kennung null sein und darf nicht werfen. perform set_config('app.user_id', '', true); if app_current_user_id() is not null then raise exception 'app_current_user_id() liefert ohne Kontext eine Kennung.'; end if; end; $$;