// Hängt eine bestehende profiles-Zeile auf die Entra-Identität derselben // Person um. // // Run with: node --env-file=.env.local supabase/relink-profile.ts [--apply] // // Ein Konto mit Passwort-Anmeldung und das Entra-Konto derselben Person sind // für Supabase zwei Benutzer mit verschiedenen IDs. Die profiles-Zeile hängt an // der alten; nach der ersten Anmeldung über Entra zeigt sie ins Leere und die // Person ist ausgesperrt — mit „Kein HR-Zugriff", obwohl sie HR ist. // // Ohne --apply wird nur angezeigt, was passieren würde. import { createClient } from "@supabase/supabase-js"; const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL; const SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY; if (!SUPABASE_URL || !SERVICE_ROLE_KEY) { throw new Error("Missing NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY in the environment"); } const email = process.argv[2]; const apply = process.argv.includes("--apply"); if (!email) { console.error("Aufruf: node --env-file=.env.local supabase/relink-profile.ts [--apply]"); process.exit(1); } const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY, { auth: { autoRefreshToken: false, persistSession: false }, }); // Die Fremdschlüssel auf auth.users(id). Sie zeigen sonst weiter auf das alte // Konto, und in der Historie stünde eine Kennung ohne Konto dahinter. const REFERENCES: { table: string; column: string }[] = [ { table: "audit_log", column: "actor_user_id" }, { table: "employee_notes", column: "author_user_id" }, { table: "employee_notes", column: "done_by" }, { table: "hire_drafts", column: "created_by" }, { table: "saved_reports", column: "created_by" }, { table: "pending_org_changes", column: "created_by" }, { table: "profiles", column: "created_by" }, ]; const { data: userList, error } = await supabase.auth.admin.listUsers({ perPage: 1000 }); if (error) throw new Error(error.message); const accounts = userList.users.filter((u) => u.email?.toLowerCase() === email.toLowerCase()); const entra = accounts.find((u) => u.identities?.some((i) => i.provider === "azure")); const alt = accounts.find((u) => u.id !== entra?.id); if (!entra) { console.error(`Kein Entra-Konto zu ${email}. Bitte zuerst einmal über „Mit Firmenkonto anmelden" anmelden.`); process.exit(1); } if (!alt) { console.log(`Zu ${email} gibt es nur das Entra-Konto (${entra.id}) — nichts umzuhängen.`); process.exit(0); } const { data: profile } = await supabase.from("profiles").select("*").eq("id", alt.id).maybeSingle(); if (!profile) { console.error(`Das alte Konto ${alt.id} hat keine profiles-Zeile. Nichts umzuhängen.`); process.exit(1); } console.log(`alt: ${alt.id} (${alt.identities?.map((i) => i.provider).join(", ")})`); console.log(`neu: ${entra.id} (azure)`); console.log(`Rolle: ${profile.role}, aktiv: ${profile.is_active}`); if (!apply) { console.log("\nTrockenlauf. Mit --apply ausführen."); process.exit(0); } // Neue Zeile zuerst: profiles.id verweist auf auth.users(id), und die alte // Zeile fällt erst, wenn die neue steht — sonst gibt es einen Moment ohne // HR-Konto, und niemand könnte eines mehr freischalten. const { error: insertError } = await supabase.from("profiles").insert({ ...profile, id: entra.id, email: entra.email ?? profile.email, }); if (insertError) throw new Error(`profiles-Zeile anlegen fehlgeschlagen: ${insertError.message}`); console.log("profiles-Zeile für das Entra-Konto angelegt."); for (const ref of REFERENCES) { const { error: updateError, count } = await supabase .from(ref.table) .update({ [ref.column]: entra.id }, { count: "exact" }) .eq(ref.column, alt.id); if (updateError) { console.warn(` ${ref.table}.${ref.column}: ${updateError.message}`); continue; } console.log(` ${ref.table}.${ref.column}: ${count ?? 0} Zeile(n) umgehängt`); } const { error: deleteProfileError } = await supabase.from("profiles").delete().eq("id", alt.id); if (deleteProfileError) throw new Error(`alte profiles-Zeile löschen fehlgeschlagen: ${deleteProfileError.message}`); const { error: deleteUserError } = await supabase.auth.admin.deleteUser(alt.id); if (deleteUserError) { // Kein Abbruch: der Zugriff hängt an profiles, und die ist bereits // umgehängt. Das alte Konto ist damit wirkungslos, nur nicht aufgeräumt. console.warn(`Altes Konto konnte nicht gelöscht werden: ${deleteUserError.message}`); } else { console.log("Altes Konto gelöscht."); } console.log("\nFertig. Die Anmeldung läuft jetzt über das Firmenkonto.");