forked from baron/baron-sso
Details: - Backend: Extract Kratos session cookies and propagate via SetCookies in AuthInfo. - Backend: Include sessionJwt and token during OIDC flows in PasswordLogin. - UserFront: Add _silentSessionRecovery in main.dart to recover session via cookies if localStorage token is missing. - UserFront: Update AuthProxyService, AuthTokenStore, AuthNotifier to support silent recovery and immediate local state update before redirect. - AdminFront/DevFront: Fix OIDC authority to point directly to Gateway proxy and add recovery/error UI components.
142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
import { ExternalLink, LogIn, ShieldHalf } from "lucide-react";
|
|
import { useEffect, useRef } from "react";
|
|
import { useAuth } from "react-oidc-context";
|
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
|
import { Button } from "../../components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "../../components/ui/card";
|
|
|
|
function LoginPage() {
|
|
const auth = useAuth();
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
const autoStartedRef = useRef(false);
|
|
const returnTo = searchParams.get("returnTo") || "/";
|
|
const shouldAutoLogin = searchParams.get("auto") === "1";
|
|
|
|
useEffect(() => {
|
|
if (auth.isAuthenticated) {
|
|
navigate(returnTo, { replace: true });
|
|
}
|
|
}, [auth.isAuthenticated, navigate, returnTo]);
|
|
|
|
useEffect(() => {
|
|
if (!shouldAutoLogin) {
|
|
return;
|
|
}
|
|
if (autoStartedRef.current || auth.isLoading || auth.activeNavigator) {
|
|
return;
|
|
}
|
|
|
|
autoStartedRef.current = true;
|
|
void auth.signinRedirect({
|
|
state: {
|
|
returnTo,
|
|
},
|
|
});
|
|
}, [auth, auth.activeNavigator, auth.isLoading, returnTo, shouldAutoLogin]);
|
|
|
|
const handleSSOLogin = () => {
|
|
void auth.signinRedirect({
|
|
state: {
|
|
returnTo: "/",
|
|
},
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-primary/10 via-background to-background">
|
|
<div className="w-full max-w-md space-y-8">
|
|
<div className="flex flex-col items-center justify-center space-y-4 text-center">
|
|
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/15 text-primary shadow-[0_20px_50px_rgba(54,211,153,0.3)]">
|
|
<ShieldHalf size={32} />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<h1 className="text-3xl font-bold tracking-tight">Baron SSO</h1>
|
|
<p className="text-sm text-muted-foreground uppercase tracking-[0.2em]">
|
|
Admin Control Plane
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{auth.error && (
|
|
<div className="rounded-lg bg-destructive/15 p-4 text-sm text-destructive border border-destructive/20 animate-in fade-in slide-in-from-top-1">
|
|
<div className="font-bold flex items-center gap-2 mb-1">
|
|
<ShieldHalf size={16} />
|
|
인증 오류가 발생했습니다
|
|
</div>
|
|
<p className="opacity-90">{auth.error.message}</p>
|
|
<Button
|
|
variant="link"
|
|
className="p-0 h-auto text-destructive underline mt-2"
|
|
onClick={() => {
|
|
window.location.href =
|
|
window.location.origin + window.location.pathname;
|
|
}}
|
|
>
|
|
다시 시도하기
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
<Card className="border-primary/20 bg-card/50 backdrop-blur-xl shadow-2xl">
|
|
<CardHeader className="space-y-1">
|
|
<CardTitle className="text-2xl flex items-center gap-2">
|
|
<LogIn size={20} className="text-primary" />
|
|
관리자 로그인
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Baron 통합 인증(SSO)을 통해 관리자 페이지에 접속합니다.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="pt-4 pb-8 space-y-3">
|
|
<Button
|
|
onClick={handleSSOLogin}
|
|
className="w-full h-14 text-lg font-semibold flex gap-3 shadow-lg"
|
|
disabled={auth.isLoading}
|
|
>
|
|
{auth.isLoading ? (
|
|
<>
|
|
<div className="h-5 w-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
로그인 진행 중...
|
|
</>
|
|
) : (
|
|
<>
|
|
<ShieldHalf size={22} />
|
|
SSO 계정으로 로그인
|
|
<ExternalLink size={16} className="opacity-50" />
|
|
</>
|
|
)}
|
|
</Button>
|
|
|
|
<p className="mt-6 text-xs text-center text-muted-foreground leading-relaxed">
|
|
관리자 전역 세션은 보안을 위해 15분간 유지됩니다.
|
|
<br />
|
|
민감한 작업 시 재인증을 요구할 수 있습니다.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="flex justify-center gap-4">
|
|
<div className="h-1 w-1 rounded-full bg-primary/30" />
|
|
<div className="h-1 w-1 rounded-full bg-primary/30" />
|
|
<div className="h-1 w-1 rounded-full bg-primary/30" />
|
|
</div>
|
|
|
|
<p className="px-8 text-center text-sm text-muted-foreground">
|
|
인증 정보가 없거나 로그인이 되지 않는 경우
|
|
<br />
|
|
시스템 관리자에게 문의하세요.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default LoginPage;
|