forked from EduCraft/curriculum-project-hub
feat: secure organization Feishu credentials
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user