Festes Enddatum des Passwortpfads zuruecknehmen
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
-- Das feste Enddatum des Passwortpfads fällt weg.
|
||||
--
|
||||
-- ═══ Was hier zurückgenommen wird ═══
|
||||
--
|
||||
-- 20260908120000 hat den Passwortpfad mit einem eingebauten Ende versehen:
|
||||
-- `gueltig_bis` mit Vorgabewert 08.10.2026 und einer Prüfbedingung, die keinen
|
||||
-- späteren Wert zuliess. Der Gedanke war, dass die Übergangslösung von selbst
|
||||
-- aufhört, ohne dass sich jemand erinnern muss.
|
||||
--
|
||||
-- Die Entscheidung ist bewusst revidiert: was hier entsteht, ist ein
|
||||
-- Testaufbau. Echte Personaldaten stehen nicht im System und sind auch nicht
|
||||
-- kurzfristig vorgesehen, die Anwendung soll später ohnehin nicht mehr aus dem
|
||||
-- Internet erreichbar sein, und ein Stichtag im Schema, der mitten in einen
|
||||
-- laufenden Test fällt, kostet mehr, als er in dieser Lage einbringt. Ein
|
||||
-- abgelaufener Zugang sieht für die Testerin aus wie ein Fehler in der
|
||||
-- Anwendung, nicht wie eine Schutzmassnahme.
|
||||
--
|
||||
-- ═══ Was bleibt ═══
|
||||
--
|
||||
-- Die **Spalte** bleibt, und die Prüfung in app_passwort_pruefen() bleibt Wort
|
||||
-- für Wort stehen. Nur steht in der Spalte künftig nichts:
|
||||
--
|
||||
-- if current_date > k.gueltig_bis then return null; end if;
|
||||
--
|
||||
-- Bei `gueltig_bis is null` ergibt der Vergleich „unbekannt", und ein `if` auf
|
||||
-- unbekannt verzweigt nicht — die Zeile ist also wirkungslos, ohne dass etwas
|
||||
-- daran geändert werden müsste. Sobald ein Ende gebraucht wird, genügt ein
|
||||
-- update; eine Migration braucht es dafür nicht mehr:
|
||||
--
|
||||
-- update app_passwoerter set gueltig_bis = current_date; -- ab morgen zu
|
||||
-- update app_passwoerter set gueltig_bis = null; -- wieder offen
|
||||
--
|
||||
-- Der Abschaltknopf bleibt damit erhalten, er löst nur nicht mehr von selbst
|
||||
-- aus. Dass der Passwortpfad eine Übergangslösung ist und vor echten Daten
|
||||
-- verschwindet, ist ab jetzt eine Entscheidung und keine Zusicherung des
|
||||
-- Schemas — so festgehalten in CLAUDE.md.
|
||||
--
|
||||
-- Die vier übrigen Schranken sind unberührt: Zwang zum Wechsel beim ersten
|
||||
-- Mal, sieben Tage Frist für einen nie benutzten Zugang, Sperre nach fünf
|
||||
-- Fehlversuchen, und die Freischaltung je Person.
|
||||
|
||||
-- ── 1. Die Prüfbedingung weg ──────────────────────────────────────────
|
||||
alter table app_passwoerter drop constraint if exists chk_passwort_pfad_endet;
|
||||
|
||||
-- ── 2. Die Spalte leer und leerbar ────────────────────────────────────
|
||||
--
|
||||
-- Erst den Vorgabewert weg, dann die Pflicht: in dieser Reihenfolge kann keine
|
||||
-- Zeile mehr entstehen, die den alten Stichtag trägt, während die Migration
|
||||
-- noch läuft.
|
||||
alter table app_passwoerter alter column gueltig_bis drop default;
|
||||
alter table app_passwoerter alter column gueltig_bis drop not null;
|
||||
|
||||
-- Der Bestand: heute sind das null Zeilen (es wurde noch kein Zugang mit
|
||||
-- Passwort angelegt), aber die Anweisung gehört trotzdem hierher — dieselbe
|
||||
-- Migration läuft später auf einer Datenbank, in der es welche gibt.
|
||||
update app_passwoerter set gueltig_bis = null where gueltig_bis is not null;
|
||||
|
||||
comment on table app_passwoerter is
|
||||
'Anmeldung mit Passwort — Übergangslösung für die Testphase. gueltig_bis ist leer und begrenzt damit nichts; ein Datum darin schaltet den Zugang ab dem Folgetag ab.';
|
||||
|
||||
comment on column app_passwoerter.gueltig_bis is
|
||||
'Letzter Tag, an dem dieses Passwort angenommen wird. Leer heisst unbefristet — der Abschaltknopf, nicht die Frist.';
|
||||
|
||||
-- ── 3. Den Kommentar in der Funktion nachziehen ───────────────────────
|
||||
--
|
||||
-- Im Rumpf von app_passwort_pruefen() steht an dieser Verzweigung „Läuft ohne
|
||||
-- Zutun ab". Das stimmt seit dieser Migration nicht mehr, und ein Kommentar,
|
||||
-- der eine Zusicherung beschreibt, die es nicht gibt, ist schlimmer als keiner
|
||||
-- — er wird beim nächsten Lesen für den Stand gehalten.
|
||||
--
|
||||
-- Ersetzt wird nur der Kommentartext, über pg_get_functiondef und replace().
|
||||
-- Vorbild ist 20260805110000: der Rumpf bleibt dabei nachweislich unangetastet,
|
||||
-- weil er nicht abgetippt wird. Findet sich der Text nicht, bricht die
|
||||
-- Migration ab, statt still nichts zu tun.
|
||||
do $$
|
||||
declare
|
||||
v_def text;
|
||||
v_alt text := 'Das Ende des Passwortpfads. Läuft ohne Zutun ab; siehe Kopf der Datei.';
|
||||
v_neu text := 'Das Ende des Passwortpfads. Greift nur, wenn gueltig_bis gesetzt ist — '
|
||||
|| 'bei null ist der Vergleich unbekannt und die Verzweigung wirkungslos '
|
||||
|| '(siehe 20260908140000).';
|
||||
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 = 'app_passwort_pruefen';
|
||||
|
||||
if v_def is null then
|
||||
raise exception 'app_passwort_pruefen() nicht gefunden — Abbruch.';
|
||||
end if;
|
||||
if position(v_alt in v_def) = 0 then
|
||||
raise exception 'Der erwartete Kommentar steht nicht in app_passwort_pruefen(). Der Rumpf wurde zwischenzeitlich geändert; bitte von Hand nachsehen.';
|
||||
end if;
|
||||
|
||||
execute replace(v_def, v_alt, v_neu);
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function app_passwort_pruefen(text, text) is
|
||||
'Prüft E-Mail und Passwort und liefert app_users.id oder null. Nennt nie einen Grund. gueltig_bis wird beachtet, ist aber standardmässig leer.';
|
||||
|
||||
-- ── 4. Gegenprobe ─────────────────────────────────────────────────────
|
||||
--
|
||||
-- Zwei Fragen, und die zweite ist die eigentliche: verhält sich die Anmeldung
|
||||
-- bei leerem gueltig_bis so, wie diese Migration behauptet? Der Vergleich mit
|
||||
-- null ist genau die Art Annahme, die stimmt, bis sie es nicht tut.
|
||||
do $$
|
||||
declare
|
||||
v_id uuid := gen_random_uuid();
|
||||
v_treffer uuid;
|
||||
begin
|
||||
-- 1. Die Prüfbedingung ist weg, und ein weit entferntes Datum geht durch.
|
||||
if exists (
|
||||
select 1 from pg_constraint
|
||||
where conrelid = 'app_passwoerter'::regclass and conname = 'chk_passwort_pfad_endet'
|
||||
) then
|
||||
raise exception 'chk_passwort_pfad_endet besteht weiterhin — Abbruch.';
|
||||
end if;
|
||||
|
||||
if exists (
|
||||
select 1 from information_schema.columns
|
||||
where table_schema = 'public' and table_name = 'app_passwoerter'
|
||||
and column_name = 'gueltig_bis'
|
||||
and (is_nullable = 'NO' or column_default is not null)
|
||||
) then
|
||||
raise exception 'gueltig_bis ist weiterhin pflichtig oder trägt einen Vorgabewert — Abbruch.';
|
||||
end if;
|
||||
|
||||
if exists (select 1 from app_passwoerter where gueltig_bis is not null) then
|
||||
raise exception 'Es stehen noch Zeilen mit einem Enddatum in app_passwoerter — Abbruch.';
|
||||
end if;
|
||||
|
||||
-- 2. Eine Probeperson mit Passwort. Der Hash mit gen_salt('bf', 4) statt 12:
|
||||
-- crypt() liest die Stärke aus dem gespeicherten Hash, die Prüfung läuft
|
||||
-- also genauso — nur schneller, und eine Migration soll nicht rechnen.
|
||||
insert into app_users (id, external_id, email, full_name)
|
||||
values (v_id, 'probe:' || v_id::text, 'probe-passwort@example.invalid', 'Probe');
|
||||
insert into app_passwoerter (user_id, hash, muss_wechseln, gueltig_bis)
|
||||
values (v_id, crypt('Nur-fuer-die-Probe1', gen_salt('bf', 4)), true, current_date - 1);
|
||||
|
||||
-- 2a. Mit abgelaufenem Datum: abgewiesen.
|
||||
v_treffer := app_passwort_pruefen('probe-passwort@example.invalid', 'Nur-fuer-die-Probe1');
|
||||
if v_treffer is not null then
|
||||
raise exception 'Ein abgelaufenes gueltig_bis wird nicht mehr beachtet — die Abschaltung wäre wirkungslos. Abbruch.';
|
||||
end if;
|
||||
|
||||
-- 2b. Ohne Datum: angenommen. Das ist der Zustand, den diese Migration
|
||||
-- herstellt, und die Aussage, auf die sich alles Weitere stützt.
|
||||
update app_passwoerter set gueltig_bis = null where user_id = v_id;
|
||||
v_treffer := app_passwort_pruefen('probe-passwort@example.invalid', 'Nur-fuer-die-Probe1');
|
||||
if v_treffer is distinct from v_id then
|
||||
raise exception 'Bei leerem gueltig_bis kommt keine Anmeldung zustande — Abbruch.';
|
||||
end if;
|
||||
|
||||
-- Aufräumen. Der erfolgreiche Versuch oben hat eine Protokollzeile
|
||||
-- geschrieben; sie muss mit weg, sonst scheitert das Löschen des Kontos am
|
||||
-- Fremdschlüssel — und stehenbleiben darf sie erst recht nicht, sie gehört
|
||||
-- zu einer Person, die es nie gab. Eng begrenzt auf diese eine Kennung.
|
||||
delete from audit_log where actor_user_id = v_id;
|
||||
delete from app_passwoerter where user_id = v_id;
|
||||
delete from app_users where id = v_id;
|
||||
end;
|
||||
$$;
|
||||
Reference in New Issue
Block a user