forked from bai/curriculum-project-hub
232 lines
9.1 KiB
TypeScript
232 lines
9.1 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { afterAll, beforeEach, describe, expect, it } from "vitest";
|
|
import {
|
|
FeishuApplicationConnectionService,
|
|
rewrapStoredFeishuApplicationEnvelopes,
|
|
resolveActiveFeishuApplication,
|
|
verifyStoredFeishuApplicationEnvelopes,
|
|
} from "../../src/connections/feishuApplicationConnections.js";
|
|
import { DEFAULT_ORG_ID, prisma, resetDb, seedTestOrganization, testSecretEnvelope } from "./helpers.js";
|
|
|
|
describe("Organization Feishu Application Connections", () => {
|
|
beforeEach(async () => {
|
|
await resetDb();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await prisma.$disconnect();
|
|
});
|
|
|
|
it("stores write-only app credentials and resolves them only from the explicit Organization", async () => {
|
|
await seedAdmin("feishu-admin", DEFAULT_ORG_ID, "ADMIN");
|
|
const service = new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {});
|
|
const credential = {
|
|
appId: "cli_shared_provider_local_id",
|
|
appSecret: "feishu-secret-never-return",
|
|
botOpenId: "ou_shared_bot",
|
|
verificationToken: "verification-never-return",
|
|
encryptKey: "encrypt-key-never-return",
|
|
};
|
|
|
|
const created = await service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-admin",
|
|
...credential,
|
|
});
|
|
|
|
expect(created).toMatchObject({
|
|
created: true,
|
|
status: "ACTIVE",
|
|
activeVersion: 1,
|
|
keyId: "test-active",
|
|
});
|
|
const persisted = await prisma.$queryRaw<Array<{ envelope: unknown }>>`
|
|
SELECT "envelope" FROM "FeishuApplicationCredentialVersion"
|
|
`;
|
|
const serialized = JSON.stringify(persisted);
|
|
for (const plaintext of Object.values(credential)) expect(serialized).not.toContain(plaintext);
|
|
|
|
await expect(resolveActiveFeishuApplication(prisma, testSecretEnvelope, {
|
|
organizationId: DEFAULT_ORG_ID,
|
|
})).resolves.toMatchObject({
|
|
connectionId: created.id,
|
|
organizationId: DEFAULT_ORG_ID,
|
|
...credential,
|
|
});
|
|
});
|
|
|
|
it("allows colliding provider-local identifiers in two connection namespaces", async () => {
|
|
await seedTestOrganization("org_feishu_other", "feishu-other");
|
|
await seedAdmin("feishu-admin-a", DEFAULT_ORG_ID, "OWNER");
|
|
await seedAdmin("feishu-admin-b", "org_feishu_other", "ADMIN");
|
|
const service = new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {});
|
|
const shared = {
|
|
appSecret: "same-secret-is-still-separately-encrypted",
|
|
botOpenId: "ou_same_bot",
|
|
};
|
|
const first = await service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-admin-a",
|
|
appId: "cli_app_a",
|
|
...shared,
|
|
});
|
|
const second = await service.rotateCustomerApplication({
|
|
organizationId: "org_feishu_other",
|
|
actorUserId: "feishu-admin-b",
|
|
appId: "cli_app_b",
|
|
...shared,
|
|
});
|
|
|
|
expect(first.id).not.toBe(second.id);
|
|
const resolved = await Promise.all([
|
|
resolveActiveFeishuApplication(prisma, testSecretEnvelope, { connectionId: first.id }),
|
|
resolveActiveFeishuApplication(prisma, testSecretEnvelope, { connectionId: second.id }),
|
|
]);
|
|
expect(resolved.map((item) => item.organizationId)).toEqual([DEFAULT_ORG_ID, "org_feishu_other"]);
|
|
});
|
|
|
|
it("refuses to relabel a stable connection or share one app across Organizations", async () => {
|
|
await seedTestOrganization("org_feishu_other", "feishu-other");
|
|
await seedAdmin("feishu-owner-a", DEFAULT_ORG_ID, "OWNER");
|
|
await seedAdmin("feishu-owner-b", "org_feishu_other", "OWNER");
|
|
const service = new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {});
|
|
await service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner-a",
|
|
appId: "cli_stable",
|
|
appSecret: "secret-one",
|
|
botOpenId: "ou_bot",
|
|
});
|
|
|
|
await expect(service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner-a",
|
|
appId: "cli_replacement",
|
|
appSecret: "replacement-secret",
|
|
botOpenId: "ou_replacement_bot",
|
|
})).rejects.toThrow("cannot change app identity");
|
|
await expect(service.rotateCustomerApplication({
|
|
organizationId: "org_feishu_other",
|
|
actorUserId: "feishu-owner-b",
|
|
appId: "cli_stable",
|
|
appSecret: "secret-one",
|
|
botOpenId: "ou_bot",
|
|
})).rejects.toThrow("cannot be shared across Organizations");
|
|
await expect(prisma.organizationFeishuApplicationConnection.count()).resolves.toBe(1);
|
|
await expect(prisma.feishuApplicationCredentialVersion.count()).resolves.toBe(1);
|
|
});
|
|
|
|
it("rotates immutable versions and rejects a non-admin service caller", async () => {
|
|
await seedAdmin("feishu-owner", DEFAULT_ORG_ID, "OWNER");
|
|
await seedAdmin("feishu-member", DEFAULT_ORG_ID, "MEMBER");
|
|
const service = new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {});
|
|
await service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner",
|
|
appId: "cli_one",
|
|
appSecret: "secret-one",
|
|
botOpenId: "ou_bot",
|
|
});
|
|
const rotated = await service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner",
|
|
appId: "cli_one",
|
|
appSecret: "secret-two",
|
|
botOpenId: "ou_bot",
|
|
});
|
|
expect(rotated).toMatchObject({ created: false, activeVersion: 2 });
|
|
await expect(prisma.feishuApplicationCredentialVersion.findMany({
|
|
orderBy: { version: "asc" },
|
|
select: { version: true, retiredAt: true },
|
|
})).resolves.toEqual([
|
|
{ version: 1, retiredAt: expect.any(Date) },
|
|
{ version: 2, retiredAt: null },
|
|
]);
|
|
|
|
await expect(service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-member",
|
|
appId: "cli_forbidden",
|
|
appSecret: "forbidden-secret",
|
|
botOpenId: "ou_bot",
|
|
})).rejects.toThrow("requires an active Organization OWNER or ADMIN");
|
|
await expect(prisma.feishuApplicationCredentialVersion.count()).resolves.toBe(2);
|
|
});
|
|
|
|
it("disables immediately through an authorized audited lifecycle write", async () => {
|
|
await seedAdmin("feishu-owner", DEFAULT_ORG_ID, "OWNER");
|
|
const service = new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {});
|
|
const created = await service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner",
|
|
appId: "cli_disable",
|
|
appSecret: "disable-secret",
|
|
botOpenId: "ou_bot",
|
|
});
|
|
|
|
await expect(service.disable({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner",
|
|
})).resolves.toMatchObject({ id: created.id, status: "DISABLED", activeVersion: 1 });
|
|
await expect(resolveActiveFeishuApplication(prisma, testSecretEnvelope, {
|
|
connectionId: created.id,
|
|
})).rejects.toThrow("active Feishu Application Connection not found");
|
|
await expect(prisma.auditEntry.count({
|
|
where: { organizationId: DEFAULT_ORG_ID, action: "feishu_application.disabled" },
|
|
})).resolves.toBe(1);
|
|
});
|
|
|
|
it("fails closed when the stored app fingerprint is detached from the encrypted app id", async () => {
|
|
await seedAdmin("feishu-owner", DEFAULT_ORG_ID, "OWNER");
|
|
const service = new FeishuApplicationConnectionService(prisma, testSecretEnvelope, async () => {});
|
|
const created = await service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner",
|
|
appId: "cli_original",
|
|
appSecret: "original-secret",
|
|
botOpenId: "ou_bot",
|
|
});
|
|
const replacementFingerprint = createHash("sha256")
|
|
.update("cph-feishu-app\0")
|
|
.update("cli_replacement", "utf8")
|
|
.digest("hex");
|
|
await prisma.organizationFeishuApplicationConnection.update({
|
|
where: { id: created.id },
|
|
data: { appIdentityFingerprint: replacementFingerprint },
|
|
});
|
|
|
|
await expect(resolveActiveFeishuApplication(prisma, testSecretEnvelope, {
|
|
connectionId: created.id,
|
|
})).rejects.toThrow("app identity fingerprint mismatch");
|
|
await expect(verifyStoredFeishuApplicationEnvelopes(
|
|
prisma,
|
|
testSecretEnvelope,
|
|
)).rejects.toThrow("app identity fingerprint mismatch");
|
|
await expect(prisma.$transaction((tx) => rewrapStoredFeishuApplicationEnvelopes(
|
|
tx,
|
|
testSecretEnvelope,
|
|
))).rejects.toThrow("app identity fingerprint mismatch");
|
|
await expect(service.rotateCustomerApplication({
|
|
organizationId: DEFAULT_ORG_ID,
|
|
actorUserId: "feishu-owner",
|
|
appId: "cli_replacement",
|
|
appSecret: "replacement-secret",
|
|
botOpenId: "ou_replacement_bot",
|
|
})).rejects.toThrow("app identity fingerprint mismatch");
|
|
await expect(prisma.feishuApplicationCredentialVersion.count()).resolves.toBe(1);
|
|
});
|
|
});
|
|
|
|
async function seedAdmin(
|
|
userId: string,
|
|
organizationId: string,
|
|
role: "OWNER" | "ADMIN" | "MEMBER",
|
|
): Promise<void> {
|
|
await prisma.user.create({
|
|
data: { id: userId, feishuOpenId: `legacy_${userId}`, displayName: userId },
|
|
});
|
|
await prisma.organizationMembership.create({
|
|
data: { organizationId, userId, role },
|
|
});
|
|
}
|