feat: bootstrap commercial AI drama platform

This commit is contained in:
xz
2026-08-24 10:24:34 +08:00
commit ffb27d845b
100 changed files with 35314 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
import { createHmac } from "node:crypto";
import { dbRun } from "../server/db.mjs";
const base = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787";
async function request(path, options = {}) {
const response = await fetch(`${base}${path}`, {
...options,
headers: { "content-type": "application/json", ...(options.headers || {}) }
});
const payload = await response.json().catch(() => ({}));
return { response, payload };
}
function assert(condition, message) {
if (!condition) throw new Error(message);
}
function totp(secret, timestamp = Date.now()) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const normalized = String(secret).replace(/=+$/g, "").toUpperCase();
let buffer = 0;
let bits = 0;
const bytes = [];
for (const character of normalized) {
buffer = (buffer << 5) | alphabet.indexOf(character);
bits += 5;
if (bits >= 8) {
bits -= 8;
bytes.push((buffer >> bits) & 0xff);
}
}
const counter = Math.floor(timestamp / 30000);
const counterBuffer = Buffer.alloc(8);
counterBuffer.writeBigUInt64BE(BigInt(counter));
const digest = createHmac("sha1", Buffer.from(bytes)).update(counterBuffer).digest();
const offset = digest[digest.length - 1] & 0x0f;
const value = ((digest[offset] & 0x7f) << 24) | ((digest[offset + 1] & 0xff) << 16) | ((digest[offset + 2] & 0xff) << 8) | (digest[offset + 3] & 0xff);
return String(value % 1000000).padStart(6, "0");
}
const ownerLogin = await request("/api/auth/login", {
method: "POST",
body: JSON.stringify({ email: "producer@local.test", password: "Demo@123456" })
});
assert(ownerLogin.response.ok && ownerLogin.payload.session?.token, "identity smoke owner login failed");
const ownerHeaders = { authorization: `Bearer ${ownerLogin.payload.session.token}` };
const writerLogin = await request("/api/auth/login", {
method: "POST",
body: JSON.stringify({ email: "writer@local.test", password: "Demo@123456" })
});
assert(writerLogin.response.ok && writerLogin.payload.session?.token, "identity smoke writer login failed");
const writerHeaders = { authorization: `Bearer ${writerLogin.payload.session.token}` };
const writerDenied = await request("/api/system/identity", { headers: writerHeaders });
assert(writerDenied.response.status === 403, "普通成员读取身份中心必须被后端拒绝");
const initial = await request("/api/system/identity", { headers: ownerHeaders });
assert(initial.response.ok && initial.payload.policy && Array.isArray(initial.payload.providers), "管理员读取身份中心失败");
const provider = await request("/api/system/identity/providers", {
method: "POST",
headers: ownerHeaders,
body: JSON.stringify({
name: `Smoke OIDC ${Date.now()}`,
kind: "oidc",
issuerUrl: "http://127.0.0.1:8799/issuer",
clientId: "smoke-client",
clientSecretRef: "SMOKE_OIDC_CLIENT_SECRET",
scopes: ["openid", "profile", "email"],
enabled: true
})
});
assert(provider.response.status === 201 && provider.payload.providers.some((item) => item.enabled), "OIDC 提供商登记失败");
const smokeProvider = provider.payload.providers.at(-1);
const policy = await request("/api/system/identity/policy", {
method: "PATCH",
headers: ownerHeaders,
body: JSON.stringify({ ssoEnabled: true, localLoginFallback: true, mfaRequiredForAdmins: true })
});
assert(policy.response.ok && policy.payload.policy.ssoEnabled && policy.payload.policy.mfaRequiredForAdmins, "身份策略保存失败");
const enrollmentLogin = await request("/api/auth/login", {
method: "POST",
body: JSON.stringify({ email: "producer@local.test", password: "Demo@123456" })
});
assert(enrollmentLogin.response.ok && enrollmentLogin.payload.mfaEnrollmentRequired && enrollmentLogin.payload.enrollmentToken, "强制管理员 MFA 必须进入首次绑定流程");
const enrollmentSetup = await request("/api/auth/mfa/enroll/setup", {
method: "POST",
body: JSON.stringify({ enrollmentToken: enrollmentLogin.payload.enrollmentToken })
});
assert(enrollmentSetup.response.status === 201 && enrollmentSetup.payload.setup?.secret, "MFA enrollment setup failed");
const enrollmentComplete = await request("/api/auth/mfa/enroll/enable", {
method: "POST",
body: JSON.stringify({ enrollmentToken: enrollmentLogin.payload.enrollmentToken, methodId: enrollmentSetup.payload.setup.methodId, code: totp(enrollmentSetup.payload.setup.secret) })
});
assert(enrollmentComplete.response.ok && enrollmentComplete.payload.session?.token, "MFA enrollment completion failed");
const publicProviders = await request("/api/auth/sso/providers");
assert(publicProviders.response.ok && publicProviders.payload.providers.some((item) => item.id === smokeProvider.id), "公开 SSO 提供商目录读取失败");
const directory = await request("/api/system/identity/directory-syncs", {
method: "POST",
headers: ownerHeaders,
body: JSON.stringify({ name: `Smoke SCIM ${Date.now()}`, syncMode: "provision-and-deprovision", schedule: "manual" })
});
assert(directory.response.status === 201 && directory.payload.token && directory.payload.directorySync?.endpointPath, "SCIM 目录创建失败");
const directoryId = directory.payload.directorySync.id;
const enabledDirectory = await request(`/api/system/identity/directory-syncs/${encodeURIComponent(directoryId)}`, {
method: "PATCH",
headers: ownerHeaders,
body: JSON.stringify({ enabled: true })
});
assert(enabledDirectory.response.ok && enabledDirectory.payload.directorySyncs.some((item) => item.id === directoryId && item.enabled), "SCIM 目录启用失败");
const scimHeaders = { authorization: `Bearer ${directory.payload.token}` };
const scimList = await request(`/scim/v2.0/${encodeURIComponent(directoryId)}/Users`, { headers: scimHeaders });
assert(scimList.response.ok && Array.isArray(scimList.payload.Resources), "SCIM 用户列表读取失败");
const scimEmail = `scim-${Date.now()}@local.test`;
const scimCreate = await request(`/scim/v2.0/${encodeURIComponent(directoryId)}/Users`, {
method: "POST",
headers: scimHeaders,
body: JSON.stringify({ userName: scimEmail, displayName: "SCIM 测试成员", active: true })
});
assert(scimCreate.response.status === 201 && scimCreate.payload.userName === scimEmail, "SCIM 用户创建失败");
const scimUserId = scimCreate.payload.id;
const scimPatch = await request(`/scim/v2.0/${encodeURIComponent(directoryId)}/Users/${encodeURIComponent(scimUserId)}`, {
method: "PATCH",
headers: scimHeaders,
body: JSON.stringify({ Operations: [{ op: "Replace", path: "active", value: false }] })
});
assert(scimPatch.response.ok && scimPatch.payload.active === false, "SCIM 用户停用失败");
const invalidScim = await request(`/scim/v2.0/${encodeURIComponent(directoryId)}/Users`, { headers: { authorization: "Bearer invalid-token" } });
assert(invalidScim.response.status === 401, "无效 SCIM 令牌必须被拒绝");
const cleanupMfa = await request("/api/auth/mfa/disable", {
method: "POST",
headers: { authorization: `Bearer ${enrollmentComplete.payload.session.token}` },
body: JSON.stringify({ currentPassword: "Demo@123456", code: totp(enrollmentSetup.payload.setup.secret) })
});
assert(cleanupMfa.response.ok && cleanupMfa.payload.enabled === false, "identity smoke cleanup MFA failed");
await request(`/api/system/identity/providers/${encodeURIComponent(smokeProvider.id)}`, {
method: "PATCH",
headers: ownerHeaders,
body: JSON.stringify({ enabled: false })
});
await request("/api/system/identity/policy", {
method: "PATCH",
headers: ownerHeaders,
body: JSON.stringify({ ssoEnabled: false, mfaRequiredForAdmins: false })
});
dbRun("DELETE FROM directory_sync_tokens WHERE directory_sync_id IN (SELECT id FROM directory_syncs WHERE name LIKE 'Smoke SCIM %')");
dbRun("DELETE FROM directory_syncs WHERE name LIKE 'Smoke SCIM %'");
dbRun("DELETE FROM identity_providers WHERE name LIKE 'Smoke OIDC %'");
dbRun("DELETE FROM users WHERE email = ?", [scimEmail]);
console.log(`identity smoke passed: ${base}`);