"use client"; import { LockOutlined, UserOutlined } from "@ant-design/icons"; import { App, Button, Form, Input, Segmented, Space } from "antd"; import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; import { fetchCurrentUser } from "@/services/api/auth"; import { useConfigStore } from "@/stores/use-config-store"; import { useUserStore } from "@/stores/use-user-store"; type LoginFormValues = { username: string; password: string; confirmPassword?: string; }; // 仅放行站内相对路径,拦截开放重定向。浏览器会忽略 URL 中的 Tab/换行/回车,并把 // //host 或 /\host 解析为协议相对的跨站地址,因此先剥离控制字符,再拒绝 // 与 /\ 前缀。 function safeRedirect(value: string | null): string { const cleaned = (value ?? "").replace(/[\t\n\r]/g, ""); if (!cleaned.startsWith("/") || cleaned.startsWith("//") || cleaned.startsWith("/\\")) { return "/"; } return cleaned; } export default function LoginPage() { return ( ); } function LoginContent() { const { message } = App.useApp(); const router = useRouter(); const searchParams = useSearchParams(); const login = useUserStore((state) => state.login); const register = useUserStore((state) => state.register); const setSession = useUserStore((state) => state.setSession); const isLoading = useUserStore((state) => state.isLoading); const linuxDoEnabled = useConfigStore((state) => state.publicSettings?.auth?.linuxDo?.enabled === true); const allowRegister = useConfigStore((state) => state.publicSettings?.auth?.allowRegister !== false); const [mode, setMode] = useState<"login" | "register">("login"); const redirect = safeRedirect(searchParams.get("redirect")); useEffect(() => { const token = searchParams.get("token"); const error = searchParams.get("error"); if (error) message.error(error); if (!token) return; void fetchCurrentUser(token).then((user) => { setSession(token, user); message.success("登录成功"); router.replace(redirect); router.refresh(); }); }, [message, redirect, router, searchParams, setSession]); useEffect(() => { if (!allowRegister && mode === "register") setMode("login"); }, [allowRegister, mode]); const submit = async (values: LoginFormValues) => { try { if (mode === "register" && !allowRegister) { message.error("当前未开放注册"); return; } if (mode === "register" && values.password !== values.confirmPassword) { message.error("两次输入的密码不一致"); return; } const action = mode === "register" ? register : login; const user = await action({ username: values.username, password: values.password }); message.success(mode === "register" ? "注册成功" : "登录成功"); router.replace(redirect); router.refresh(); if (user.role !== "admin") router.replace("/"); } catch (error) { message.error(error instanceof Error ? error.message : "登录失败"); } }; return (

账号登录

支持账号密码和 Linux.do 登录。

layout="vertical" size="large" requiredMark={false} onFinish={submit}> setMode(value as "login" | "register")} options={allowRegister ? [{ label: "登录", value: "login" }, { label: "注册", value: "register" }] : [{ label: "登录", value: "login" }]} /> 用户名} rules={[{ required: true, message: "请输入用户名" }]}> } autoComplete="username" /> 密码} rules={[{ required: true, message: "请输入密码" }]}> } autoComplete="current-password" /> {mode === "register" ? ( 确认密码} rules={[{ required: true, message: "请再次输入密码" }]}> } autoComplete="new-password" /> ) : null} {linuxDoEnabled ? ( ) : null}
); }