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
+45 -16
View File
@@ -8,6 +8,7 @@ import { PrismaClient } from "@prisma/client";
import { parse } from "dotenv";
import { LocalSecretEnvelope, loadLocalSecretKeyring } from "../security/secretEnvelope.js";
import { verifyStoredProviderEnvelopes } from "../connections/providerConnections.js";
import { verifyStoredFeishuApplicationEnvelopes } from "../connections/feishuApplicationConnections.js";
import {
DeploymentPreflightError,
validateDeploymentPreflight,
@@ -218,32 +219,41 @@ async function validatePostgresQuery(
log: [],
});
let queryFailure: unknown;
let envelopeFailure: unknown;
const envelopeFailures: Array<{ readonly kind: "provider" | "Feishu"; readonly error: unknown }> = [];
try {
await client.$queryRawUnsafe("SELECT 1");
const migrationTable = await client.$queryRaw<Array<{ relation: string | null }>>`
SELECT to_regclass('public."_prisma_migrations"')::text AS "relation"
`;
if (migrationTable[0]?.relation === null || migrationTable[0]?.relation === undefined) {
console.log("[preflight] empty database has no migration table or stored provider envelopes");
console.log("[preflight] empty database has no migration table or stored connection envelopes");
} else {
const migration = await client.$queryRaw<Array<{ finished_at: Date | null; rolled_back_at: Date | null }>>`
SELECT "finished_at", "rolled_back_at"
FROM "_prisma_migrations"
WHERE "migration_name" = '20260710220000_org_provider_secret_envelopes'
ORDER BY "started_at" DESC
LIMIT 1
`;
const applied = migration[0]?.finished_at !== null && migration[0]?.finished_at !== undefined &&
migration[0]?.rolled_back_at === null;
if (!applied) {
const providerApplied = await migrationApplied(
client,
"20260710220000_org_provider_secret_envelopes",
);
if (!providerApplied) {
console.log("[preflight] provider envelope migration is pending; no stored envelopes to authenticate");
} else {
try {
const verified = await verifyStoredProviderEnvelopes(client, secretEnvelope);
console.log(`[preflight] authenticated ${verified} stored provider envelope(s)`);
} catch (error) {
envelopeFailure = error;
envelopeFailures.push({ kind: "provider", error });
}
}
const feishuApplied = await migrationApplied(
client,
"20260710230000_org_feishu_application_secrets",
);
if (!feishuApplied) {
console.log("[preflight] Feishu envelope migration is pending; no stored envelopes to authenticate");
} else {
try {
const verified = await verifyStoredFeishuApplicationEnvelopes(client, secretEnvelope);
console.log(`[preflight] authenticated ${verified} stored Feishu envelope(s)`);
} catch (error) {
envelopeFailures.push({ kind: "Feishu", error });
}
}
}
@@ -272,15 +282,34 @@ async function validatePostgresQuery(
{ cause: disconnectFailure },
);
}
if (envelopeFailure !== undefined) {
if (envelopeFailures.length > 0) {
const failures = envelopeFailures.map(({ error }) => error);
const cause = failures.length === 1
? failures[0]
: new AggregateError(failures, "multiple connection envelope verifications failed");
const details = envelopeFailures
.map(({ kind, error }) => `${kind}: ${formatFailure(error)}`)
.join("; ");
throw new Error(
`stored provider envelope verification failed (${formatFailure(envelopeFailure)})`,
{ cause: envelopeFailure },
`stored connection envelope verification failed (${details})`,
{ cause },
);
}
console.log("[preflight] PostgreSQL authenticated query succeeded");
}
async function migrationApplied(client: PrismaClient, migrationName: string): Promise<boolean> {
const rows = await client.$queryRaw<Array<{ finished_at: Date | null; rolled_back_at: Date | null }>>`
SELECT "finished_at", "rolled_back_at"
FROM "_prisma_migrations"
WHERE "migration_name" = ${migrationName}
ORDER BY "started_at" DESC
LIMIT 1
`;
return rows[0]?.finished_at !== null && rows[0]?.finished_at !== undefined &&
rows[0]?.rolled_back_at === null;
}
async function validateProvisionedDirectories(
input: DeploymentPreflightInput,
serviceUid: number,