Files
dsh_shenxian/scripts/smoke-subdomain.mjs
T
admin 5ec99ce227
build / build-and-scan (push) Canceled after 0s
chore: 文档库全量重新入库(2026-10-20)
2026-10-20 15:05:55 +08:00

111 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Subdomain routing + auth flow: a per-user `Host: <username>.<baseDomain>` routes
// to that user's DSH only when the caller's session cookie matches the subdomain.
import { request as httpRequest } from 'node:http'
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { buildServer } from '../lib/web/server.js'
import { resolveConfig } from '../lib/config.js'
import { hashPassword } from '../lib/web/auth.js'
function assert(condition, message) {
if (!condition) throw new Error('ASSERT: ' + message)
}
const here = dirname(fileURLToPath(import.meta.url))
const fakeDsh = join(here, 'fake-dsh.mjs')
const dataRoot = mkdtempSync(join(tmpdir(), 'dsh-smoke-subdomain-'))
const app = await buildServer(
resolveConfig({ port: 0, dbPath: ':memory:', dataRoot, baseDomain: 'test.local', dshCommand: [process.execPath, fakeDsh] }),
)
await app.listen({ port: 0 })
const port = app.server.address().port
await app.db.createUser({
id: 'u1',
username: 'Carol',
passHash: await hashPassword('carolpass123'),
role: 'active',
homeDir: '/tmp/u1-home',
})
await app.db.createUser({
id: 'u2',
username: 'bob',
passHash: await hashPassword('bobpass123'),
role: 'active',
homeDir: '/tmp/u2-home',
})
mkdirSync(join(dataRoot, 'users', 'u1', 'ws', 'proj'), { recursive: true })
async function json(path, { method = 'GET', body, cookie } = {}) {
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
method,
headers: { ...(body ? { 'content-type': 'application/json' } : {}), ...(cookie ? { cookie } : {}) },
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
return { status: res.status, body: text ? JSON.parse(text) : null, setCookie: res.headers.get('set-cookie') }
}
function getWithHost(path, host, cookie) {
return new Promise((resolve, reject) => {
const req = httpRequest(
{ hostname: '127.0.0.1', port, path, method: 'GET', headers: { host, ...(cookie ? { cookie } : {}) } },
(res) => {
let data = ''
res.on('data', (chunk) => (data += chunk))
res.on('end', () => resolve({ status: res.statusCode, body: data }))
},
)
req.on('error', reject)
req.end()
})
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
try {
let r = await json('/api/auth/login', { method: 'POST', body: { username: 'Carol', password: 'carolpass123' } })
const cookie = r.setCookie.split(';')[0]
r = await json('/api/auth/login', { method: 'POST', body: { username: 'bob', password: 'bobpass123' } })
const bobCookie = r.setCookie.split(';')[0]
r = await json('/api/dsh/launch', { method: 'POST', cookie, body: { folder: 'proj' } })
console.log('launch ->', r.status, r.body?.url)
assert(r.status === 200, 'launch succeeds')
// 2026-09-15(T08 S3):夹具 `fake-dsh.mjs` 现在**照实打印带 token 的 URL**(与真实 dsh 一致),
// 于是这里不能再写死成不带 token 的相等 —— 原断言是"夹具不吐 token"时的意外产物。
// 保留原意(是子域 URL、不泄露回环端口),并把 token 的存在一并纳入判据。
assert(r.body.url.startsWith('https://carol.test.local/'), 'launch returns the subdomain URL')
assert(!r.body.url.includes('127.0.0.1'), 'launch URL must not leak the loopback port')
await sleep(200)
let res = await getWithHost('/hello', 'carol.test.local', cookie)
console.log('authed /hello ->', res.status)
assert(res.status === 200 && res.body.includes('fake-dsh'), 'authed subdomain routes to the DSH')
res = await getWithHost('/hello', 'carol.test.local')
console.log('no-cookie /hello ->', res.status)
assert(res.status === 401, 'unauthenticated subdomain is rejected')
res = await getWithHost('/hello', 'carol.test.local', bobCookie)
console.log('bob /hello ->', res.status)
assert(res.status === 403, 'wrong user is rejected')
res = await getWithHost('/', `127.0.0.1:${port}`)
assert(!res.body.includes('fake-dsh'), 'non-subdomain Host does not proxy')
console.log('OK: subdomain routing + auth flow passed')
} finally {
await app.close()
await sleep(300)
try {
rmSync(dataRoot, { recursive: true, force: true })
} catch {
// best-effort cleanup
}
}