import { EntraSignInButton } from "@/components/auth/EntraSignInButton"; import { PasswortFormular } from "@/components/auth/PasswortFormular"; import { AppWortmarke } from "@/components/brand/Logo"; import { logout, signInWithEntra } from "@/actions/auth"; // The query string is attacker-controlled, so the login page renders a message // looked up by code rather than whatever text ?error= carries. Reflecting the // raw parameter let anyone put arbitrary wording ("Ihr Konto wurde gesperrt, // rufen Sie …") on the real, correctly-branded sign-in screen. Dasselbe gilt // für die Fehlertexte, die Entra im Rückweg mitschickt. const ERROR_MESSAGES = { no_hr_access: { title: "Kein HR-Zugriff", body: "Ihr Firmenkonto ist bekannt, aber nicht für die Personalverwaltung freigeschaltet. Bitte wenden Sie sich an eine:n bestehende:n HR-Benutzer:in.", }, sso_failed: { title: "Anmeldung fehlgeschlagen", body: "Die Anmeldung über das Firmenkonto konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut.", }, // Auth.js' eigener Code für eine gescheiterte Passwortanmeldung. Im Regelfall // kommt er hier nicht an — die Server Action fängt den Fehlschlag ab und // zeigt ihn am Formular. Ohne diesen Eintrag fiele er aber auf `sso_failed` // zurück, und dort stünde „über das Firmenkonto", was mit dem tatsächlichen // Weg nichts zu tun hätte. CredentialsSignin: { title: "Anmeldung fehlgeschlagen", body: "E-Mail-Adresse oder Passwort stimmt nicht.", }, } as const; type ErrorCode = keyof typeof ERROR_MESSAGES; type LoginPageProps = { searchParams: Promise<{ error?: string }>; }; export default async function LoginPage({ searchParams }: LoginPageProps) { const params = await searchParams; // Ein unbekannter Code wird nicht verschluckt, sondern auf die allgemeine // Meldung abgebildet: Auth.js schickt bei einem Fehlschlag seine eigenen // Codes („Configuration", „AccessDenied", „OAuthCallbackError" …), und ohne // diese Abbildung stünde man vor einer Anmeldeseite, die so tut, als wäre // nichts gewesen. Angezeigt wird trotzdem nur eigener Text — der Parameter // selbst kommt nie auf die Seite. const code = params.error ? Object.hasOwn(ERROR_MESSAGES, params.error) ? (params.error as ErrorCode) : "sso_failed" : null; const error = code ? ERROR_MESSAGES[code] : null; return ( // dvh statt vh: auf iOS zählt vh die Adressleiste mit, wodurch die Karte // im ersten Moment unter dem Faltenrand sitzt.
{/* Ohne Versatz: hier steht das Logo auf Weiss und bekommt nach §4.1 sein rosa Feld, dessen Kante mit der Ueberschrift darunter fluchten soll. */}

Anmelden

Der Zugang läuft über Ihr Firmenkonto. Ein eigenes Passwort gibt es nicht.

{error && (

{error.title}

{error.body}

{code === "no_hr_access" && (
)}
)}
{/* Der Passwortweg steht bewusst *unter* dem Firmenkonto und ohne eigene Überschrift: er ist die Ausnahme für die Testphase, nicht die zweite gleichwertige Möglichkeit. Wer ein Firmenkonto hat, soll oben klicken. */}
oder

Die Anmeldung allein erteilt keinen Zugriff. HR-Rechte vergibt die Personalabteilung — bis dahin bleiben alle Personaldaten verschlossen.

); } function BrandPanel() { return ( // Rosa und nicht Blau: Rosa ist die Farbe, in der die Marke auftritt, und // die Flaeche ist das Groesste, was diese Seite zu vergeben hat. Blau war // hier doppelt falsch — es drehte das Verhaeltnis der beiden Markenfarben // um, und das Logo bringt sein eigenes rosa Feld mit (§1.2), das auf Blau // wie ein aufgeklebtes Rechteck stand. Auf Rosa geht es in die Flaeche // ueber, und genau das meint §3.1 mit „in einem grossflaechigen rosa // Umfeld eingebettet". ); } // Die vier Quadrate, die hier als Platzhalter-Bildmarke standen, sind // entfallen: an ihrer Stelle steht jetzt das Manner-Logo // (components/brand/Logo.tsx).