feat: add china auth providers

This commit is contained in:
Codex
2026-06-28 22:19:46 +08:00
parent 523b63c53b
commit dee82e670f
11 changed files with 1011 additions and 26 deletions

View File

@@ -1,5 +1,6 @@
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import http from 'node:http';
import net from 'node:net';
const DEFAULT_DATABASE_URL = 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
@@ -36,6 +37,7 @@ let serverProcess = null;
let serverLogs = '';
let legacyDisabledServer = null;
let legacyDisabledServerLogs = '';
let fakeWechatServer = null;
function buildUrl(path, query = {}) {
return buildUrlAt(apiBase, path, query);
@@ -200,6 +202,56 @@ async function startLegacyDisabledServer() {
return baseUrl;
}
async function startFakeWechatServer() {
const port = await getFreePort();
const baseUrl = `http://127.0.0.1:${port}`;
const requests = [];
fakeWechatServer = http.createServer((req, res) => {
const url = new URL(req.url || '/', baseUrl);
requests.push({
method: req.method,
pathname: url.pathname,
query: Object.fromEntries(url.searchParams.entries()),
});
if (url.pathname !== '/sns/jscode2session') {
res.writeHead(404, { 'content-type': 'application/json' });
res.end(JSON.stringify({ errcode: 404, errmsg: 'not found' }));
return;
}
if (
url.searchParams.get('appid') !== 'wx-smoke-appid' ||
url.searchParams.get('secret') !== 'wechat-app-secret-smoke' ||
url.searchParams.get('grant_type') !== 'authorization_code'
) {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ errcode: 40013, errmsg: 'invalid appid or secret' }));
return;
}
const jsCode = url.searchParams.get('js_code') || 'unknown';
res.writeHead(200, { 'content-type': 'application/json' });
res.end(
JSON.stringify({
openid: `openid-${jsCode}`,
session_key: `session-key-${jsCode}`,
unionid: 'unionid-smoke-user',
}),
);
});
await new Promise((resolve, reject) => {
fakeWechatServer.once('error', reject);
fakeWechatServer.listen(port, '127.0.0.1', resolve);
});
return {
endpoint: `${baseUrl}/sns/jscode2session`,
requests,
};
}
async function waitForProcessExit(child, timeoutMs = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
@@ -366,6 +418,10 @@ function stopServer() {
if (legacyDisabledServer && !legacyDisabledServer.killed) {
legacyDisabledServer.kill();
}
if (fakeWechatServer) {
fakeWechatServer.close();
fakeWechatServer = null;
}
}
async function testCatalogAndLearning() {
@@ -1426,6 +1482,7 @@ async function testTenantContentAssetsAndImports() {
}
async function testTenantAdminOps() {
const fakeWechat = await startFakeWechatServer();
const denied = await request('/api/tenant-admin/branding', {
method: 'PUT',
body: { brandName: '学生不能改品牌' },
@@ -1477,6 +1534,7 @@ async function testTenantAdminOps() {
configPublic: {
appId: 'wx-smoke-appid',
envVersion: 'trial',
endpoint: fakeWechat.endpoint,
},
secret: {
secretValue: 'wechat-app-secret-smoke',
@@ -1488,6 +1546,32 @@ async function testTenantAdminOps() {
assert.equal(authProvider.item?.secret?.hasSecretValue, true, 'auth provider should report masked secret status');
assert.ok(!JSON.stringify(authProvider).includes('wechat-app-secret-smoke'), 'auth provider response must not include secret plaintext');
const miniappLogin = await request('/api/auth/oauth/wechat-miniapp', {
userId: false,
method: 'POST',
body: {
code: 'integration-code-001',
profile: {
nickName: '微信烟测学生',
avatarUrl: 'https://example.test/avatar.png',
},
},
});
assert.equal(miniappLogin.provider, 'wechat-miniapp', 'wechat miniapp login should return provider');
assert.ok(miniappLogin.user?.id, 'wechat miniapp login should create or resolve user');
assert.ok(miniappLogin.session?.token?.startsWith('tk_'), 'wechat miniapp login should issue API session token');
assert.equal(miniappLogin.identity?.openId, 'openid-integration-code-001', 'wechat miniapp login should expose openId');
assert.equal(miniappLogin.identity?.unionId, 'unionid-smoke-user', 'wechat miniapp login should expose unionId');
assert.equal(fakeWechat.requests.at(-1)?.query?.js_code, 'integration-code-001', 'wechat code should be exchanged server-side');
assert.ok(!JSON.stringify(miniappLogin).includes('session-key-integration-code-001'), 'login response must not leak WeChat session_key');
assert.ok(!JSON.stringify(miniappLogin).includes('wechat-app-secret-smoke'), 'login response must not leak app secret');
const miniappMe = await request('/api/auth/me', {
userId: false,
headers: { authorization: `Bearer ${miniappLogin.session.token}` },
});
assert.equal(miniappMe.user?.id, miniappLogin.user.id, 'wechat session should work with auth/me');
const paymentAccount = await request('/api/tenant-admin/payment-accounts', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',