feat: secure organization Feishu credentials

This commit is contained in:
2026-07-10 23:13:11 +08:00
parent 848f913597
commit 44557da499
18 changed files with 1443 additions and 24 deletions
@@ -0,0 +1,59 @@
-- ADR-0024: one Organization-scoped customer Feishu application connection
-- with immutable encrypted credential versions. No Feishu plaintext column is
-- present in either table.
CREATE TABLE "OrganizationFeishuApplicationConnection" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"appIdentityFingerprint" TEXT NOT NULL,
"status" "OrganizationConnectionStatus" NOT NULL DEFAULT 'DRAFT',
"activeSecretVersionId" TEXT,
"activatedAt" TIMESTAMP(3),
"disabledAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "OrganizationFeishuApplicationConnection_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "FeishuApplicationCredentialVersion" (
"id" TEXT NOT NULL,
"connectionId" TEXT NOT NULL,
"version" INTEGER NOT NULL,
"envelopeVersion" INTEGER NOT NULL DEFAULT 1,
"keyId" TEXT NOT NULL,
"envelope" JSONB NOT NULL,
"createdByUserId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"retiredAt" TIMESTAMP(3),
CONSTRAINT "FeishuApplicationCredentialVersion_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "OrganizationFeishuApplicationConnection_organizationId_key"
ON "OrganizationFeishuApplicationConnection"("organizationId");
CREATE UNIQUE INDEX "OrganizationFeishuApplicationConnection_appIdentityFingerprint_key"
ON "OrganizationFeishuApplicationConnection"("appIdentityFingerprint");
CREATE UNIQUE INDEX "OrganizationFeishuApplicationConnection_activeSecretVersionId_key"
ON "OrganizationFeishuApplicationConnection"("activeSecretVersionId");
CREATE INDEX "OrganizationFeishuApplicationConnection_status_idx"
ON "OrganizationFeishuApplicationConnection"("status");
CREATE UNIQUE INDEX "FeishuApplicationCredentialVersion_connectionId_version_key"
ON "FeishuApplicationCredentialVersion"("connectionId", "version");
CREATE INDEX "FeishuApplicationCredentialVersion_connectionId_retiredAt_idx"
ON "FeishuApplicationCredentialVersion"("connectionId", "retiredAt");
CREATE INDEX "FeishuApplicationCredentialVersion_keyId_idx"
ON "FeishuApplicationCredentialVersion"("keyId");
CREATE INDEX "FeishuApplicationCredentialVersion_createdByUserId_idx"
ON "FeishuApplicationCredentialVersion"("createdByUserId");
ALTER TABLE "OrganizationFeishuApplicationConnection"
ADD CONSTRAINT "OrganizationFeishuApplicationConnection_organizationId_fkey"
FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "FeishuApplicationCredentialVersion"
ADD CONSTRAINT "FeishuApplicationCredentialVersion_connectionId_fkey"
FOREIGN KEY ("connectionId") REFERENCES "OrganizationFeishuApplicationConnection"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "FeishuApplicationCredentialVersion"
ADD CONSTRAINT "FeishuApplicationCredentialVersion_createdByUserId_fkey"
FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "OrganizationFeishuApplicationConnection"
ADD CONSTRAINT "OrganizationFeishuApplicationConnection_activeSecretVersionId_fkey"
FOREIGN KEY ("activeSecretVersionId") REFERENCES "FeishuApplicationCredentialVersion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+46
View File
@@ -42,6 +42,7 @@ model Organization {
teams Team[]
externalDirectoryConnections ExternalDirectoryConnection[]
providerConnections OrganizationProviderConnection[]
feishuApplicationConnection OrganizationFeishuApplicationConnection?
auditEntries AuditEntry[] @relation("organizationAudit")
@@index([status])
@@ -109,6 +110,51 @@ model User {
roleTriggerGrants RoleTriggerGrant[] @relation("roleGrantCreator")
auditEntries AuditEntry[] @relation("auditActor")
providerCredentialVersions ProviderCredentialVersion[] @relation("providerCredentialVersionCreator")
feishuCredentialVersions FeishuApplicationCredentialVersion[] @relation("feishuCredentialVersionCreator")
}
/// ADR-0024: exactly one customer-owned Feishu application connection may be
/// configured for an Organization. External Feishu identifiers are scoped by
/// this stable connection id, never treated as process-global identities.
model OrganizationFeishuApplicationConnection {
id String @id @default(cuid())
organizationId String @unique
appIdentityFingerprint String @unique
status OrganizationConnectionStatus @default(DRAFT)
activeSecretVersionId String? @unique
activatedAt DateTime?
disabledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
secretVersions FeishuApplicationCredentialVersion[] @relation("feishuCredentialVersions")
activeSecretVersion FeishuApplicationCredentialVersion? @relation("activeFeishuCredentialVersion", fields: [activeSecretVersionId], references: [id], onDelete: Restrict)
@@index([status])
}
/// ADR-0024: all Feishu app material, including provider-local app/bot ids, is
/// inside one immutable authenticated envelope version.
model FeishuApplicationCredentialVersion {
id String @id @default(cuid())
connectionId String
version Int
envelopeVersion Int @default(1)
keyId String
envelope Json
createdByUserId String?
createdAt DateTime @default(now())
retiredAt DateTime?
connection OrganizationFeishuApplicationConnection @relation("feishuCredentialVersions", fields: [connectionId], references: [id], onDelete: Cascade)
activeFor OrganizationFeishuApplicationConnection? @relation("activeFeishuCredentialVersion")
createdBy User? @relation("feishuCredentialVersionCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
@@unique([connectionId, version])
@@index([connectionId, retiredAt])
@@index([keyId])
@@index([createdByUserId])
}
/// ADR-0024: model-provider connection identity is stable and belongs to one