Trust external host for Auth.js, provide missing frontend auth env/secrets, and submit a proper CSRF-backed sign-in POST so browser login reaches Keycloak reliably.
90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, Suspense } from 'react';
|
|
import { signOut, useSession } from 'next-auth/react';
|
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
function LoginContent() {
|
|
const { status } = useSession();
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const hasError = searchParams.get('error') || searchParams.get('callbackUrl');
|
|
|
|
useEffect(() => {
|
|
if (status === 'authenticated') {
|
|
router.push('/dashboard');
|
|
}
|
|
}, [status, router]);
|
|
|
|
const handleSignIn = async () => {
|
|
const csrfResponse = await fetch('/api/auth/csrf');
|
|
const csrfPayload = await csrfResponse.json() as { csrfToken?: string };
|
|
|
|
if (!csrfPayload.csrfToken) {
|
|
window.location.href = '/api/auth/signin?callbackUrl=%2Fdashboard';
|
|
return;
|
|
}
|
|
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = '/api/auth/signin/keycloak';
|
|
|
|
const csrfInput = document.createElement('input');
|
|
csrfInput.type = 'hidden';
|
|
csrfInput.name = 'csrfToken';
|
|
csrfInput.value = csrfPayload.csrfToken;
|
|
form.appendChild(csrfInput);
|
|
|
|
const callbackInput = document.createElement('input');
|
|
callbackInput.type = 'hidden';
|
|
callbackInput.name = 'callbackUrl';
|
|
callbackInput.value = `${window.location.origin}/dashboard`;
|
|
form.appendChild(callbackInput);
|
|
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
};
|
|
|
|
const handleSwitchAccount = () => {
|
|
const keycloakLogoutUrl = `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER || 'http://localhost:8080/realms/workclub'}/protocol/openid-connect/logout?redirect_uri=${encodeURIComponent(window.location.origin + '/login')}`;
|
|
signOut({ redirect: false }).then(() => {
|
|
window.location.href = keycloakLogoutUrl;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Card className="w-96">
|
|
<CardHeader>
|
|
<CardTitle className="text-2xl text-center">WorkClub Manager</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<Button onClick={handleSignIn} className="w-full">
|
|
Sign in with Keycloak
|
|
</Button>
|
|
<Button variant="outline" onClick={handleSwitchAccount} className="w-full">
|
|
Use different credentials
|
|
</Button>
|
|
</CardContent>
|
|
{hasError && (
|
|
<CardFooter>
|
|
<p className="text-sm text-muted-foreground text-center w-full">
|
|
Having trouble? Try "Use different credentials" to clear your session.
|
|
</p>
|
|
</CardFooter>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default function LoginPage() {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen bg-gray-50">
|
|
<Suspense fallback={<Card className="w-96 p-6 text-center">Loading...</Card>}>
|
|
<LoginContent />
|
|
</Suspense>
|
|
</div>
|
|
);
|
|
}
|