feat: 完善鉴权

This commit is contained in:
2026-05-22 23:10:40 +08:00
parent 3857f77f8b
commit c48d57df14
14 changed files with 118 additions and 117 deletions
-42
View File
@@ -1,42 +0,0 @@
// server/utils/accessToken.ts - OCS API 可选访问令牌校验(用于 stats/cache 等管理接口)
import { getQuery, type H3Event } from "h3";
import { serverEnv } from "~~/server/utils/env";
/**
* H3 的 query 值可能是字符串、数组或 undefined
* 访问令牌只接受第一个值,和多数 Web 框架读取 query 的行为保持一致
*/
const firstQueryValue = (value: unknown) => {
if (Array.isArray(value)) return value[0]?.toString() || "";
return value?.toString() || "";
};
/**
* 校验可选访问令牌
*
* - 没配置 `ACCESS_TOKEN` 时,服务保持旧 Python 项目的开放行为
* - 配置后,兼容两种传递方式:`X-Access-Token` 请求头或 `?token=...`
*/
export const verifyAccessToken = (event: H3Event) => {
if (!serverEnv.accessToken) return true;
const headerToken = event.node.req.headers["x-access-token"]?.toString();
const queryToken = firstQueryValue(getQuery(event).token);
return (
headerToken === serverEnv.accessToken ||
queryToken === serverEnv.accessToken
);
};
/**
* OCS 搜索接口使用 `{ code, msg }`,而 stats/cache 使用 `{ success, message }`
* 这里先返回最基础的 OCS 形状,其他接口复用其中的 msg 文案
*/
export const invalidAccessTokenResponse = () => {
return {
code: 0,
msg: "无效的访问令牌"
};
};
+46
View File
@@ -0,0 +1,46 @@
// server/utils/api-auth-rules.ts - API 鉴权路由规则表:声明公开路由,其余默认需要登录
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS";
type ApiRouteRule = {
path: string | RegExp;
method?: HttpMethod | HttpMethod[];
};
/**
* 公开 API 路由规则表
*
* 新增接口默认需要登录,无需任何改动;
* 只有明确需要公开访问的路由才加到这里。
*/
export const publicApiRoutes: ApiRouteRule[] = [
// Better Auth 自己的登录、注册、退出、get-session 等接口必须放行
{ path: "/api/auth/**" },
// 服务健康检查,供部署平台和 Docker healthcheck 使用,不含敏感信息
{ method: "GET", path: "/api/health" },
// OCS 油猴脚本跨域无法携带 cookie,搜索接口有自己的双通道鉴权(session 或 apiToken
{ path: "/api/search" }
];
/** 路径匹配:支持精确匹配、`/**` 前缀通配和 RegExp */
function matchPath(rulePath: string | RegExp, pathname: string): boolean {
if (rulePath instanceof RegExp) return rulePath.test(pathname);
if (rulePath.endsWith("/**")) {
const prefix = rulePath.slice(0, -3);
return pathname === prefix || pathname.startsWith(`${prefix}/`);
}
return pathname === rulePath;
}
/** 判断当前请求是否命中公开路由规则 */
export function isPublicApiRoute(pathname: string, method: string): boolean {
return publicApiRoutes.some((rule) => {
const methods = Array.isArray(rule.method)
? rule.method
: rule.method
? [rule.method]
: null;
const methodMatched = !methods || methods.includes(method as HttpMethod);
return methodMatched && matchPath(rule.path, pathname);
});
}
-2
View File
@@ -73,8 +73,6 @@ export const serverEnv = {
maxTokens: readInteger("MAX_TOKENS", 500),
/** 模型采样温度,越低越稳定 */
temperature: readNumber("TEMPERATURE", 0.7),
/** 可选访问令牌;配置后 API 需要 header 或 query 携带 token */
accessToken: readOptionalString("ACCESS_TOKEN"),
/** 是否启用内存缓存 */
enableCache: readBoolean("ENABLE_CACHE", true),
/** 缓存过期时间,单位秒 */