auto-save 2026-04-19 22:51 (~2)

This commit is contained in:
2026-04-19 22:51:12 +08:00
parent 81e2710b6f
commit 7d83838a2e
2 changed files with 58 additions and 2 deletions

View File

@@ -307,6 +307,13 @@
"message": "auto-save 2026-04-19 22:40 (~1)",
"hash": "7adecd9",
"files_changed": 1
},
{
"ts": "2026-04-19T22:45:43+08:00",
"type": "commit",
"message": "auto-save 2026-04-19 22:45 (~1)",
"hash": "81e2710",
"files_changed": 1
}
]
}

View File

@@ -7,11 +7,53 @@ import { containerName, incus } from './incus.ts';
export const admin = new Hono();
// 鉴权中间件 — token 走 query(首屏 iframe/跳转友好)或 header
// 生成用户范围 token(供 LobeChat 内嵌 iframe 用,只能访问自己的沙箱)
export const scopedToken = async (userId: string): Promise<string> => {
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(env.orchSecret),
{ hash: 'SHA-256', name: 'HMAC' },
false,
['sign'],
);
const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`view:${userId}`));
return Buffer.from(sig).toString('base64url').slice(0, 32);
};
const tokenForUser = (token: string, userId: string) =>
scopedToken(userId).then((t) => t === token);
admin.use('*', async (c, next) => {
if (!env.adminToken) return c.text('Admin UI disabled: set ADMIN_TOKEN env', 503);
const token = c.req.query('token') ?? c.req.header('Admin-Token');
if (token !== env.adminToken) return c.text('Unauthorized', 401);
if (!token) return c.text('Unauthorized', 401);
// 1. admin token (全局)
if (env.adminToken && token === env.adminToken) {
await next();
return;
}
// 2. scoped token 针对单用户路由,验证 userId 匹配
const userId =
c.req.param('userId') ??
c.req.query('userId') ??
(c.req.path.match(/\/user\/([^\/]+)/)?.[1]);
if (userId && (await tokenForUser(token, userId))) {
c.set('scopedUserId', userId);
await next();
return;
}
return c.text('Unauthorized', 401);
});
// 允许 ai.milejoy.com / lobehub.kang-kang.com iframe 引用
admin.use('*', async (c, next) => {
await next();
c.header(
'Content-Security-Policy',
"frame-ancestors 'self' https://ai.milejoy.com https://lobehub.kang-kang.com",
);
c.header('X-Frame-Options', '');
});
// 全局用户列表
@@ -37,6 +79,13 @@ admin.get('/users', async (c) => {
return c.json({ users });
});
// 给一个 userId 生成 scoped token(admin-token only;LobeChat 服务器端也要实现同样算法)
admin.get('/token/:userId', async (c) => {
const userId = c.req.param('userId');
const token = await scopedToken(userId);
return c.json({ userId, token, viewUrl: `/admin/user/${userId}?token=${token}` });
});
// 单用户 detail
admin.get('/user/:userId', async (c) => {
const userId = c.req.param('userId');