feat: set jwt expired time to 30d

This commit is contained in:
zaunist committed 2026-01-01 17:02:20 +08:00
1 parent 726c6c5e97
commit 4803321951
8 files changed
+223 -66

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
/**
* 应用配置常量
*/
/**
* JWT 配置
*/
export const JWT_CONFIG = {
/**
* Access Token 有效期:30天
* 用户登录后 30 天内无需重新登录
*/
ACCESS_TOKEN_EXPIRES_IN: "30d",
/**
* 默认 JWT 密钥(生产环境应使用 CloudFlare 版本 ID)
*/
DEFAULT_SECRET: "your-secret-key-change-in-production",
};
/**
* 认证相关配置
*/
export const AUTH_CONFIG = {
/**
* Bcrypt 加密盐的轮数
*/
BCRYPT_SALT_ROUNDS: 10,
/**
* 密码最小长度
*/
MIN_PASSWORD_LENGTH: 6,
};
/**
* 监控配置
*/
export const MONITOR_CONFIG = {
/**
* 监控检查间隔(分钟)
*/
CHECK_INTERVAL_MINUTES: 5,
/**
* 请求超时时间(毫秒)
*/
REQUEST_TIMEOUT_MS: 30000,
};
+2 -1
View File
@@ -1 +1,2 @@
export * from "./db";
export * from "./db";
export * from "./constants";
+12 -5
View File
@@ -10,6 +10,7 @@ import { getJwtSecret } from "../utils/jwt";
import { Bindings } from "../models/db";
import * as SettingsService from "./SettingsService";
import * as NotificationService from "./NotificationService";
import { JWT_CONFIG, AUTH_CONFIG } from "../config";
/**
* 用户登录
@@ -78,7 +79,10 @@ export async function loginUser(
const secret = getJwtSecret(env);
console.log("获取到的 JWT secret:", secret);
const token = jsonwebtoken.sign(payload, secret, { expiresIn: "24h" });
// 使用配置的 token 有效期(30天)
const token = jsonwebtoken.sign(payload, secret, {
expiresIn: JWT_CONFIG.ACCESS_TOKEN_EXPIRES_IN,
} as jsonwebtoken.SignOptions) as string;
console.log("成功生成 JWT token, 长度:", token.length);
return {
@@ -121,7 +125,8 @@ export async function registerUser(
): Promise<{ success: boolean; message: string; user?: any }> {
try {
// 新增:检查是否允许新用户注册
const isRegistrationAllowed = await SettingsService.getAllowNewUserRegistration();
const isRegistrationAllowed =
await SettingsService.getAllowNewUserRegistration();
if (!isRegistrationAllowed) {
return { success: false, message: "新用户注册功能已关闭" };
}
@@ -132,8 +137,8 @@ export async function registerUser(
return { success: false, message: "用户名已存在" };
}
// 密码加密
const salt = await bcrypt.genSalt(10);
// 密码加密(使用配置的盐轮数)
const salt = await bcrypt.genSalt(AUTH_CONFIG.BCRYPT_SALT_ROUNDS);
const hashedPassword = await bcrypt.hash(password, salt);
// 创建用户, 默认角色为 'user'
@@ -146,7 +151,9 @@ export async function registerUser(
// 为新用户创建默认的通知设置
if (newUser && newUser.id) {
await NotificationService.createDefaultNotificationSettingsForUser(newUser.id);
await NotificationService.createDefaultNotificationSettingsForUser(
newUser.id
);
}
return {
+5 -3
View File
@@ -2,6 +2,8 @@
* JWT工具类,提供JWT相关的通用功能
*/
import { JWT_CONFIG } from "../config";
/**
* 获取JWT密钥
* 优先从环境变量中获取JWT_SECRET,如果不存在则使用默认值
@@ -13,7 +15,7 @@ export const getJwtSecret = (c: any): string => {
// 检查是否直接包含 CF_VERSION_METADATA
if (c.CF_VERSION_METADATA) {
const { id: versionId } = c.CF_VERSION_METADATA;
return versionId || "your-secret-key-change-in-production";
return versionId || JWT_CONFIG.DEFAULT_SECRET;
}
// 检查是否在 env 属性下包含 CF_VERSION_METADATA
@@ -21,11 +23,11 @@ export const getJwtSecret = (c: any): string => {
console.log("context.env 包含 CF_VERSION_METADATA");
const { id: versionId } = c.env.CF_VERSION_METADATA;
console.log("解析出的 versionId:", versionId);
return versionId || "your-secret-key-change-in-production";
return versionId || JWT_CONFIG.DEFAULT_SECRET;
}
console.error("错误: 未找到 CF_VERSION_METADATA");
return "your-secret-key-change-in-production";
return JWT_CONFIG.DEFAULT_SECRET;
};
/**
@@ -0,0 +1,35 @@
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "../providers/AuthProvider";
interface ProtectedRouteProps {
children: React.ReactNode;
}
/**
* 路由守卫组件
* 用于保护需要登录才能访问的路由
*/
export default function ProtectedRoute({ children }: ProtectedRouteProps) {
const { isAuthenticated, isLoading } = useAuth();
const location = useLocation();
// 如果正在加载,显示加载状态
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto"></div>
<p className="mt-4 text-gray-600">加载中...</p>
</div>
</div>
);
}
// 如果未登录,重定向到登录页面,并保存当前路径
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
// 已登录,渲染子组件
return <>{children}</>;
}
+12 -2
View File
@@ -14,11 +14,19 @@ const Login = () => {
const [message, setMessage] = useState("");
const [showRegister, setShowRegister] = useState(false); // 新增状态
const { login } = useAuth();
const { login, isAuthenticated } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation();
// 如果已登录,重定向到 dashboard 或原来要访问的页面
useEffect(() => {
if (isAuthenticated) {
const from = (location.state as any)?.from?.pathname || "/dashboard";
navigate(from, { replace: true });
}
}, [isAuthenticated, navigate, location]);
// 检查是否有来自注册页面的消息
useEffect(() => {
// 检查是否允许注册
@@ -47,7 +55,9 @@ const Login = () => {
try {
const result = await login({ username, password });
if (result.success) {
navigate("/dashboard");
// 登录成功后,重定向到用户原来要访问的页面,或默认到 dashboard
const from = (location.state as any)?.from?.pathname || "/dashboard";
navigate(from, { replace: true });
} else {
setError(result.message);
}
+30 -8
View File
@@ -11,12 +11,7 @@ import {
getCurrentUser,
} from "../api/auth";
import { getAllowNewUserRegistration } from "../api/settings"; // 新增
import {
User,
LoginRequest,
RegisterRequest,
AuthContextType,
} from "../types";
import { User, LoginRequest, RegisterRequest, AuthContextType } from "../types";
import { useTranslation } from "react-i18next";
const AuthContext = createContext<AuthContextType | undefined>(undefined);
@@ -37,12 +32,39 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({
if (storedToken && storedUser) {
setToken(storedToken);
setUser(JSON.parse(storedUser));
setIsLoading(false);
// 验证 token 是否有效
verifyToken();
} else {
setIsLoading(false);
}
}, []);
// 验证 token 是否有效
const verifyToken = async () => {
try {
const response = await getCurrentUser();
if (response.success && response.user) {
setUser(response.user);
localStorage.setItem("user", JSON.stringify(response.user));
} else {
// Token 无效,清除登录状态
localStorage.removeItem("token");
localStorage.removeItem("user");
setToken(null);
setUser(null);
}
} catch (error) {
console.error(t("auth.error.fetchUser"), error);
// Token 验证失败,清除登录状态
localStorage.removeItem("token");
localStorage.removeItem("user");
setToken(null);
setUser(null);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
// 如果有 token,但没有 user,则获取用户信息
if (token && !user) {
@@ -93,7 +115,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({
// 客户端再次检查是否允许注册
const allowResponse = await getAllowNewUserRegistration();
if (!allowResponse.success || !allowResponse.allow) {
return { success: false, message: t('register.disabled') };
return { success: false, message: t("register.disabled") };
}
const response = await apiRegister(data);
return { success: response.success, message: response.message };
+78 -47
View File
@@ -5,6 +5,7 @@ import { lazy, Suspense, ReactNode } from "react";
import Layout from "../components/Layout";
import UsersList from "../pages/users/UsersList";
import UserProfile from "../pages/users/UserProfile";
import ProtectedRoute from "../components/ProtectedRoute";
// 懒加载页面组件
const Dashboard = lazy(() => import("../pages/Dashboard"));
@@ -55,9 +56,11 @@ const protectedRoutes: RouteObject[] = [
{
path: "/dashboard",
element: (
<Suspense fallback={<div>加载中...</div>}>
<Dashboard />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<Dashboard />
</Suspense>
</ProtectedRoute>
),
},
// 代理页面
@@ -67,33 +70,41 @@ const protectedRoutes: RouteObject[] = [
{
path: "",
element: (
<Suspense fallback={<div>加载中...</div>}>
<AgentsList />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<AgentsList />
</Suspense>
</ProtectedRoute>
),
},
{
path: ":id",
element: (
<Suspense fallback={<div>加载中...</div>}>
<AgentDetail />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<AgentDetail />
</Suspense>
</ProtectedRoute>
),
},
{
path: "create",
element: (
<Suspense fallback={<div>加载中...</div>}>
<CreateAgent />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<CreateAgent />
</Suspense>
</ProtectedRoute>
),
},
{
path: "edit/:id",
element: (
<Suspense fallback={<div>加载中...</div>}>
<EditAgent />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<EditAgent />
</Suspense>
</ProtectedRoute>
),
},
],
@@ -113,9 +124,11 @@ const protectedRoutes: RouteObject[] = [
{
path: "config",
element: (
<Suspense fallback={<div>加载中...</div>}>
<StatusPageConfig />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<StatusPageConfig />
</Suspense>
</ProtectedRoute>
),
},
],
@@ -127,33 +140,41 @@ const protectedRoutes: RouteObject[] = [
{
path: "",
element: (
<Suspense fallback={<div>加载中...</div>}>
<MonitorsList />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<MonitorsList />
</Suspense>
</ProtectedRoute>
),
},
{
path: ":id",
element: (
<Suspense fallback={<div>加载中...</div>}>
<MonitorDetail />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<MonitorDetail />
</Suspense>
</ProtectedRoute>
),
},
{
path: "create",
element: (
<Suspense fallback={<div>加载中...</div>}>
<CreateMonitor />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<CreateMonitor />
</Suspense>
</ProtectedRoute>
),
},
{
path: "edit/:id",
element: (
<Suspense fallback={<div>加载中...</div>}>
<EditMonitor />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<EditMonitor />
</Suspense>
</ProtectedRoute>
),
},
],
@@ -162,9 +183,11 @@ const protectedRoutes: RouteObject[] = [
{
path: "/notifications",
element: (
<Suspense fallback={<div>加载中...</div>}>
<NotificationsConfig />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<NotificationsConfig />
</Suspense>
</ProtectedRoute>
),
},
// 用户管理
@@ -174,25 +197,31 @@ const protectedRoutes: RouteObject[] = [
{
path: "",
element: (
<Suspense fallback={<div>加载中...</div>}>
<UsersList />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<UsersList />
</Suspense>
</ProtectedRoute>
),
},
{
path: "create",
element: (
<Suspense fallback={<div>加载中...</div>}>
<CreateUser />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<CreateUser />
</Suspense>
</ProtectedRoute>
),
},
{
path: ":id",
element: (
<Suspense fallback={<div>加载中...</div>}>
<EditUser />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<EditUser />
</Suspense>
</ProtectedRoute>
),
},
],
@@ -202,9 +231,11 @@ const protectedRoutes: RouteObject[] = [
{
path: "/profile",
element: (
<Suspense fallback={<div>加载中...</div>}>
<UserProfile />
</Suspense>
<ProtectedRoute>
<Suspense fallback={<div>加载中...</div>}>
<UserProfile />
</Suspense>
</ProtectedRoute>
),
},
];
@@ -246,7 +277,7 @@ const publicRoutes: RouteObject[] = [
];
// 创建路由
const router = createBrowserRouter(
const router: ReturnType<typeof createBrowserRouter> = createBrowserRouter(
[
{
element: (
@@ -268,4 +299,4 @@ const router = createBrowserRouter(
}
);
export default router;
export default router;