Files

198 lines
15 KiB
JavaScript

const base = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787";
const scopeHeaders = {
"x-organization-id": "org-studio-lab",
"x-workspace-id": "ws-local-aidrama",
"x-project-id": "thunder-mouth"
};
async function request(path, headers = ownerHeaders, init = {}) {
const response = await fetch(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers || {}) } });
const payload = await response.json();
return { response, payload };
}
function assert(condition, message) {
if (!condition) throw new Error(message);
}
async function login(email) {
const result = await request("/api/auth/login", {}, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ email, password: "Demo@123456" })
});
assert(result.response.ok, `${email} 登录失败`);
assert(result.payload.user?.id && result.payload.user.id !== result.payload.session.id, `${email} 登录响应不能把 session id 当作 user id`);
assert(result.payload.user.id === result.payload.context.currentUser.id, `${email} 登录响应 user 与 context.currentUser 不一致`);
return { authorization: `Bearer ${result.payload.session.token}` };
}
const ownerAuth = await login("producer@local.test");
const writerAuth = await login("writer@local.test");
const ownerHeaders = { ...ownerAuth, ...scopeHeaders };
const sessionTestAuth = await login("producer2@local.test");
const sessionTestHeaders = { ...sessionTestAuth };
const ownerSessions = await request("/api/auth/sessions", sessionTestHeaders);
assert(ownerSessions.response.ok && ownerSessions.payload.sessions.some((session) => session.current), "当前用户必须能看到当前登录会话");
const revokedOthers = await request("/api/auth/sessions/revoke-others", sessionTestHeaders, { method: "POST", body: "{}", headers: { "content-type": "application/json" } });
assert(revokedOthers.response.ok && revokedOthers.payload.sessions.some((session) => session.current), "撤销其他会话后当前会话必须保持有效");
const context = await request("/api/context", {});
assert(context.response.status === 401, "未登录请求不应访问 context");
const authenticatedContext = await request("/api/context", ownerHeaders);
assert(authenticatedContext.response.ok, "登录后的 context 不可访问");
assert(authenticatedContext.payload.platform.organizations.length >= 2, "没有多组织数据");
assert(authenticatedContext.payload.platform.workspaces.length >= 2, "没有多工作区数据");
assert(authenticatedContext.payload.context.permissions.includes("job:create"), "所有者缺少生成任务权限");
assert(authenticatedContext.payload.context.systemAdmin === true, "系统管理员身份未生效");
assert(authenticatedContext.payload.rolePermissions.some((role) => role.name === "组织所有者" && role.permissions.includes("organization:manage")), "管理员必须能读取服务端角色权限矩阵");
const ownerRolePolicies = await request("/api/organizations/org-studio-lab/role-policies", ownerHeaders);
assert(ownerRolePolicies.response.ok, "组织管理员无法读取角色权限策略");
assert(ownerRolePolicies.payload.roles.some((role) => role.key === "writer"), "角色策略目录缺少编剧角色");
assert(ownerRolePolicies.payload.permissions.some((permission) => permission.key === "voice:approve"), "角色策略目录缺少声音审批权限");
const writerRolePolicy = ownerRolePolicies.payload.roles.find((role) => role.key === "writer");
const policyPermissionKey = "voice:approve";
const originalWriterPermission = writerRolePolicy.permissions.includes(policyPermissionKey);
const writerRolePolicies = await request("/api/organizations/org-studio-lab/role-policies", { ...writerAuth, ...scopeHeaders });
assert(writerRolePolicies.response.status === 403, "普通用户不应读取组织角色权限策略");
try {
const grantedWriterPolicy = await request("/api/organizations/org-studio-lab/role-policies/writer", ownerHeaders, {
method: "PATCH",
body: JSON.stringify({ permissionKey: policyPermissionKey, enabled: true }),
headers: { "content-type": "application/json" }
});
assert(grantedWriterPolicy.response.ok && grantedWriterPolicy.payload.roles.find((role) => role.key === "writer")?.permissions.includes(policyPermissionKey), "管理员授予组织角色权限失败");
const writerAfterGrant = await request("/api/context", { ...writerAuth, ...scopeHeaders });
assert(writerAfterGrant.response.ok && writerAfterGrant.payload.context.permissions.includes(policyPermissionKey), "组织角色授权没有进入普通用户有效权限");
const revokedWriterPolicy = await request("/api/organizations/org-studio-lab/role-policies/writer", ownerHeaders, {
method: "PATCH",
body: JSON.stringify({ permissionKey: policyPermissionKey, enabled: false }),
headers: { "content-type": "application/json" }
});
assert(revokedWriterPolicy.response.ok && !revokedWriterPolicy.payload.roles.find((role) => role.key === "writer")?.permissions.includes(policyPermissionKey), "管理员撤销组织角色权限失败");
const writerAfterRevoke = await request("/api/context", { ...writerAuth, ...scopeHeaders });
assert(writerAfterRevoke.response.ok && !writerAfterRevoke.payload.context.permissions.includes(policyPermissionKey), "组织角色撤销没有从普通用户有效权限移除");
} finally {
await request("/api/organizations/org-studio-lab/role-policies/writer", ownerHeaders, {
method: "PATCH",
body: JSON.stringify({ permissionKey: policyPermissionKey, enabled: originalWriterPermission }),
headers: { "content-type": "application/json" }
});
}
const lockedOwnerPolicy = await request("/api/organizations/org-studio-lab/role-policies/org_owner", ownerHeaders, {
method: "PATCH",
body: JSON.stringify({ permissionKey: policyPermissionKey, enabled: false }),
headers: { "content-type": "application/json" }
});
assert(lockedOwnerPolicy.response.status === 400 && lockedOwnerPolicy.payload.error === "owner_policy_locked", "组织所有者策略必须保持锁定");
const systemConfig = await request("/api/system/config", ownerHeaders);
assert(systemConfig.response.ok, "系统管理员无法访问系统配置");
const ownerModels = await request("/api/platform/models", ownerHeaders);
assert(ownerModels.response.ok, "系统管理员无法访问模型中台");
const ownerCosts = await request("/api/platform/costs", ownerHeaders);
assert(ownerCosts.response.ok, "系统管理员无法访问成本中心");
const ownerCompliance = await request("/api/platform/compliance", ownerHeaders);
assert(ownerCompliance.response.ok, "系统管理员无法访问合规中心");
const ownerOrganizationPath = await request("/api/organizations/org-northstar", ownerHeaders);
assert(ownerOrganizationPath.response.ok && ownerOrganizationPath.payload.organization.id === "org-northstar", "组织路径没有优先于旧上下文头");
const writerContext = await request("/api/context", { ...writerAuth, ...scopeHeaders });
assert(writerContext.response.ok, "普通用户 context 不可访问");
assert(!writerContext.payload.context.permissions.includes("system:settings:view"), "普通用户不应拥有系统配置权限");
assert(writerContext.payload.platform.members.length === 0, "普通用户不应收到组织成员明细");
assert(writerContext.payload.platform.workspaceMembers.length === 0, "普通用户不应收到工作区成员明细");
assert(writerContext.payload.platform.projectMembers.length === 0, "普通用户不应收到项目成员明细");
assert(writerContext.payload.platform.invitations.length === 0, "普通用户不应收到组织邀请明细");
assert(writerContext.payload.platform.billing === null, "普通用户不应收到计费账户");
assert(writerContext.payload.platform.usage === null, "普通用户不应收到用量明细");
assert(writerContext.payload.platform.auditLog.length === 0, "普通用户不应收到审计日志");
assert(writerContext.payload.platform.modelRegistry.length === 0, "普通用户不应收到模型连接器明细");
assert(writerContext.payload.platform.adapterCatalog.some((model) => model.id === "owned-i2v"), "有生成权限的普通用户必须收到可用适配器目录");
assert(writerContext.payload.platform.adapterCatalog.find((model) => model.id === "newapi-audio-production")?.approvalRequired === true, "混合音频连接器必须携带审批标记");
assert(writerContext.payload.platform.adapterCatalog.every((model) => !Object.prototype.hasOwnProperty.call(model, "endpoint")), "普通用户的适配器目录不应暴露连接器 Endpoint");
assert(writerContext.payload.platform.runnerHealth?.length === 0, "普通用户不应收到 Runner 健康明细");
const writerSystem = await request("/api/system/config", { ...writerAuth, ...scopeHeaders });
assert(writerSystem.response.status === 403, "普通用户不应访问系统配置");
const writerProjectPayload = await request("/api/project", { ...writerAuth, ...scopeHeaders });
assert(writerProjectPayload.response.ok, "普通用户应能读取当前项目生产数据");
assert((writerProjectPayload.payload.adapters || []).every((adapter) => !Object.prototype.hasOwnProperty.call(adapter, "endpoint") && !Object.prototype.hasOwnProperty.call(adapter, "baseUrl")), "项目接口不应向普通用户泄露连接器地址");
const writerModels = await request("/api/platform/models", { ...writerAuth, ...scopeHeaders });
assert(writerModels.response.status === 403, "普通用户不应访问模型中台");
const writerCosts = await request("/api/platform/costs", { ...writerAuth, ...scopeHeaders });
assert(writerCosts.response.status === 403, "普通用户不应访问成本中心");
const writerCompliance = await request("/api/platform/compliance", { ...writerAuth, ...scopeHeaders });
assert(writerCompliance.response.status === 403, "普通用户不应访问合规中心");
const writerMembers = await request("/api/organizations/org-studio-lab/members", { ...writerAuth, ...scopeHeaders });
assert(writerMembers.response.status === 403, "普通用户不应访问组织成员接口");
const writerQa = await request("/api/qa", { ...writerAuth, ...scopeHeaders });
assert(writerQa.response.status === 403, "普通用户不应执行审片接口");
const writerCreateOrganization = await request("/api/organizations", { ...writerAuth, ...scopeHeaders }, { method: "POST", body: JSON.stringify({ name: "blocked-org" }), headers: { "content-type": "application/json" } });
assert(writerCreateOrganization.response.status === 403, "普通用户不应创建组织");
const apiClientCreated = await request("/api/system/api-clients", ownerHeaders, {
method: "POST",
body: JSON.stringify({ name: `smoke-runner-${Date.now()}`, scopes: ["jobs:read", "jobs:write"] }),
headers: { "content-type": "application/json" }
});
assert(apiClientCreated.response.status === 201 && apiClientCreated.payload.clientKey, "系统 API 客户端必须返回一次性 client key");
const apiClientHeaders = { authorization: `Bearer ${apiClientCreated.payload.clientKey}`, ...scopeHeaders };
const apiClientContext = await request("/api/context", apiClientHeaders);
assert(apiClientContext.response.ok && apiClientContext.payload.context.systemAdmin === false, "API 客户端不应继承系统管理员身份");
assert(apiClientContext.payload.context.permissions.includes("job:create"), "API 客户端 jobs scope 未映射为生成权限");
const apiClientSystem = await request("/api/system/config", apiClientHeaders);
assert(apiClientSystem.response.status === 403, "API 客户端不应访问系统配置");
const revokeApiClient = await request(`/api/system/api-clients/${apiClientCreated.payload.client.id}`, ownerHeaders, {
method: "PATCH",
body: JSON.stringify({ status: "revoked" }),
headers: { "content-type": "application/json" }
});
assert(revokeApiClient.response.ok, "API 客户端撤销失败");
const revokedApiClient = await request("/api/context", apiClientHeaders);
assert(revokedApiClient.response.status === 401, "撤销 API 客户端后必须返回 401");
const externalJob = await request("/api/jobs", { ...writerAuth, ...scopeHeaders }, { method: "POST", body: JSON.stringify({ adapter: "newapi-audio-production", kind: "smoke external gate", shotId: "shot-01", output: "qa/smoke/external-gate.json" }), headers: { "content-type": "application/json" } });
assert(externalJob.response.status === 403 && externalJob.payload.error === "external_connector_requires_approval", "混合连接器未批准时必须由后端阻断");
const orgAdminAuth = await login("producer2@local.test");
const orgAdminHeaders = { ...orgAdminAuth, "x-organization-id": "org-northstar", "x-workspace-id": "ws-northstar-main", "x-project-id": "northstar-pilot" };
const orgAdminContext = await request("/api/context", orgAdminHeaders);
assert(orgAdminContext.response.ok, "组织管理员 context 不可访问");
assert(orgAdminContext.payload.context.systemAdmin === false, "组织管理员不应被识别为系统管理员");
assert(orgAdminContext.payload.context.permissions.includes("model:manage"), "组织管理员缺少模型管理权限");
const orgAdminModels = await request("/api/platform/models", orgAdminHeaders);
assert(orgAdminModels.response.ok, "组织管理员无法访问本组织模型中台");
const orgAdminQueue = await request("/api/admin/queue", orgAdminHeaders);
assert(orgAdminQueue.response.ok, "组织管理员无法访问本组织任务队列");
const orgAdminSystem = await request("/api/system/config", orgAdminHeaders);
assert(orgAdminSystem.response.status === 403, "组织管理员不应访问系统配置");
const northstar = await request("/api/context", {
...ownerAuth,
"x-organization-id": "org-northstar",
"x-workspace-id": "ws-northstar-main",
"x-project-id": "northstar-pilot"
});
assert(northstar.response.ok, "组织切换失败");
assert(northstar.payload.context.currentOrganization.id === "org-northstar", "组织 scope 没有切换");
assert(northstar.payload.platform.modelRegistry.every((model) => model.id === "northstar-image"), "模型没有按组织隔离");
const writerInvite = await request("/api/organizations/org-studio-lab/invitations", {
...writerAuth,
...scopeHeaders
}, { method: "POST", body: JSON.stringify({ email: "blocked-smoke@local.test", roleKey: "writer" }), headers: { "content-type": "application/json" } });
assert(writerInvite.response.status === 403 && writerInvite.payload.error === "permission_denied", "编剧越权邀请未被拒绝");
const crossOrg = await request("/api/context", {
...writerAuth,
"x-organization-id": "org-northstar",
"x-workspace-id": "ws-northstar-main"
});
assert(crossOrg.response.status === 403 && crossOrg.payload.error === "organization_forbidden", "跨组织访问未被拒绝");
console.log(`tenant smoke passed: ${base}`);