666db0b939
Persist goal runs in MySQL, bind agent runs to checkpoints, expose awaiting-approval UX in chat, and add admin inspection routes with local verify scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
464 lines
10 KiB
JavaScript
464 lines
10 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { attachPortalCoreAuthRoutes } from './portal-core-auth-routes.mjs';
|
|
|
|
function createResponse() {
|
|
return {
|
|
statusCode: 200,
|
|
headers: {},
|
|
body: undefined,
|
|
status(code) {
|
|
this.statusCode = code;
|
|
return this;
|
|
},
|
|
set(name, value) {
|
|
this.headers[name] = value;
|
|
return this;
|
|
},
|
|
json(body) {
|
|
this.body = body;
|
|
return this;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createSetup(overrides = {}) {
|
|
const routes = [];
|
|
const calls = [];
|
|
let userAuth = {
|
|
async getMe(token) {
|
|
calls.push(['get-me', token]);
|
|
return { id: 'user-1', username: 'john' };
|
|
},
|
|
async getUserById(userId) {
|
|
calls.push(['get-user', userId]);
|
|
return { id: userId };
|
|
},
|
|
async resolveUserCapabilities(row) {
|
|
calls.push(['capabilities', row]);
|
|
return {
|
|
capabilities: ['chat'],
|
|
grantedSkills: ['web'],
|
|
unrestricted: false,
|
|
};
|
|
},
|
|
async login(options) {
|
|
calls.push(['login', options]);
|
|
return {
|
|
ok: true,
|
|
token: 'user-token',
|
|
user: { id: 'user-1' },
|
|
};
|
|
},
|
|
async loginByWechatMiniProgram(options) {
|
|
calls.push(['miniapp-login', options]);
|
|
return {
|
|
ok: true,
|
|
token: 'mini-token',
|
|
user: { id: 'user-2' },
|
|
isNewUser: true,
|
|
};
|
|
},
|
|
async register(options) {
|
|
calls.push(['register', options]);
|
|
return {
|
|
ok: true,
|
|
user: { id: 'user-3' },
|
|
};
|
|
},
|
|
async resetPassword(options) {
|
|
calls.push(['reset-password', options]);
|
|
return { ok: true };
|
|
},
|
|
};
|
|
let legacyAuth = null;
|
|
const jsonBody = (_req, _res, next) => next();
|
|
const app = {
|
|
get(path, ...handlers) {
|
|
routes.push({ method: 'get', path, handlers });
|
|
},
|
|
post(path, ...handlers) {
|
|
routes.push({ method: 'post', path, handlers });
|
|
},
|
|
};
|
|
const plazaSeo = {
|
|
recordAttribution(payload, ip) {
|
|
calls.push(['attribution', payload, ip]);
|
|
return Promise.resolve();
|
|
},
|
|
};
|
|
const options = {
|
|
app,
|
|
jsonBody,
|
|
userAuthReady: Promise.resolve(),
|
|
getUserAuth: () => userAuth,
|
|
getLegacyAuth: () => legacyAuth,
|
|
userToken: () => 'request-user-token',
|
|
legacySessionToken: () => 'legacy-token',
|
|
setUserLoginCookies(_res, _req, token) {
|
|
calls.push(['set-user-cookies', token]);
|
|
},
|
|
isSecureRequest: () => true,
|
|
async resolveSkillRuntimeForClient() {
|
|
calls.push(['skill-runtime']);
|
|
return { enabled: true };
|
|
},
|
|
async resolveAgentCodeRunForClient(userId) {
|
|
calls.push(['agent-code-run', userId]);
|
|
return { enabled: true, userId };
|
|
},
|
|
async resolveGoalRunForClient(userId) {
|
|
calls.push(['goal-run', userId]);
|
|
return { enabled: true };
|
|
},
|
|
getPlazaSeo: () => plazaSeo,
|
|
plazaClientIp: () => '203.0.113.5',
|
|
logger: {
|
|
error(...args) {
|
|
calls.push(['error', ...args]);
|
|
},
|
|
},
|
|
isDatabaseConfiguredFn: () => false,
|
|
sessionCookieFn(token, secure) {
|
|
calls.push(['session-cookie', token, secure]);
|
|
return `session=${token}`;
|
|
},
|
|
loadWechatMiniappConfigFn: () => ({
|
|
enabled: true,
|
|
appId: 'mini-app',
|
|
appSecret: 'mini-secret',
|
|
}),
|
|
async exchangeMiniProgramCodeFn(options) {
|
|
calls.push(['exchange-code', options]);
|
|
return {
|
|
openid: 'openid-1',
|
|
unionid: 'unionid-1',
|
|
};
|
|
},
|
|
...overrides,
|
|
};
|
|
attachPortalCoreAuthRoutes(options);
|
|
|
|
return {
|
|
calls,
|
|
routes,
|
|
options,
|
|
jsonBody,
|
|
setUserAuth(value) {
|
|
userAuth = value;
|
|
},
|
|
setLegacyAuth(value) {
|
|
legacyAuth = value;
|
|
},
|
|
route(method, path) {
|
|
return routes.find(
|
|
(route) =>
|
|
route.method === method &&
|
|
route.path === path,
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
async function invoke(setup, method, path, req = {}) {
|
|
const route = setup.route(method, path);
|
|
assert.ok(route, `${method.toUpperCase()} ${path}`);
|
|
const res = createResponse();
|
|
await route.handlers.at(-1)(
|
|
{
|
|
body: {},
|
|
ip: '127.0.0.1',
|
|
get: () => '',
|
|
...req,
|
|
},
|
|
res,
|
|
);
|
|
return res;
|
|
}
|
|
|
|
test('registers the core auth route inventory and middleware order', () => {
|
|
const setup = createSetup();
|
|
assert.deepEqual(
|
|
setup.routes.map(({ method, path }) => [
|
|
method,
|
|
path,
|
|
]),
|
|
[
|
|
['get', '/auth/status'],
|
|
['post', '/auth/login'],
|
|
['post', '/auth/wechat-miniapp/login'],
|
|
['post', '/auth/register'],
|
|
['post', '/auth/reset-password'],
|
|
],
|
|
);
|
|
for (const route of setup.routes.filter(
|
|
({ method }) => method === 'post',
|
|
)) {
|
|
assert.equal(route.handlers[0], setup.jsonBody);
|
|
}
|
|
});
|
|
|
|
test('returns multi-user status and preserves capability projection', async () => {
|
|
const setup = createSetup();
|
|
const res = await invoke(
|
|
setup,
|
|
'get',
|
|
'/auth/status',
|
|
);
|
|
|
|
assert.equal(res.statusCode, 200);
|
|
assert.deepEqual(res.body, {
|
|
authenticated: true,
|
|
user: { id: 'user-1', username: 'john' },
|
|
mode: 'user',
|
|
capabilities: ['chat'],
|
|
grantedSkills: ['web'],
|
|
unrestricted: false,
|
|
skillRuntime: { enabled: true },
|
|
agentCodeRun: { enabled: true, userId: 'user-1' },
|
|
goalRun: { enabled: true },
|
|
});
|
|
});
|
|
|
|
test('preserves unavailable, legacy, and disabled status modes', async () => {
|
|
const unavailable = createSetup({
|
|
isDatabaseConfiguredFn: () => true,
|
|
});
|
|
unavailable.setUserAuth(null);
|
|
assert.equal(
|
|
(
|
|
await invoke(
|
|
unavailable,
|
|
'get',
|
|
'/auth/status',
|
|
)
|
|
).statusCode,
|
|
503,
|
|
);
|
|
|
|
const legacy = createSetup();
|
|
legacy.setUserAuth(null);
|
|
legacy.setLegacyAuth({
|
|
verify: (token) => token === 'legacy-token',
|
|
});
|
|
assert.deepEqual(
|
|
(
|
|
await invoke(legacy, 'get', '/auth/status')
|
|
).body,
|
|
{ authenticated: true, mode: 'legacy' },
|
|
);
|
|
|
|
const disabled = createSetup();
|
|
disabled.setUserAuth(null);
|
|
assert.deepEqual(
|
|
(
|
|
await invoke(disabled, 'get', '/auth/status')
|
|
).body,
|
|
{ authenticated: false, mode: 'none' },
|
|
);
|
|
});
|
|
|
|
test('preserves multi-user and legacy login flows', async () => {
|
|
const setup = createSetup();
|
|
const userRes = await invoke(
|
|
setup,
|
|
'post',
|
|
'/auth/login',
|
|
{
|
|
body: {
|
|
username: 'john',
|
|
password: 'secret',
|
|
},
|
|
ip: '203.0.113.10',
|
|
},
|
|
);
|
|
assert.deepEqual(userRes.body, {
|
|
authenticated: true,
|
|
user: { id: 'user-1' },
|
|
mode: 'user',
|
|
sessionToken: 'user-token',
|
|
});
|
|
assert.ok(
|
|
setup.calls.some(
|
|
([name, token]) =>
|
|
name === 'set-user-cookies' &&
|
|
token === 'user-token',
|
|
),
|
|
);
|
|
|
|
const legacy = createSetup();
|
|
legacy.setUserAuth(null);
|
|
legacy.setLegacyAuth({
|
|
login(password, ip) {
|
|
assert.equal(password, 'legacy-secret');
|
|
assert.equal(ip, '127.0.0.1');
|
|
return { ok: true, token: 'legacy-session' };
|
|
},
|
|
});
|
|
const legacyRes = await invoke(
|
|
legacy,
|
|
'post',
|
|
'/auth/login',
|
|
{ body: { password: 'legacy-secret' } },
|
|
);
|
|
assert.deepEqual(legacyRes.body, {
|
|
authenticated: true,
|
|
mode: 'legacy',
|
|
});
|
|
assert.equal(
|
|
legacyRes.headers['Set-Cookie'],
|
|
'session=legacy-session',
|
|
);
|
|
});
|
|
|
|
test('preserves login validation and retry limits', async () => {
|
|
const missing = createSetup();
|
|
assert.equal(
|
|
(
|
|
await invoke(
|
|
missing,
|
|
'post',
|
|
'/auth/login',
|
|
)
|
|
).statusCode,
|
|
400,
|
|
);
|
|
|
|
const limited = createSetup();
|
|
limited.setUserAuth({
|
|
async login() {
|
|
return {
|
|
ok: false,
|
|
retryAfterMs: 1500,
|
|
message: 'limited',
|
|
};
|
|
},
|
|
});
|
|
const limitedRes = await invoke(
|
|
limited,
|
|
'post',
|
|
'/auth/login',
|
|
{
|
|
body: { username: 'john', password: 'bad' },
|
|
},
|
|
);
|
|
assert.equal(limitedRes.statusCode, 429);
|
|
assert.equal(limitedRes.headers['Retry-After'], '2');
|
|
});
|
|
|
|
test('preserves mini-program exchange and login mapping', async () => {
|
|
const setup = createSetup();
|
|
const res = await invoke(
|
|
setup,
|
|
'post',
|
|
'/auth/wechat-miniapp/login',
|
|
{ body: { code: ' code-1 ' } },
|
|
);
|
|
|
|
assert.deepEqual(res.body, {
|
|
authenticated: true,
|
|
user: { id: 'user-2' },
|
|
mode: 'user',
|
|
isNewUser: true,
|
|
sessionToken: 'mini-token',
|
|
});
|
|
assert.ok(
|
|
setup.calls.some(
|
|
([name, options]) =>
|
|
name === 'exchange-code' &&
|
|
options.code === 'code-1',
|
|
),
|
|
);
|
|
assert.ok(
|
|
setup.calls.some(
|
|
([name, options]) =>
|
|
name === 'miniapp-login' &&
|
|
options.openid === 'openid-1',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('preserves mini-program configuration and exchange failures', async () => {
|
|
const disabled = createSetup({
|
|
loadWechatMiniappConfigFn: () => ({
|
|
enabled: false,
|
|
}),
|
|
});
|
|
assert.equal(
|
|
(
|
|
await invoke(
|
|
disabled,
|
|
'post',
|
|
'/auth/wechat-miniapp/login',
|
|
{ body: { code: 'code-1' } },
|
|
)
|
|
).statusCode,
|
|
503,
|
|
);
|
|
|
|
const failed = createSetup({
|
|
async exchangeMiniProgramCodeFn() {
|
|
const error = new Error('code rejected');
|
|
error.code = 'wechat_miniapp_code_failed';
|
|
throw error;
|
|
},
|
|
});
|
|
const failedRes = await invoke(
|
|
failed,
|
|
'post',
|
|
'/auth/wechat-miniapp/login',
|
|
{ body: { code: 'code-1' } },
|
|
);
|
|
assert.equal(failedRes.statusCode, 401);
|
|
assert.deepEqual(failedRes.body, {
|
|
message: 'code rejected',
|
|
});
|
|
});
|
|
|
|
test('preserves registration attribution and password reset', async () => {
|
|
const setup = createSetup();
|
|
const registerRes = await invoke(
|
|
setup,
|
|
'post',
|
|
'/auth/register',
|
|
{
|
|
body: {
|
|
username: 'john',
|
|
password: 'secret',
|
|
displayName: 'John',
|
|
email: 'john@example.com',
|
|
utm_source: 'campaign',
|
|
utm_medium: 'wechat',
|
|
ref: 'ref-1',
|
|
},
|
|
},
|
|
);
|
|
assert.deepEqual(registerRes.body, {
|
|
ok: true,
|
|
user: { id: 'user-3' },
|
|
});
|
|
assert.ok(
|
|
setup.calls.some(
|
|
([name, payload, ip]) =>
|
|
name === 'attribution' &&
|
|
payload.user_id === 'user-3' &&
|
|
payload.utm_source === 'campaign' &&
|
|
ip === '203.0.113.5',
|
|
),
|
|
);
|
|
|
|
const resetRes = await invoke(
|
|
setup,
|
|
'post',
|
|
'/auth/reset-password',
|
|
{
|
|
body: {
|
|
username: 'john',
|
|
email: 'john@example.com',
|
|
password: 'new-secret',
|
|
},
|
|
},
|
|
);
|
|
assert.deepEqual(resetRes.body, { ok: true });
|
|
});
|