feat: bootstrap commercial AI drama platform
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { dbGet, dbRun, withTransaction } from "../server/db.mjs";
|
||||
|
||||
const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787";
|
||||
|
||||
async function request(path, options = {}) {
|
||||
const response = await fetch(`${api}${path}`, {
|
||||
...options,
|
||||
headers: { "content-type": "application/json", ...(options.headers || {}) }
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
return { response, payload };
|
||||
}
|
||||
|
||||
async function login(email, deviceId, deviceLabel) {
|
||||
const result = await request("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "x-device-id": deviceId, "x-device-label": deviceLabel },
|
||||
body: JSON.stringify({ email, password: "Demo@123456" })
|
||||
});
|
||||
assert.equal(result.response.status, 200, `${email} 登录失败`);
|
||||
assert.ok(result.payload.session?.token, `${email} 未返回登录会话`);
|
||||
return {
|
||||
authorization: `Bearer ${result.payload.session.token}`,
|
||||
session: result.payload.session
|
||||
};
|
||||
}
|
||||
|
||||
const suffix = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
const ownerDeviceId = `smoke-owner-device-${suffix}`;
|
||||
const secondDeviceId = `smoke-owner-device-2-${suffix}`;
|
||||
const ownerLabel = "Device Risk Smoke Browser";
|
||||
const owner = await login("producer@local.test", ownerDeviceId, ownerLabel);
|
||||
let clientId = "";
|
||||
let clientKey = "";
|
||||
|
||||
try {
|
||||
assert.equal(owner.session.riskLevel, "high", "首次出现的设备应该标记为高风险");
|
||||
assert.ok(owner.session.riskScore >= 75, "首次设备风险分数不足");
|
||||
assert.ok(!JSON.stringify(owner.session).includes(ownerDeviceId), "登录响应不能返回原始设备 ID");
|
||||
|
||||
const firstDevices = await request("/api/auth/devices", { headers: owner });
|
||||
assert.equal(firstDevices.response.status, 200, "登录用户无法读取自己的设备列表");
|
||||
assert.ok(Array.isArray(firstDevices.payload.devices), "设备接口必须返回 devices 数组");
|
||||
const firstDevice = firstDevices.payload.devices.find((device) => device.label === ownerLabel);
|
||||
assert.ok(firstDevice, "首次登录没有登记设备");
|
||||
assert.equal(firstDevice.status, "known", "首次设备默认不能直接标记为信任");
|
||||
assert.ok(!JSON.stringify(firstDevice).includes(ownerDeviceId), "设备接口不能返回原始设备 ID");
|
||||
assert.ok(!Object.keys(firstDevice).some((key) => /(hash|token|secret|key)/i.test(key)), "设备接口不能返回设备摘要或令牌字段");
|
||||
const storedDevice = dbGet("SELECT device_key_hash, fingerprint_hash FROM auth_devices WHERE id = ?", [firstDevice.id]);
|
||||
assert.ok(storedDevice && /^[a-f0-9]{64}$/i.test(storedDevice.device_key_hash), "数据库必须保存设备 ID 的摘要");
|
||||
assert.notEqual(storedDevice.device_key_hash, ownerDeviceId, "数据库不能保存原始设备 ID");
|
||||
|
||||
const secondLogin = await login("producer@local.test", ownerDeviceId, ownerLabel);
|
||||
assert.ok(secondLogin.session.riskScore < owner.session.riskScore, "同设备再次登录的风险分数应该下降");
|
||||
assert.equal(secondLogin.session.riskLevel, "medium", "已知但未信任的设备应该是中风险");
|
||||
|
||||
const trusted = await request(`/api/auth/devices/${encodeURIComponent(firstDevice.id)}/trust`, { method: "POST", headers: owner, body: "{}" });
|
||||
assert.equal(trusted.response.status, 200, "信任设备失败");
|
||||
assert.equal(trusted.payload.device.status, "trusted", "设备信任状态没有落库");
|
||||
assert.ok(trusted.payload.devices.some((device) => device.id === firstDevice.id && device.status === "trusted"), "设备列表没有返回信任状态");
|
||||
|
||||
const trustedLogin = await login("producer@local.test", ownerDeviceId, ownerLabel);
|
||||
assert.equal(trustedLogin.session.riskLevel, "low", "信任设备再次登录应该是低风险");
|
||||
assert.ok(trustedLogin.session.riskScore < secondLogin.session.riskScore, "信任设备风险分数应该继续下降");
|
||||
|
||||
const untrusted = await request(`/api/auth/devices/${encodeURIComponent(firstDevice.id)}/untrust`, { method: "POST", headers: owner, body: "{}" });
|
||||
assert.equal(untrusted.response.status, 200, "取消设备信任失败");
|
||||
assert.equal(untrusted.payload.device.status, "known", "取消信任后设备状态不正确");
|
||||
|
||||
const secondDeviceLogin = await login("producer@local.test", secondDeviceId, "Second Smoke Browser");
|
||||
assert.equal(secondDeviceLogin.session.riskLevel, "high", "新设备应该重新标记为高风险");
|
||||
|
||||
const ownerEvents = await request("/api/auth/security-events", { headers: owner });
|
||||
assert.equal(ownerEvents.response.status, 200, "无法读取设备安全事件");
|
||||
const eventTypes = new Set(ownerEvents.payload.events.map((event) => event.eventType));
|
||||
assert.ok(eventTypes.has("device.first_seen"), "安全事件缺少 device.first_seen");
|
||||
assert.ok(eventTypes.has("device.trusted"), "安全事件缺少 device.trusted");
|
||||
assert.ok(eventTypes.has("device.untrusted"), "安全事件缺少 device.untrusted");
|
||||
assert.ok(!JSON.stringify(ownerEvents.payload.events).includes(ownerDeviceId), "安全事件不能记录原始设备 ID");
|
||||
|
||||
const systemDetail = await request("/api/system/users/u-owner", { headers: owner });
|
||||
assert.equal(systemDetail.response.status, 200, "系统管理员无法读取自己的设备风险详情");
|
||||
assert.ok(Array.isArray(systemDetail.payload.devices), "系统用户详情缺少设备风险列表");
|
||||
assert.ok(systemDetail.payload.devices.some((device) => device.latestRiskLevel === "high"), "系统用户详情没有显示高风险设备");
|
||||
assert.ok(systemDetail.payload.sessions.some((session) => session.riskLevel && session.device), "系统用户详情会话缺少设备风险字段");
|
||||
assert.ok(!JSON.stringify(systemDetail.payload).includes(ownerDeviceId), "系统用户详情不能返回原始设备 ID");
|
||||
|
||||
const deniedWriter = await login("writer@local.test", `smoke-writer-device-${suffix}`, "Writer Smoke Browser");
|
||||
const ordinarySystemDenied = await request("/api/system/users/u-owner", { headers: deniedWriter });
|
||||
assert.equal(ordinarySystemDenied.response.status, 403, "普通用户不能访问系统用户设备详情");
|
||||
|
||||
const createdClient = await request("/api/system/api-clients", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: owner.authorization,
|
||||
"x-organization-id": "org-studio-lab",
|
||||
"x-workspace-id": "ws-local-aidrama"
|
||||
},
|
||||
body: JSON.stringify({ name: `Device Risk Smoke ${suffix}`, scopes: ["jobs:read"] })
|
||||
});
|
||||
assert.equal(createdClient.response.status, 201, "创建设备风险 smoke API Client 失败");
|
||||
clientId = createdClient.payload.client.id;
|
||||
clientKey = createdClient.payload.clientKey;
|
||||
assert.ok(clientKey, "设备风险 smoke 没有拿到 API Client 密钥");
|
||||
|
||||
const apiClientDenied = await request("/api/auth/devices", { headers: { authorization: `Bearer ${clientKey}` } });
|
||||
assert.equal(apiClientDenied.response.status, 403, "API Client 不能管理浏览器设备");
|
||||
assert.equal(apiClientDenied.payload.error, "device_management_unavailable", "设备管理 API Client 错误码不稳定");
|
||||
|
||||
console.log(`device risk smoke passed: registration, trust boundary, risk scoring, and sensitive-field checks (${firstDevice.id})`);
|
||||
} finally {
|
||||
if (clientId) {
|
||||
withTransaction(() => {
|
||||
dbRun("DELETE FROM api_clients WHERE id = ?", [clientId]);
|
||||
dbRun("DELETE FROM audit_logs WHERE target_id = ?", [clientId]);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user