Add argon2 polyfill and unified preview/API startup for admin login.

Node < 24 lacks crypto.argon2Sync; wire @node-rs/argon2 fallback and start Admin API alongside vite preview in local and remote deploy flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-17 16:38:58 -07:00
parent 9ecfa73831
commit 7a5b9cc912
7 changed files with 414 additions and 21 deletions
+9 -17
View File
@@ -1,5 +1,5 @@
import express from 'express';
import { paginateArray, listUsagePaged, listLedgerPaged } from './pagination.mjs';
import { listUsagePaged, listLedgerPaged } from './pagination.mjs';
export function createAdminApp(services) {
const { userAuth, llmProviderService, pool, ready } = services;
@@ -100,22 +100,14 @@ export function createAdminApp(services) {
};
adminApi.get('/users', requireAdmin, async (req, res) => {
let users = await userAuth.listUsers();
const search = typeof req.query.search === 'string' ? req.query.search.trim().toLowerCase() : '';
const role = typeof req.query.role === 'string' ? req.query.role.trim() : '';
const status = typeof req.query.status === 'string' ? req.query.status.trim() : '';
if (search) {
users = users.filter(
(user) =>
user.username?.toLowerCase().includes(search) ||
user.displayName?.toLowerCase().includes(search) ||
user.email?.toLowerCase().includes(search),
);
}
if (role) users = users.filter((user) => user.role === role);
if (status) users = users.filter((user) => user.status === status);
const paged = paginateArray(users, req.query, 20);
res.json({ users: paged.items, total: paged.total, page: paged.page, pageSize: paged.pageSize });
const result = await userAuth.listUsers({
page: Number(req.query.page) || 1,
pageSize: Number(req.query.pageSize) || 20,
search: typeof req.query.search === 'string' ? req.query.search.trim() : '',
role: typeof req.query.role === 'string' ? req.query.role.trim() : '',
status: typeof req.query.status === 'string' ? req.query.status.trim() : '',
});
res.json(result);
});
adminApi.get('/summary', requireAdmin, async (_req, res) => {
+23
View File
@@ -0,0 +1,23 @@
import crypto from 'node:crypto';
import { Algorithm, hashRawSync } from '@node-rs/argon2';
/** Node < 24 无 crypto.argon2SyncMemind user-auth 登录会崩溃。 */
export function ensureArgon2Sync() {
if (typeof crypto.argon2Sync === 'function') return;
crypto.argon2Sync = (algorithm, options = {}) => {
if (algorithm !== 'argon2id') {
throw new Error(`unsupported argon2 algorithm: ${algorithm}`);
}
const message = options.message ?? '';
const nonce = options.nonce ?? Buffer.alloc(0);
return hashRawSync(String(message), {
algorithm: Algorithm.Argon2id,
salt: nonce,
parallelism: options.parallelism ?? 1,
outputLen: options.tagLength ?? 32,
memoryCost: options.memory ?? 65536,
timeCost: options.passes ?? 3,
});
};
}
+3
View File
@@ -1,4 +1,7 @@
import { ensureArgon2Sync } from './argon2-polyfill.mjs';
import { loadProjectEnv } from './load-env.mjs';
ensureArgon2Sync();
import { bootstrapAdminServices } from './bootstrap.mjs';
import { importMemind } from './lib-path.mjs';
import { createAdminApp } from './app.mjs';