feat: secure organization provider credentials

This commit is contained in:
2026-07-10 22:04:43 +08:00
parent e049cb6880
commit 848f913597
51 changed files with 3280 additions and 230 deletions
+138 -32
View File
@@ -1,22 +1,37 @@
import type { Prisma, PrismaClient } from "@prisma/client";
import { InMemoryModelRegistry, type ModelRegistry } from "../agent/models.js";
import { lockActiveOrganization } from "../org/status.js";
import { decryptStoredProviderCredential } from "../connections/providerConnections.js";
import { openProviderProxyLease, type AgentProviderLease, type ProviderUpstreamCredential } from "../connections/providerProxy.js";
import { LocalSecretEnvelope } from "../security/secretEnvelope.js";
export type Env = Readonly<Record<string, string | undefined>>;
type EnvSource = Env | (() => Env);
const DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api";
const DEFAULT_SONNET_MODEL = "anthropic/claude-sonnet-5";
const DEFAULT_SONNET_LABEL = "Claude Sonnet 5";
const DEFAULT_AGENT_MAX_TURNS = 25;
export interface ProviderRuntimeSettings {
readonly id: string;
readonly baseUrl: string;
readonly authToken: string;
readonly anthropicApiKey: string;
readonly sdkEnv: Record<string, string | undefined>;
openAgentLease(input: { readonly runId: string }): Promise<AgentProviderLease>;
}
export interface ProviderLeaseContext {
readonly providerId: string;
readonly projectId: string;
readonly runId: string;
}
export type ProviderLeaseFactory = (
credential: ProviderUpstreamCredential,
context: ProviderLeaseContext,
) => Promise<AgentProviderLease>;
const DEFAULT_PROVIDER_LEASE_FACTORY: ProviderLeaseFactory = (credential) =>
openProviderProxyLease(credential);
export interface RuntimeScope {
readonly projectId?: string;
}
@@ -44,26 +59,11 @@ export class EnvRuntimeSettings implements RuntimeSettings {
}
async provider(providerId: string, scope?: RuntimeScope): Promise<ProviderRuntimeSettings> {
void providerId;
void scope;
if (providerId !== "openrouter") {
throw new Error(`unknown provider runtime settings: ${providerId}`);
}
const env = this.envSource();
const baseUrl = readEnv(env, "ANTHROPIC_BASE_URL") ?? DEFAULT_OPENROUTER_BASE_URL;
const authToken = requireSetting(env, "ANTHROPIC_AUTH_TOKEN");
const anthropicApiKey = readEnv(env, "ANTHROPIC_API_KEY") ?? "";
return {
id: providerId,
baseUrl,
authToken,
anthropicApiKey,
sdkEnv: {
ANTHROPIC_BASE_URL: baseUrl,
ANTHROPIC_AUTH_TOKEN: authToken,
ANTHROPIC_API_KEY: anthropicApiKey,
},
};
throw new Error(
"process-global provider credentials are disabled; use the project-scoped provider resolver",
);
}
async modelRegistry(scope?: RuntimeScope): Promise<ModelRegistry> {
@@ -79,10 +79,124 @@ export class EnvRuntimeSettings implements RuntimeSettings {
}
}
export class DatabaseRuntimeSettings implements RuntimeSettings {
private readonly envSettings: EnvRuntimeSettings;
constructor(
private readonly prisma: PrismaClient,
private readonly secrets: LocalSecretEnvelope,
env: EnvSource = process.env,
private readonly leaseFactory: ProviderLeaseFactory = DEFAULT_PROVIDER_LEASE_FACTORY,
) {
this.envSettings = new EnvRuntimeSettings(env);
}
async provider(providerId: string, scope?: RuntimeScope): Promise<ProviderRuntimeSettings> {
const projectId = scope?.projectId?.trim();
if (projectId === undefined || projectId === "") {
throw new Error("projectId is required to resolve provider credentials");
}
await this.prisma.$transaction(async (tx) => {
await loadActiveProviderSecret(tx, projectId, providerId);
});
return {
id: providerId,
openAgentLease: async ({ runId }) => this.prisma.$transaction(async (tx) => {
const resolved = await loadActiveProviderSecret(tx, projectId, providerId);
const secretVersion = resolved.connection.activeSecretVersion;
const credential = decryptStoredProviderCredential(this.secrets, {
organizationId: resolved.organizationId,
connectionId: resolved.connection.id,
providerId,
secretVersionId: secretVersion.id,
envelopeVersion: secretVersion.envelopeVersion,
keyId: secretVersion.keyId,
envelope: secretVersion.envelope,
});
return this.leaseFactory(
{
baseUrl: credential.baseUrl,
authToken: credential.authToken,
anthropicApiKey: credential.anthropicApiKey,
},
{ providerId, projectId, runId },
);
}),
};
}
modelRegistry(scope?: RuntimeScope): Promise<ModelRegistry> {
return this.envSettings.modelRegistry(scope);
}
runPolicy(input: RunPolicyInput): Promise<RunPolicy> {
return this.envSettings.runPolicy(input);
}
}
async function loadActiveProviderSecret(
tx: Prisma.TransactionClient,
projectId: string,
providerId: string,
): Promise<{
readonly organizationId: string;
readonly connection: {
readonly id: string;
readonly activeSecretVersion: {
readonly id: string;
readonly connectionId: string;
readonly envelopeVersion: number;
readonly keyId: string;
readonly envelope: Prisma.JsonValue;
readonly retiredAt: Date | null;
};
};
}> {
const project = await tx.project.findUnique({
where: { id: projectId },
select: { organizationId: true, archivedAt: true },
});
if (project === null || project.archivedAt !== null) {
throw new Error(`active project not found: ${projectId}`);
}
await lockActiveOrganization(tx, project.organizationId);
const connection = await tx.organizationProviderConnection.findUnique({
where: {
organizationId_providerId: {
organizationId: project.organizationId,
providerId,
},
},
include: { activeSecretVersion: true },
});
const activeSecretVersion = connection?.activeSecretVersion;
if (connection === null || connection.status !== "ACTIVE" ||
activeSecretVersion === null || activeSecretVersion === undefined || activeSecretVersion.retiredAt !== null ||
activeSecretVersion.connectionId !== connection.id) {
throw new Error(`active provider connection not found: ${providerId} for project ${projectId}`);
}
return {
organizationId: project.organizationId,
connection: { id: connection.id, activeSecretVersion },
};
}
export function createEnvRuntimeSettings(env: EnvSource = process.env): RuntimeSettings {
return new EnvRuntimeSettings(env);
}
export function createDatabaseRuntimeSettings(
prisma: PrismaClient,
secrets: LocalSecretEnvelope,
env: EnvSource = process.env,
leaseFactory: ProviderLeaseFactory = DEFAULT_PROVIDER_LEASE_FACTORY,
): RuntimeSettings {
return new DatabaseRuntimeSettings(prisma, secrets, env, leaseFactory);
}
export function defaultSonnetModel(env: Env = process.env): string {
return readEnv(env, "ANTHROPIC_DEFAULT_SONNET_MODEL") ?? DEFAULT_SONNET_MODEL;
}
@@ -109,14 +223,6 @@ function readEnv(env: Env, name: string): string | undefined {
return value === undefined || value === "" ? undefined : value;
}
function requireSetting(env: Env, name: string): string {
const value = readEnv(env, name);
if (value === undefined) {
throw new Error(`missing required runtime setting: ${name}`);
}
return value;
}
function positiveIntegerEnv(env: Env, name: string, fallback: number): number {
const raw = readEnv(env, name);
if (raw === undefined) return fallback;