import type { ConnectionId, ScopeId } from "@executor-js/sdk/core"; import { oauth2ClientSecretSlot } from "./store";import type { ConfiguredHeaderValue, OpenApiSourceBindingValue } from "./types"; export type BindingRowForCredentialStatus = { readonly slot: string; readonly scopeId: ScopeId; readonly value: OpenApiSourceBindingValue;}; export type SourceForCredentialStatus = { readonly config: { readonly headers?: Record<string, ConfiguredHeaderValue>; readonly oauth2?: { readonly securitySchemeName: string; readonly flow: "authorizationCode" | "clientCredentials"; readonly clientIdSlot: string; readonly clientSecretSlot: string | null; readonly connectionSlot: string; }; };}; const scopeRank = (ranks: ReadonlyMap<string, number>, scopeId: ScopeId): number => ranks.get(scopeId) ?? Number.MAX_SAFE_INTEGER; export const effectiveBindingForScope = ( rows: readonly BindingRowForCredentialStatus[], slot: string, targetScope: ScopeId, ranks: ReadonlyMap<string, number>,): BindingRowForCredentialStatus | null => rows .filter( (row) => row.slot === slot && scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope), ) .sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0] ?? null; const hasSecretBinding = ( rows: readonly BindingRowForCredentialStatus[], slot: string, targetScope: ScopeId, ranks: ReadonlyMap<string, number>,) => effectiveBindingForScope(rows, slot, targetScope, ranks)?.value.kind === "secret"; const hasConnectionBinding = ( rows: readonly BindingRowForCredentialStatus[], slot: string, targetScope: ScopeId, ranks: ReadonlyMap<string, number>, liveConnectionIds?: ReadonlySet<string>,) => { const binding = effectiveBindingForScope(rows, slot, targetScope, ranks); if (binding?.value.kind !== "connection") return false; return liveConnectionIds ? liveConnectionIds.has(binding.value.connectionId) : true;}; const effectiveClientSecretSlot = (oauth2: { readonly securitySchemeName: string; readonly clientSecretSlot: string | null;}): string => oauth2.clientSecretSlot ?? oauth2ClientSecretSlot(oauth2.securitySchemeName); export function missingCredentialLabels( source: SourceForCredentialStatus, bindings: readonly BindingRowForCredentialStatus[], targetScope: ScopeId, ranks: ReadonlyMap<string, number>, options?: { readonly liveConnectionIds?: ReadonlySet<string> | readonly ConnectionId[]; },): string[] { const missing: string[] = []; const rawLiveConnectionIds = options?.liveConnectionIds; const liveConnectionIds = rawLiveConnectionIds ? rawLiveConnectionIds instanceof Set ? rawLiveConnectionIds : new Set(rawLiveConnectionIds) : undefined; for (const [headerName, value] of Object.entries(source.config.headers ?? {})) { if (typeof value === "string") continue; if (!hasSecretBinding(bindings, value.slot, targetScope, ranks)) { missing.push(headerName); } } const oauth2 = source.config.oauth2; if (!oauth2) return missing; if (!hasSecretBinding(bindings, oauth2.clientIdSlot, targetScope, ranks)) { missing.push("Client ID"); } const clientSecretSlot = effectiveClientSecretSlot(oauth2); if (!hasSecretBinding(bindings, clientSecretSlot, targetScope, ranks)) { missing.push("Client Secret"); } if ( !hasConnectionBinding(bindings, oauth2.connectionSlot, targetScope, ranks, liveConnectionIds) ) { missing.push(oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in"); } return missing;}
import type { ConnectionId, ScopeId } from "@executor-js/sdk/core";
import { oauth2ClientSecretSlot } from "./store";
import type { ConfiguredHeaderValue, OpenApiSourceBindingValue } from "./types";
export type BindingRowForCredentialStatus = {
readonly slot: string;
readonly scopeId: ScopeId;
readonly value: OpenApiSourceBindingValue;
};
export type SourceForCredentialStatus = {
readonly config: {
readonly headers?: Record<string, ConfiguredHeaderValue>;
readonly oauth2?: {
readonly securitySchemeName: string;
readonly flow: "authorizationCode" | "clientCredentials";
readonly clientIdSlot: string;
readonly clientSecretSlot: string | null;
readonly connectionSlot: string;
};
};
};
const scopeRank = (ranks: ReadonlyMap<string, number>, scopeId: ScopeId): number =>
ranks.get(scopeId) ?? Number.MAX_SAFE_INTEGER;
export const effectiveBindingForScope = (
rows: readonly BindingRowForCredentialStatus[],
slot: string,
targetScope: ScopeId,
ranks: ReadonlyMap<string, number>,
): BindingRowForCredentialStatus | null =>
rows
.filter(
(row) => row.slot === slot && scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope),
)
.sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0] ?? null;
const hasSecretBinding = (
rows: readonly BindingRowForCredentialStatus[],
slot: string,
targetScope: ScopeId,
ranks: ReadonlyMap<string, number>,
) => effectiveBindingForScope(rows, slot, targetScope, ranks)?.value.kind === "secret";
const hasConnectionBinding = (
rows: readonly BindingRowForCredentialStatus[],
slot: string,
targetScope: ScopeId,
ranks: ReadonlyMap<string, number>,
liveConnectionIds?: ReadonlySet<string>,
) => {
const binding = effectiveBindingForScope(rows, slot, targetScope, ranks);
if (binding?.value.kind !== "connection") return false;
return liveConnectionIds ? liveConnectionIds.has(binding.value.connectionId) : true;
};
const effectiveClientSecretSlot = (oauth2: {
readonly securitySchemeName: string;
readonly clientSecretSlot: string | null;
}): string => oauth2.clientSecretSlot ?? oauth2ClientSecretSlot(oauth2.securitySchemeName);
export function missingCredentialLabels(
source: SourceForCredentialStatus,
bindings: readonly BindingRowForCredentialStatus[],
targetScope: ScopeId,
ranks: ReadonlyMap<string, number>,
options?: {
readonly liveConnectionIds?: ReadonlySet<string> | readonly ConnectionId[];
},
): string[] {
const missing: string[] = [];
const rawLiveConnectionIds = options?.liveConnectionIds;
const liveConnectionIds = rawLiveConnectionIds
? rawLiveConnectionIds instanceof Set
? rawLiveConnectionIds
: new Set(rawLiveConnectionIds)
: undefined;
for (const [headerName, value] of Object.entries(source.config.headers ?? {})) {
if (typeof value === "string") continue;
if (!hasSecretBinding(bindings, value.slot, targetScope, ranks)) {
missing.push(headerName);
}
}
const oauth2 = source.config.oauth2;
if (!oauth2) return missing;
if (!hasSecretBinding(bindings, oauth2.clientIdSlot, targetScope, ranks)) {
missing.push("Client ID");
}
const clientSecretSlot = effectiveClientSecretSlot(oauth2);
if (!hasSecretBinding(bindings, clientSecretSlot, targetScope, ranks)) {
missing.push("Client Secret");
}
if (
!hasConnectionBinding(bindings, oauth2.connectionSlot, targetScope, ranks, liveConnectionIds)
) {
missing.push(oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in");
}
return missing;
}