feat: 新增鉴权
This commit is contained in:
+57
-32
@@ -1,42 +1,67 @@
|
||||
// server/utils/auth.ts - OCS API 可选访问令牌校验
|
||||
import { getQuery, type H3Event } from "h3";
|
||||
// server/utils/auth.ts - Better Auth 服务端实例,邮箱密码登录 + 用户 API token
|
||||
import { randomBytes } from "node:crypto";
|
||||
|
||||
import { serverEnv } from "~~/server/utils/env";
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import type { H3Event } from "h3";
|
||||
import { toWebRequest } from "h3";
|
||||
|
||||
import { prisma } from "~~/server/utils/db";
|
||||
|
||||
/**
|
||||
* H3 的 query 值可能是字符串、数组或 undefined
|
||||
* 访问令牌只接受第一个值,和多数 Web 框架读取 query 的行为保持一致
|
||||
*/
|
||||
const firstQueryValue = (value: unknown) => {
|
||||
if (Array.isArray(value)) return value[0]?.toString() || "";
|
||||
return value?.toString() || "";
|
||||
};
|
||||
|
||||
/**
|
||||
* 校验可选访问令牌
|
||||
* Better Auth 全局实例
|
||||
*
|
||||
* - 没配置 `ACCESS_TOKEN` 时,服务保持旧 Python 项目的开放行为
|
||||
* - 配置后,兼容两种传递方式:`X-Access-Token` 请求头或 `?token=...`
|
||||
* - 使用 Prisma MySQL/MariaDB 适配器
|
||||
* - 只启用邮箱/密码登录,无邮箱验证和密码重置(保持最简)
|
||||
* - 注册时通过 databaseHook 自动生成 32 位 hex apiToken
|
||||
* apiToken 供 OCS 油猴脚本跨域调用 /api/search 时在 body 中携带
|
||||
* - apiToken 不允许客户端直接设置(input: false),但随 session 返回给登录用户
|
||||
*/
|
||||
export const verifyAccessToken = (event: H3Event) => {
|
||||
if (!serverEnv.accessToken) return true;
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, { provider: "mysql" }),
|
||||
|
||||
const headerToken = event.node.req.headers["x-access-token"]?.toString();
|
||||
const queryToken = firstQueryValue(getQuery(event).token);
|
||||
session: {
|
||||
expiresIn: 60 * 60 * 24 * 14, // 14 天过期
|
||||
updateAge: 60 * 60 * 24 // 每天自动续期
|
||||
},
|
||||
|
||||
return (
|
||||
headerToken === serverEnv.accessToken ||
|
||||
queryToken === serverEnv.accessToken
|
||||
);
|
||||
};
|
||||
emailAndPassword: {
|
||||
enabled: true
|
||||
},
|
||||
|
||||
user: {
|
||||
additionalFields: {
|
||||
apiToken: {
|
||||
type: "string",
|
||||
required: false,
|
||||
// 客户端(signup/updateUser)不能设置此字段,只由 databaseHooks 写入
|
||||
input: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
databaseHooks: {
|
||||
user: {
|
||||
create: {
|
||||
// 注册时自动生成 apiToken,格式与旧 ACCESS_TOKEN 字段一致(32 位 hex)
|
||||
before: async (user) => {
|
||||
return {
|
||||
data: {
|
||||
...user,
|
||||
apiToken: randomBytes(16).toString("hex")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* OCS 搜索接口使用 `{ code, msg }`,而 stats/cache 使用 `{ success, message }`
|
||||
* 这里先返回最基础的 OCS 形状,其他接口复用其中的 msg 文案
|
||||
* 从 H3 事件中读取 Better Auth session
|
||||
*
|
||||
* 封装 toWebRequest 转换,避免在每个 handler 中重复导入和调用
|
||||
* 未登录或 session 无效时返回 null
|
||||
*/
|
||||
export const invalidAccessTokenResponse = () => {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "无效的访问令牌"
|
||||
};
|
||||
};
|
||||
export const getAuthSession = (event: H3Event) =>
|
||||
auth.api.getSession({ headers: toWebRequest(event).headers });
|
||||
|
||||
Reference in New Issue
Block a user