feat: 完善鉴权
This commit is contained in:
@@ -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: "无效的访问令牌"
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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),
|
||||
/** 缓存过期时间,单位秒 */
|
||||
|
||||
Reference in New Issue
Block a user