forked from wangziqi/gongxue-base
feat: establish production SaaS foundation
This commit is contained in:
170
scripts/tenant-resolve-contract-test.js
Normal file
170
scripts/tenant-resolve-contract-test.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { HttpError } from '../apps/api/src/core/errors.ts';
|
||||
import { normalizeTenantHost, selectTenantLocator } from '../apps/api/src/features/tenant/locator.ts';
|
||||
import { resolveTenantRoute } from '../apps/api/src/features/tenant/routes.ts';
|
||||
|
||||
const TENANT_ID = '00000000-0000-4000-8000-000000000001';
|
||||
|
||||
function tenantRow(overrides = {}) {
|
||||
return {
|
||||
id: TENANT_ID,
|
||||
slug: 'campus-a',
|
||||
name: 'Campus A',
|
||||
status: 'active',
|
||||
mode: 'saas',
|
||||
host: 'campus-a.example.com',
|
||||
brand_name: 'Campus A',
|
||||
short_name: 'Campus A',
|
||||
slogan: null,
|
||||
logo_url: null,
|
||||
favicon_url: null,
|
||||
service_wechat: null,
|
||||
service_account_name: null,
|
||||
theme: {},
|
||||
public_assets: {},
|
||||
feature_flags: {},
|
||||
admin_feature_flags: {},
|
||||
public_config: {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function requestContext(url, headers = {}) {
|
||||
return {
|
||||
url: new URL(url),
|
||||
req: { headers },
|
||||
res: {},
|
||||
};
|
||||
}
|
||||
|
||||
async function expectHttpError(run, statusCode, code) {
|
||||
await assert.rejects(run, error => {
|
||||
assert.ok(error instanceof HttpError);
|
||||
assert.equal(error.statusCode, statusCode);
|
||||
assert.equal(error.code, code);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
assert.equal(normalizeTenantHost('Example.COM.:443'), 'example.com');
|
||||
assert.equal(normalizeTenantHost('[::1]:8787'), '::1');
|
||||
assert.equal(normalizeTenantHost('::1'), '::1');
|
||||
assert.equal(normalizeTenantHost('bad,forwarded.example.com'), '');
|
||||
|
||||
assert.deepEqual(selectTenantLocator({
|
||||
origin: 'https://Campus-A.Example.com:443',
|
||||
requestedHost: 'campus-a.example.com.',
|
||||
requestHost: 'api.example.com',
|
||||
isProduction: true,
|
||||
}), {
|
||||
ok: true,
|
||||
locator: {
|
||||
kind: 'host',
|
||||
host: 'campus-a.example.com',
|
||||
expectedTenantCode: null,
|
||||
source: 'browser',
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(selectTenantLocator({
|
||||
origin: 'null',
|
||||
referer: 'https://campus-a.example.com/pages/student/home',
|
||||
requestHost: 'api.example.com',
|
||||
isProduction: true,
|
||||
}), {
|
||||
ok: true,
|
||||
locator: {
|
||||
kind: 'host',
|
||||
host: 'campus-a.example.com',
|
||||
expectedTenantCode: null,
|
||||
source: 'browser',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(selectTenantLocator({
|
||||
origin: 'https://campus-a.example.com',
|
||||
requestedHost: 'campus-b.example.com',
|
||||
isProduction: true,
|
||||
}).code, 'TENANT_HOST_CONFLICT');
|
||||
|
||||
assert.deepEqual(selectTenantLocator({
|
||||
origin: 'http://localhost:5173',
|
||||
requestedHost: 'localhost:5173',
|
||||
tenantCode: 'campus-a',
|
||||
requestHost: 'localhost:8787',
|
||||
isProduction: false,
|
||||
}), {
|
||||
ok: true,
|
||||
locator: { kind: 'tenantCode', tenantCode: 'campus-a', source: 'local-development' },
|
||||
});
|
||||
|
||||
assert.deepEqual(selectTenantLocator({
|
||||
tenantCode: 'campus-a',
|
||||
requestHost: 'api.example.com',
|
||||
isProduction: true,
|
||||
}), {
|
||||
ok: true,
|
||||
locator: { kind: 'tenantCode', tenantCode: 'campus-a', source: 'headless-client' },
|
||||
});
|
||||
|
||||
assert.equal(selectTenantLocator({
|
||||
requestedHost: 'campus-a.example.com',
|
||||
requestHost: 'api.example.com',
|
||||
isProduction: true,
|
||||
}).code, 'TENANT_HOST_UNTRUSTED');
|
||||
|
||||
{
|
||||
const calls = [];
|
||||
const result = await resolveTenantRoute(
|
||||
requestContext('https://api.example.com/api/tenant/resolve?host=campus-a.example.com', {
|
||||
host: 'api.example.com',
|
||||
origin: 'https://campus-a.example.com',
|
||||
'x-forwarded-host': 'attacker.example.com',
|
||||
}),
|
||||
async (sql, params) => {
|
||||
calls.push({ sql, params });
|
||||
return tenantRow();
|
||||
},
|
||||
);
|
||||
assert.equal(result.tenant.id, TENANT_ID);
|
||||
assert.deepEqual(calls[0].params, ['campus-a.example.com']);
|
||||
assert.match(calls[0].sql, /d\.status = 'active' and t\.status = 'active'/);
|
||||
}
|
||||
|
||||
await expectHttpError(
|
||||
() => resolveTenantRoute(
|
||||
requestContext('https://api.example.com/api/tenant/resolve?host=unknown.example.com', {
|
||||
host: 'api.example.com',
|
||||
origin: 'https://unknown.example.com',
|
||||
}),
|
||||
async () => null,
|
||||
),
|
||||
404,
|
||||
'TENANT_DOMAIN_NOT_BOUND',
|
||||
);
|
||||
|
||||
await expectHttpError(
|
||||
() => resolveTenantRoute(
|
||||
requestContext('https://api.example.com/api/tenant/resolve?host=campus-a.example.com&tenantCode=campus-b', {
|
||||
host: 'api.example.com',
|
||||
origin: 'https://campus-a.example.com',
|
||||
}),
|
||||
async () => tenantRow(),
|
||||
),
|
||||
409,
|
||||
'TENANT_LOCATOR_CONFLICT',
|
||||
);
|
||||
|
||||
await expectHttpError(
|
||||
() => resolveTenantRoute(
|
||||
requestContext('https://api.example.com/api/tenant/resolve?host=campus-a.example.com', {
|
||||
host: 'api.example.com',
|
||||
origin: 'https://campus-b.example.com',
|
||||
}),
|
||||
async () => tenantRow(),
|
||||
),
|
||||
409,
|
||||
'TENANT_HOST_CONFLICT',
|
||||
);
|
||||
|
||||
console.log('[PASS] Tenant resolve fail-closed contract');
|
||||
Reference in New Issue
Block a user