89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
// server/utils/auth.ts - Better Auth 服务端实例,邮箱密码登录 + 用户 API token
|
||
import { randomBytes } from "node:crypto";
|
||
|
||
import { betterAuth } from "better-auth";
|
||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||
import type { H3Event } from "h3";
|
||
|
||
import { prisma } from "~~/server/utils/db";
|
||
|
||
/**
|
||
* Better Auth 全局实例
|
||
*
|
||
* - 使用 Prisma MySQL/MariaDB 适配器
|
||
* - 只启用邮箱/密码登录,无邮箱验证和密码重置(保持最简)
|
||
* - 注册时通过 databaseHook 自动生成 32 位 hex apiToken
|
||
* apiToken 供 OCS 油猴脚本跨域调用 /api/search 时在 body 中携带
|
||
* - apiToken 不允许客户端直接设置(input: false),但随 session 返回给登录用户
|
||
*/
|
||
export const auth = betterAuth({
|
||
database: prismaAdapter(prisma, { provider: "mysql" }),
|
||
|
||
session: {
|
||
expiresIn: 60 * 60 * 24 * 14, // 14 天过期
|
||
updateAge: 60 * 60 * 24 // 每天自动续期
|
||
},
|
||
|
||
emailAndPassword: {
|
||
enabled: true
|
||
},
|
||
|
||
user: {
|
||
additionalFields: {
|
||
apiToken: {
|
||
type: "string",
|
||
required: false,
|
||
// 客户端(signup/updateUser)不能设置此字段,只由 databaseHooks 写入
|
||
input: false
|
||
},
|
||
role: {
|
||
type: "string",
|
||
required: false,
|
||
// 角色只由服务端管理员设置,不允许客户端修改
|
||
input: false
|
||
}
|
||
}
|
||
},
|
||
|
||
databaseHooks: {
|
||
user: {
|
||
create: {
|
||
// 注册时自动生成 apiToken,格式与旧 ACCESS_TOKEN 字段一致(32 位 hex)
|
||
before: async (user) => {
|
||
return {
|
||
data: {
|
||
...user,
|
||
apiToken: randomBytes(16).toString("hex")
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
/**
|
||
* 从 H3 事件中读取 Better Auth session
|
||
*
|
||
* 只复制 headers,不调用 toWebRequest(event)。
|
||
* 对 POST/PUT 请求,toWebRequest 会包装 body stream;全局鉴权中调用后,
|
||
* 后续 handler 再 readBody(event) 可能长时间挂起。
|
||
* 未登录或 session 无效时返回 null
|
||
*/
|
||
const getHeaderSnapshot = (event: H3Event) => {
|
||
const headers = new Headers();
|
||
|
||
for (const [key, value] of Object.entries(event.node.req.headers)) {
|
||
if (Array.isArray(value)) {
|
||
for (const item of value) headers.append(key, item);
|
||
} else if (value !== undefined) {
|
||
headers.set(key, value);
|
||
}
|
||
}
|
||
|
||
return headers;
|
||
};
|
||
|
||
export const getAuthSession = (event: H3Event) =>
|
||
auth.api.getSession({ headers: getHeaderSnapshot(event) });
|