Move admin auth local

This commit is contained in:
john
2026-06-20 16:52:13 +08:00
parent 7a5b9cc912
commit a2333f991b
8 changed files with 508 additions and 39 deletions
+114 -14
View File
@@ -1,8 +1,25 @@
import express from 'express';
import { listUsagePaged, listLedgerPaged } from './pagination.mjs';
function asyncHandler(handler) {
return (req, res, next) => Promise.resolve(handler(req, res, next)).catch(next);
}
function wrapRouterAsync(router) {
for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
const original = router[method].bind(router);
router[method] = (path, ...handlers) =>
original(
path,
...handlers.map((handler) =>
typeof handler === 'function' && handler.length < 4 ? asyncHandler(handler) : handler,
),
);
}
}
export function createAdminApp(services) {
const { userAuth, llmProviderService, pool, ready } = services;
const { userAuth, llmProviderService, pool, ready, wechatAdmin } = services;
const app = express();
app.set('trust proxy', 1);
@@ -12,11 +29,13 @@ export function createAdminApp(services) {
let cookieHelpers = null;
const cookieReady = ready.then(async () => {
const [userAuthMod, authMod] = await Promise.all([
services.importUserAuthModule(),
services.importAuthModule(),
]);
cookieHelpers = { ...userAuthMod, parseCookies: authMod.parseCookies };
cookieHelpers = {
parseCookies: services.parseCookies,
USER_COOKIE: services.USER_COOKIE,
userLoginCookies: services.userLoginCookies,
clearUserSessionCookie: services.clearUserSessionCookie,
resolveCookieDomainForRequest: services.resolveCookieDomainForRequest,
};
});
function userToken(req) {
@@ -44,15 +63,19 @@ export function createAdminApp(services) {
res.json({ ok: true, service: 'memind_adm' });
});
app.get('/auth/status', async (req, res) => {
app.get('/healthz', (_req, res) => {
res.json({ ok: true, service: 'memind_adm' });
});
app.get('/auth/status', asyncHandler(async (req, res) => {
await cookieReady;
await ready;
const me = await userAuth.getMe(userToken(req));
if (!me) return res.json({ authenticated: false, mode: 'user' });
return res.json({ authenticated: true, user: me, mode: 'user' });
});
}));
app.post('/auth/login', jsonBody, async (req, res) => {
app.post('/auth/login', jsonBody, asyncHandler(async (req, res) => {
await cookieReady;
await ready;
const { username, password } = req.body ?? {};
@@ -69,27 +92,28 @@ export function createAdminApp(services) {
}
setUserLoginCookies(res, req, result.token);
return res.json({ authenticated: true, user: result.user, mode: 'user' });
});
}));
app.post('/auth/logout', async (req, res) => {
app.post('/auth/logout', asyncHandler(async (req, res) => {
await cookieReady;
await ready;
await userAuth.revoke(userToken(req));
clearUserLoginCookies(res, req);
res.status(204).end();
});
}));
const adminApi = express.Router();
wrapRouterAsync(adminApi);
adminApi.use(jsonBody);
adminApi.use(async (req, res, next) => {
adminApi.use(asyncHandler(async (req, res, next) => {
await cookieReady;
await ready;
const me = await userAuth.getMe(userToken(req));
if (!me) return res.status(401).json({ message: '未登录' });
req.currentUser = me;
next();
});
}));
const requireAdmin = (req, res, next) => {
if (!req.currentUser || req.currentUser.role !== 'admin') {
@@ -99,6 +123,14 @@ export function createAdminApp(services) {
next();
};
function resolveSessionUser(req, res, next) {
if (!req.currentUser) {
res.status(401).json({ message: '未授权,请重新登录' });
return;
}
next();
}
adminApi.get('/users', requireAdmin, async (req, res) => {
const result = await userAuth.listUsers({
page: Number(req.query.page) || 1,
@@ -161,6 +193,52 @@ export function createAdminApp(services) {
res.json(result);
});
adminApi.get('/wechat/summary', requireAdmin, async (_req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.getSummary());
});
adminApi.get('/wechat/bindings', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listBindings(req.query));
});
adminApi.get('/wechat/messages', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listMessages(req.query));
});
adminApi.get('/wechat/digests', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listDigests(req.query));
});
adminApi.get('/wechat/deliveries', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listDeliveries(req.query));
});
adminApi.post('/wechat/users/:userId/route/clear', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.clearRouteForUser(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message ?? '清除失败' });
res.json(result);
});
adminApi.post('/wechat/digests/:id/cancel', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.cancelDigest(req.params.id);
if (!result.ok) return res.status(404).json({ message: '订阅不存在或已暂停' });
res.json(result);
});
adminApi.post('/wechat/digests/:id/resume', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.resumeDigest(req.params.id);
if (!result.ok) return res.status(404).json({ message: result.message ?? '恢复失败' });
res.json(result);
});
adminApi.get('/capabilities/catalog', requireAdmin, (_req, res) => {
res.json({ catalog: userAuth.capabilityCatalog });
});
@@ -363,5 +441,27 @@ export function createAdminApp(services) {
app.use('/admin-api', adminApi);
if (services.createOpsApi) {
const opsApi = express.Router();
wrapRouterAsync(opsApi);
opsApi.use(asyncHandler(async (req, res, next) => {
await cookieReady;
await ready;
const me = await userAuth.getMe(userToken(req));
if (!me) return res.status(401).json({ message: '未授权,请重新登录' });
req.currentUser = me;
next();
}));
opsApi.use(resolveSessionUser);
opsApi.use('/ops/v1', services.createOpsApi({ jsonBody, plazaOps: services.plazaOps }));
app.use('/api', opsApi);
}
app.use((err, _req, res, _next) => {
console.error('Admin API request failed:', err);
if (res.headersSent) return;
res.status(500).json({ message: err instanceof Error ? err.message : '管理后台请求失败' });
});
return app;
}