add keycloak
This commit is contained in:
parent
ad53609099
commit
d3e4882d9e
@ -1,20 +1,57 @@
|
||||
// Datei: pages/api/auth/[...nextauth].ts
|
||||
|
||||
import NextAuth, { AuthOptions } from "next-auth"
|
||||
import CredentialsProvider from "next-auth/providers/credentials"
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
import prisma from "../../../lib/prisma" // Pfad zu deinem Prisma Client Singleton anpassen
|
||||
import bcrypt from "bcryptjs"
|
||||
import NextAuth, {AuthOptions} from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import KeycloakProvider, { KeycloakProfile } from "next-auth/providers/keycloak";
|
||||
// Falls 'next-auth/jwt' nicht funktioniert, versuche 'next-auth/core/types'
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
||||
import prisma from "../../../lib/prisma";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
// User-Typ aus deinen globalen NextAuth-Typen (next-auth.d.ts)
|
||||
// wird für den Rückgabetyp des profile-Callbacks benötigt.
|
||||
import type { User } from "next-auth";
|
||||
|
||||
// authOptions explizit als AuthOptions typisieren
|
||||
export const authOptions: AuthOptions = {
|
||||
adapter: PrismaAdapter(prisma), // Der Adapter wird weiterhin für User/Account Management genutzt
|
||||
adapter: PrismaAdapter(prisma),
|
||||
session: {
|
||||
strategy: "jwt", // Beibehaltung der JWT-Strategie
|
||||
strategy: "jwt",
|
||||
},
|
||||
providers: [
|
||||
KeycloakProvider({
|
||||
clientId: process.env.KEYCLOAK_CLIENT_ID as string,
|
||||
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET as string,
|
||||
issuer: process.env.KEYCLOAK_ISSUER as string,
|
||||
profile(profile: KeycloakProfile): User | Promise<User> {
|
||||
// profile: Das von Keycloak zurückgegebene Benutzerprofil.
|
||||
// tokens: Enthält access_token, id_token etc. und entspricht dem TokenSet-Typ.
|
||||
|
||||
let userRole = 'user'; // Standardrolle
|
||||
// Beispiel für Rollenextraktion (passe dies an deine Keycloak-Konfiguration an):
|
||||
// Oft sind Rollen in 'realm_access.roles' oder 'resource_access.[client-id].roles'
|
||||
// im dekodierten Access Token oder ID Token.
|
||||
// Oder Keycloak Mappers können Rollen direkt ins Profil-Objekt legen.
|
||||
if (profile.realm_access?.roles && Array.isArray(profile.realm_access.roles) && profile.realm_access.roles.includes('admin')) {
|
||||
userRole = 'admin';
|
||||
} else if (profile.roles && Array.isArray(profile.roles) && profile.roles.includes('admin')) { // Falls 'roles' direkt im Profil ist
|
||||
userRole = 'admin';
|
||||
}
|
||||
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name || profile.preferred_username,
|
||||
email: profile.email,
|
||||
image: profile.picture,
|
||||
role: userRole,
|
||||
isApproved: true,
|
||||
// emailVerified wird vom PrismaAdapter erwartet (kann null sein).
|
||||
// Mappe es, wenn Keycloak 'email_verified: true' sendet.
|
||||
...(profile.email_verified && { emailVerified: new Date() }),
|
||||
};
|
||||
},
|
||||
}),
|
||||
CredentialsProvider({
|
||||
name: "Credentials",
|
||||
name: "Lokale Konten",
|
||||
credentials: {
|
||||
email: { label: "E-Mail", type: "email", placeholder: "user@example.com" },
|
||||
password: { label: "Passwort", type: "password" }
|
||||
@ -23,80 +60,53 @@ export const authOptions: AuthOptions = {
|
||||
if (!credentials?.email || !credentials.password) {
|
||||
throw new Error("E-Mail und Passwort sind erforderlich.");
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email.toLowerCase() } // E-Mail normalisieren für den Abgleich
|
||||
where: { email: credentials.email.toLowerCase() }
|
||||
});
|
||||
|
||||
if (!user || !user.password) {
|
||||
// Benutzer nicht gefunden oder hat kein Passwort gesetzt (z.B. nur OAuth)
|
||||
throw new Error("Benutzer nicht gefunden oder Passwort nicht gesetzt.");
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
credentials.password,
|
||||
user.password
|
||||
);
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(credentials.password, user.password);
|
||||
if (!isPasswordValid) {
|
||||
throw new Error("E-Mail oder Passwort ist falsch.");
|
||||
}
|
||||
|
||||
// NEU: Überprüfung des Freigabestatus
|
||||
if (!user.isApproved) {
|
||||
throw new Error("Dein Konto wurde noch nicht von einem Administrator freigegeben.");
|
||||
}
|
||||
|
||||
// Stelle sicher, dass das zurückgegebene User-Objekt mit deiner
|
||||
// erweiterten User-Definition in next-auth.d.ts übereinstimmt.
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
image: user.image,
|
||||
isApproved: user.isApproved, // isApproved zum User-Objekt hinzufügen, das an jwt Callback geht
|
||||
isApproved: user.isApproved,
|
||||
};
|
||||
}
|
||||
}),
|
||||
// Füge hier weitere Provider hinzu, z.B. Google:
|
||||
// GoogleProvider({
|
||||
// clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
// clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
// }),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
// Das 'user'-Objekt ist hier das, was vom 'authorize'-Callback zurückgegeben wird.
|
||||
// Es ist nur beim ersten Aufruf nach der Anmeldung (oder Token-Erstellung) vorhanden.
|
||||
if (user) {
|
||||
async jwt({ token, user, account }) {
|
||||
if (account && user) {
|
||||
token.id = user.id;
|
||||
token.role = user.role;
|
||||
token.isApproved = user.isApproved; // isApproved zum JWT hinzufügen
|
||||
// Standard-Claims wie name, email, picture werden oft automatisch von NextAuth hinzugefügt,
|
||||
// wenn sie im User-Objekt vorhanden sind.
|
||||
token.isApproved = user.isApproved;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
// Das 'token'-Argument enthält die entschlüsselten Daten aus dem JWT.
|
||||
if (token && session.user) {
|
||||
session.user.id = token.id as string;
|
||||
session.user.role = token.role as string | undefined; // oder string | null, je nach Definition
|
||||
session.user.isApproved = token.isApproved as boolean | undefined; // isApproved zur Session hinzufügen
|
||||
// session.user.name = token.name; // Wird oft schon durch DefaultSession abgedeckt
|
||||
// session.user.email = token.email; // Wird oft schon durch DefaultSession abgedeckt
|
||||
// session.user.image = token.picture; // Wird oft schon durch DefaultSession abgedeckt (picture ist der JWT Claim für image)
|
||||
session.user.role = token.role as string | undefined;
|
||||
session.user.isApproved = token.isApproved as boolean | undefined;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: '/auth/signin',
|
||||
error: '/auth/error', // Deine benutzerdefinierte Fehlerseite
|
||||
error: '/auth/error',
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET, // Sollte in .env.local definiert sein
|
||||
// debug: process.env.NODE_ENV === 'development',
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
};
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
// Datei: pages/auth/signin.tsx
|
||||
import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next'; // GetServerSidePropsContext hinzugefügt
|
||||
import { getProviders, signIn, useSession, getCsrfToken, getSession } from 'next-auth/react'; // getSession hinzugefügt
|
||||
import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
|
||||
import { getProviders, signIn, useSession, getCsrfToken, getSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { ClientSafeProvider, LiteralUnion } from 'next-auth/react';
|
||||
import type { ParsedUrlQuery } from 'querystring'; // Für GetServerSidePropsContext
|
||||
import type { ParsedUrlQuery } from 'querystring';
|
||||
import type { PreviewData } from 'next/dist/types';
|
||||
import {BuiltInProviderType} from "next-auth/providers/index";
|
||||
import Link from "next/link"; // Für GetServerSidePropsContext
|
||||
import Link from "next/link";
|
||||
|
||||
interface SignInProps {
|
||||
providers: Record<LiteralUnion<BuiltInProviderType, string>, ClientSafeProvider> | null;
|
||||
csrfToken: string | undefined;
|
||||
csrfToken: string | undefined; // csrfToken ist für Credentials-Formular-POSTs relevant, weniger für OAuth/OIDC Klicks
|
||||
}
|
||||
|
||||
const SignInPage: NextPage<SignInProps> = ({ providers}) => {
|
||||
@ -22,10 +22,10 @@ const SignInPage: NextPage<SignInProps> = ({ providers}) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Fehler aus der URL-Query extrahieren (von NextAuth gesetzt)
|
||||
useEffect(() => {
|
||||
if (router.query.error) {
|
||||
switch (router.query.error) {
|
||||
if (router.query.error && !error) { // Nur setzen, wenn noch kein Fehler manuell gesetzt wurde
|
||||
const errorKey = router.query.error as string;
|
||||
switch (errorKey) {
|
||||
case 'CredentialsSignin':
|
||||
setError('Anmeldung fehlgeschlagen. Bitte überprüfe deine E-Mail und dein Passwort.');
|
||||
break;
|
||||
@ -34,24 +34,24 @@ const SignInPage: NextPage<SignInProps> = ({ providers}) => {
|
||||
case 'OAuthCreateAccount':
|
||||
case 'EmailCreateAccount':
|
||||
case 'Callback':
|
||||
setError('Fehler bei der OAuth-Anmeldung. Bitte versuche es erneut.');
|
||||
case 'AccountNotLinked': // Häufiger Fehler bei OIDC, wenn E-Mail bereits existiert
|
||||
setError(`Fehler bei der externen Anmeldung (${errorKey}). Existiert bereits ein Konto mit dieser E-Mail?`);
|
||||
break;
|
||||
case 'EmailSignin':
|
||||
setError('Fehler beim Senden der Anmelde-E-Mail.');
|
||||
break;
|
||||
default:
|
||||
setError(`Ein unbekannter Anmeldefehler ist aufgetreten: ${router.query.error}`);
|
||||
setError(`Ein Anmeldefehler ist aufgetreten: ${errorKey}`);
|
||||
break;
|
||||
}
|
||||
// Entferne den Fehler aus der URL, um ihn nicht erneut anzuzeigen, wenn der Benutzer interagiert
|
||||
// Entferne den Fehler aus der URL, um ihn nicht erneut anzuzeigen
|
||||
const newPath = router.pathname;
|
||||
const queryWithoutError = { ...router.query };
|
||||
delete queryWithoutError.error;
|
||||
router.replace({ pathname: newPath, query: queryWithoutError }, undefined, { shallow: true });
|
||||
}
|
||||
}, [router.query.error, router]);
|
||||
}, [router.query, error, router]); // error zur Dependency-Liste hinzugefügt
|
||||
|
||||
// Weiterleitung, wenn bereits authentifiziert
|
||||
useEffect(() => {
|
||||
if (status === 'authenticated') {
|
||||
const callbackUrl = router.query.callbackUrl as string || '/dashboard';
|
||||
@ -59,124 +59,63 @@ const SignInPage: NextPage<SignInProps> = ({ providers}) => {
|
||||
}
|
||||
}, [status, router]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const handleCredentialsSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError(null); // Reset error before new attempt
|
||||
setError(null);
|
||||
|
||||
const result = await signIn('credentials', {
|
||||
redirect: false, // Wir behandeln die Weiterleitung manuell oder warten auf den useEffect
|
||||
redirect: false,
|
||||
email: email,
|
||||
password: password,
|
||||
// callbackUrl: router.query.callbackUrl as string || '/dashboard' // Optional: hier schon setzen
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
if (result?.error) {
|
||||
// Der Fehler wird auch über router.query.error gesetzt, aber wir können ihn hier direkt setzen
|
||||
switch (result.error) {
|
||||
case 'CredentialsSignin':
|
||||
setError('Anmeldung fehlgeschlagen. Bitte überprüfe deine E-Mail und dein Passwort.');
|
||||
break;
|
||||
default:
|
||||
setError(result.error || 'Ein unbekannter Fehler ist aufgetreten.');
|
||||
// Fehlerbehandlung wie im useEffect, aber spezifischer für Credentials
|
||||
if (result.error === "CredentialsSignin" || result.error.includes("Konto wurde noch nicht") || result.error.includes("E-Mail oder Passwort ist falsch")) {
|
||||
setError(result.error); // Direkte Fehlermeldung von authorize
|
||||
} else {
|
||||
setError('Anmeldung fehlgeschlagen. Bitte überprüfe deine Eingaben.');
|
||||
}
|
||||
} else if (result?.ok && !result.error) {
|
||||
// Erfolgreich, Weiterleitung wird durch useEffect oben oder durch callbackUrl in signIn gehandhabt
|
||||
// router.push(router.query.callbackUrl as string || '/dashboard');
|
||||
} else if (result?.ok) {
|
||||
// Erfolg, Weiterleitung wird durch useEffect oben gehandhabt
|
||||
}
|
||||
};
|
||||
|
||||
if (status === 'loading' || status === 'authenticated') {
|
||||
// Zeige Ladezustand, während auf Session-Status oder Weiterleitung gewartet wird
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
||||
<p className="text-lg text-gray-600">Laden...</p>
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900">
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">Laden...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{/* Optional: Logo hier einfügen */}
|
||||
{/* <img className="mx-auto h-12 w-auto" src="/logo.svg" alt="Logo" /> */}
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
Anmelden bei deiner Strichliste
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100">
|
||||
Anmelden
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow-xl ring-1 ring-gray-900/10 sm:rounded-lg sm:px-10">
|
||||
<div className="bg-white dark:bg-slate-800 py-8 px-4 shadow-xl ring-1 ring-gray-900/10 dark:ring-gray-700 sm:rounded-lg sm:px-10">
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 text-red-700 border border-red-300 rounded-md text-sm">
|
||||
<div className="mb-4 p-3 bg-red-100 text-red-700 border border-red-300 rounded-md text-sm dark:bg-red-900 dark:text-red-200 dark:border-red-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* CSRF Token ist nicht explizit für das manuelle signIn mit Credentials nötig,
|
||||
aber NextAuth fügt es hinzu, wenn man die Standard-Submit-Action verwendet.
|
||||
Da wir signIn('credentials', ...) verwenden, ist es implizit gehandhabt.
|
||||
Wenn du action="/api/auth/callback/credentials" verwenden würdest, wäre es nötig:
|
||||
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
|
||||
*/}
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="appearance-none text-black block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
placeholder="deine@email.de"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
||||
Passwort
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="appearance-none text-black block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Anmelden...' : 'Anmelden'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* Buttons für andere Provider (z.B. Google, GitHub), falls konfiguriert */}
|
||||
{/* Keycloak Provider Button (und andere OAuth/OIDC Provider) */}
|
||||
{providers && Object.values(providers).map((provider) => {
|
||||
if (provider.id === 'credentials') return null; // Credentials Provider ist das Formular oben
|
||||
if (provider.id === 'credentials') return null; // Credentials Provider wird unten als Formular behandelt
|
||||
return (
|
||||
<div key={provider.name} className="mt-3">
|
||||
<div key={provider.name} className="mb-3">
|
||||
<button
|
||||
onClick={() => signIn(provider.id, { callbackUrl: router.query.callbackUrl as string || '/dashboard' })}
|
||||
className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-slate-700 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-slate-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
{/* Hier könntest du Provider-spezifische Icons hinzufügen */}
|
||||
Mit {provider.name} anmelden
|
||||
@ -184,10 +123,79 @@ const SignInPage: NextPage<SignInProps> = ({ providers}) => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Trennlinie, wenn beide Arten von Providern vorhanden sind */}
|
||||
{providers && Object.values(providers).some(p => p.id !== 'credentials') && Object.values(providers).some(p => p.id === 'credentials') && (
|
||||
<div className="my-6">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300 dark:border-gray-600" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white dark:bg-slate-800 text-gray-500 dark:text-gray-400">
|
||||
Oder mit lokalen Konten
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Credentials Provider Formular */}
|
||||
{providers?.credentials && (
|
||||
<form onSubmit={handleCredentialsSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 text-gray-900 dark:text-gray-100"
|
||||
placeholder="deine@email.de"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Passwort
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 text-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 dark:bg-indigo-500 dark:hover:bg-indigo-600"
|
||||
>
|
||||
{isLoading ? 'Anmelden...' : 'Mit lokalen Konten anmelden'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<div className="mt-6 text-center text-sm">
|
||||
<p className="text-gray-600">
|
||||
Noch kein Konto?{' '}
|
||||
<Link href="/auth/signup" className="font-medium text-indigo-600 hover:text-indigo-500">
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Noch kein lokales Konto?{' '}
|
||||
<Link href="/auth/signup" className="font-medium text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300">
|
||||
Registrieren
|
||||
</Link>
|
||||
</p>
|
||||
@ -201,9 +209,7 @@ const SignInPage: NextPage<SignInProps> = ({ providers}) => {
|
||||
export const getServerSideProps: GetServerSideProps<SignInProps, ParsedUrlQuery, PreviewData> = async (
|
||||
context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>
|
||||
) => {
|
||||
// Wenn der Benutzer bereits angemeldet ist, leite ihn direkt zum Dashboard weiter.
|
||||
// Dies verhindert ein kurzes Aufblitzen der Anmeldeseite.
|
||||
const session = await getSession(context); // getSession importiert
|
||||
const session = await getSession(context);
|
||||
if (session) {
|
||||
return {
|
||||
redirect: {
|
||||
@ -217,10 +223,11 @@ export const getServerSideProps: GetServerSideProps<SignInProps, ParsedUrlQuery,
|
||||
const csrfToken = await getCsrfToken(context);
|
||||
return {
|
||||
props: {
|
||||
providers: providers, // Korrigiert: providers direkt zuweisen (kann null sein)
|
||||
providers: providers,
|
||||
csrfToken: csrfToken ?? undefined,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default SignInPage;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user