Add authentication features for admin access
- Introduce a new `auth` module with login, logout, and user verification endpoints for a single admin user. - Update backend routes to require admin authentication for sensitive operations, enhancing security. - Modify frontend components to include an authentication provider and gate, ensuring only authorized users can access the application. - Implement a login page for admin access, improving user experience and security management. - Update API request handling to redirect unauthorized users to the login page, ensuring proper access control.
This commit is contained in:
@@ -2,7 +2,8 @@ import type { Metadata } from "next";
|
||||
import { Geist_Mono, Inter, Cormorant_Garamond } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AppShell } from "@/components/layout/AppShell";
|
||||
import { AuthGate } from "@/components/auth/AuthGate";
|
||||
import { AuthProvider } from "@/components/auth/AuthProvider";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
|
||||
|
||||
@@ -50,7 +51,9 @@ export default function RootLayout({
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
</head>
|
||||
<body className="min-h-full flex flex-col">
|
||||
<AppShell>{children}</AppShell>
|
||||
<AuthProvider>
|
||||
<AuthGate>{children}</AuthGate>
|
||||
</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
104
frontend/src/app/login/page.tsx
Normal file
104
frontend/src/app/login/page.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { LockKeyhole, LogIn, Video } from "lucide-react";
|
||||
import { useAuth } from "@/components/auth/AuthProvider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const { user, loading, login } = useAuth();
|
||||
const [username, setUsername] = useState("admin");
|
||||
const [password, setPassword] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && user) {
|
||||
router.replace("/");
|
||||
}
|
||||
}, [loading, router, user]);
|
||||
|
||||
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
setError("");
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await login(username.trim(), password);
|
||||
router.replace("/");
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "登录失败");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center bg-background px-6 py-10 text-foreground">
|
||||
<Card className="w-full max-w-[420px] rounded-2xl border border-hairline bg-card shadow-lg">
|
||||
<CardHeader className="gap-3">
|
||||
<div
|
||||
className="flex h-11 w-11 items-center justify-center rounded-2xl text-on-primary shadow-sm"
|
||||
style={{
|
||||
backgroundColor: "var(--primary)",
|
||||
backgroundImage:
|
||||
"radial-gradient(circle at 30% 20%, color-mix(in srgb, var(--gradient-sky) 70%, transparent), transparent 60%), radial-gradient(circle at 80% 90%, color-mix(in srgb, var(--gradient-lavender) 65%, transparent), transparent 55%)",
|
||||
}}
|
||||
>
|
||||
<Video size={22} style={{ color: "var(--primary-foreground)" }} />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-xl">AI 视频助手管理台</CardTitle>
|
||||
<CardDescription className="mt-1">
|
||||
使用超级管理员账号登录
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm text-muted-foreground">用户名</span>
|
||||
<Input
|
||||
autoComplete="username"
|
||||
value={username}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
placeholder="admin"
|
||||
/>
|
||||
</label>
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm text-muted-foreground">密码</span>
|
||||
<Input
|
||||
autoComplete="current-password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</label>
|
||||
|
||||
{error ? (
|
||||
<div className="flex items-center gap-2 rounded-2xl border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
<LockKeyhole size={15} />
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Button className="w-full gap-2" size="lg" disabled={submitting}>
|
||||
<LogIn size={17} />
|
||||
{submitting ? "登录中..." : "登录"}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user