import { useId, useState, type ReactNode } from "react"; import { ScopeId } from "@executor-js/sdk"; import { Button } from "../components/button"; import { Field, FieldGroup, FieldLabel } from "../components/field"; import { HelpTooltip } from "../components/help-tooltip"; import { Input } from "../components/input"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../components/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../components/select"; import { SecretForm } from "./secret-form"; import { SecretPicker, type SecretPickerSecret } from "./secret-picker"; import { CredentialTargetScopeSelector, type CredentialTargetScopeOption, } from "./credential-target-scope"; import { secretsForCredentialTarget } from "./secret-credential-scope"; export { secretsForCredentialTarget }; export interface HeaderAuthPreset { readonly key: string; readonly label: string; readonly name: string; readonly prefix?: string; } export const defaultHeaderAuthPresets: readonly HeaderAuthPreset[] = [ { key: "bearer", label: "Bearer Token", name: "Authorization", prefix: "Bearer ", }, { key: "basic", label: "Basic Auth", name: "Authorization", prefix: "Basic ", }, { key: "api-key", label: "API Key", name: "X-API-Key" }, { key: "auth-token", label: "Auth Token", name: "X-Auth-Token" }, { key: "access-token", label: "Access Token", name: "X-Access-Token" }, { key: "cookie", label: "Cookie", name: "Cookie" }, { key: "custom", label: "Custom", name: "" }, ]; function CreateSecretContent(props: { suggestedName: string; existingSecretIds: readonly string[]; onCreated: (secretId: string, scopeId: ScopeId) => void; onCancel?: () => void; fallbackId?: string; targetScope: ScopeId; credentialScopeOptions?: readonly CredentialTargetScopeOption[]; }) { const [scopeId, setScopeId] = useState(props.targetScope); const activeScope = props.credentialScopeOptions?.find((option) => option.scopeId === scopeId); return ( props.onCreated(secretId, scopeId)} >
{props.credentialScopeOptions && props.credentialScopeOptions.length > 1 && ( )}
{props.onCancel && ( )} Create and use
); } export function InlineCreateSecret(props: { suggestedName: string; existingSecretIds: readonly string[]; onCreated: (secretId: string, scopeId: ScopeId) => void; onCancel: () => void; fallbackId?: string; targetScope: ScopeId; credentialScopeOptions?: readonly CredentialTargetScopeOption[]; }) { return (

New secret

); } function CreateSecretDialog(props: { readonly open: boolean; readonly onOpenChange: (open: boolean) => void; readonly suggestedName: string; readonly existingSecretIds: readonly string[]; readonly onCreated: (secretId: string, scopeId: ScopeId) => void; readonly fallbackId?: string; readonly targetScope: ScopeId; readonly credentialScopeOptions?: readonly CredentialTargetScopeOption[]; }) { return ( New secret Create a reusable secret, then use it for this credential. props.onOpenChange(false)} targetScope={props.targetScope} credentialScopeOptions={props.credentialScopeOptions} /> ); } export type SecretCredentialPreviewProps = { readonly name: string; readonly secretId: string; readonly prefix?: string; }; export type SecretCredentialPreviewComponent = (props: SecretCredentialPreviewProps) => ReactNode; export function HeaderCredentialValuePreview(props: SecretCredentialPreviewProps) { const { name, prefix } = props; const maskedValue = "•".repeat(12); return (
{name}: {prefix && {prefix}} {maskedValue}
); } export function QueryParamCredentialValuePreview(props: SecretCredentialPreviewProps) { const { name, prefix } = props; const maskedValue = "•".repeat(12); return (
?{name}= {prefix && {prefix}} {maskedValue}
); } // --------------------------------------------------------------------------- // Header state helpers — shared by edit forms // --------------------------------------------------------------------------- export type HeaderState = { name: string; secretId: string | null; prefix?: string; presetKey?: string; fromPreset?: boolean; /** Scope where this source credential value is used. */ targetScope?: ScopeId; /** Scope that owns the selected reusable secret. */ secretScope?: ScopeId; }; export function matchPresetKey(name: string, prefix?: string): string { const preset = defaultHeaderAuthPresets.find((p) => p.name === name && p.prefix === prefix) ?? defaultHeaderAuthPresets.find((p) => p.name === name && p.prefix === undefined); return preset?.key ?? "custom"; } function InfoLabel(props: { readonly children: string; readonly tooltip: string }) { return (
{props.children} {props.tooltip}
); } export type SecretCredentialRowCopy = { readonly rowLabel: string; readonly nameLabel: string; readonly namePlaceholder: string; readonly prefixLabel: string; readonly prefixPlaceholder: string; readonly secretLabel: string; readonly secretHelp: string; readonly usedByLabel: string; readonly usedByHelp: string; }; const defaultSecretCredentialRowCopy: SecretCredentialRowCopy = { rowLabel: "Header", nameLabel: "Name", namePlaceholder: "Authorization", prefixLabel: "Prefix", prefixPlaceholder: "Bearer ", secretLabel: "Secret", secretHelp: "Select or create a reusable secret.", usedByLabel: "Used by", usedByHelp: "Choose who uses this credential value.", }; export function headerValueToState( name: string, value: { secretId: string; prefix?: string } | string, ): HeaderState { if (typeof value === "string") { return { name, secretId: null, presetKey: matchPresetKey(name, undefined) }; } return { name, secretId: value.secretId, prefix: value.prefix, presetKey: matchPresetKey(name, value.prefix), }; } export function headersFromState( entries: readonly HeaderState[], ): Record { const result: Record = {}; for (const entry of entries) { const name = entry.name.trim(); if (!name || !entry.secretId) continue; result[name] = { secretId: entry.secretId, ...(entry.prefix ? { prefix: entry.prefix } : {}), }; } return result; } // --------------------------------------------------------------------------- // Secret header auth row // --------------------------------------------------------------------------- export function SecretHeaderAuthRow(props: { name: string; prefix?: string; presetKey?: string; secretId: string | null; secretScope?: ScopeId; onChange: (update: { name: string; secretId?: string | null; prefix?: string; presetKey?: string; targetScope?: ScopeId; secretScope?: ScopeId; }) => void; onSelectSecret: (secretId: string, scopeId?: ScopeId) => void; existingSecrets: readonly SecretPickerSecret[]; onRemove?: () => void; removeLabel?: string; copy?: Partial; previewComponent?: SecretCredentialPreviewComponent; /** * Display name of the source this header belongs to (e.g. "Axiom"). Used * to prefix the suggested secret label and ID so tokens from different * sources don't collide on ids like `authorization`. */ sourceName?: string; targetScope: ScopeId; credentialScopeOptions?: readonly CredentialTargetScopeOption[]; bindingScopeOptions?: readonly CredentialTargetScopeOption[]; restrictSecretsToTargetScope?: boolean; }) { const [creating, setCreating] = useState(false); const nameInputId = useId(); const prefixInputId = useId(); const { name, prefix, presetKey, secretId, secretScope, onChange, onSelectSecret, existingSecrets, onRemove, removeLabel = "Remove", copy: copyOverride, previewComponent: PreviewComponent = HeaderCredentialValuePreview, sourceName, targetScope, credentialScopeOptions, bindingScopeOptions, restrictSecretsToTargetScope = false, } = props; const isCustom = presetKey === "custom" || presetKey === undefined; const copy = { ...defaultSecretCredentialRowCopy, ...copyOverride }; const headerLabel = name.trim() || "Custom Header"; const suggestedName = [sourceName?.trim(), headerLabel].filter(Boolean).join(" "); const scopedSecrets = secretsForCredentialTarget(existingSecrets, targetScope); const selectableSecrets = restrictSecretsToTargetScope ? scopedSecrets : existingSecrets; return (
secret.id)} onCreated={(id, scopeId) => { onSelectSecret(id, scopeId); setCreating(false); }} targetScope={targetScope} credentialScopeOptions={credentialScopeOptions} />
{copy.rowLabel} {onRemove && ( )}
{copy.nameLabel} onChange({ name: (e.target as HTMLInputElement).value, prefix, presetKey: isCustom ? "custom" : presetKey, }) } placeholder={copy.namePlaceholder} className="font-mono" /> {copy.prefixLabel}{" "} (optional) onChange({ name, prefix: (e.target as HTMLInputElement).value || undefined, presetKey: isCustom ? "custom" : presetKey, }) } placeholder={copy.prefixPlaceholder} className="font-mono" />
1 ? "grid gap-2 md:grid-cols-2" : undefined } >
{copy.secretLabel} onSelectSecret(id, ScopeId.make(scopeId))} secrets={selectableSecrets} onCreateNew={() => setCreating(true)} />
{bindingScopeOptions && bindingScopeOptions.length > 1 && (
{copy.usedByLabel}
)}
{secretId && name.trim() && ( )}
); } // --------------------------------------------------------------------------- // CreatableSecretPicker — SecretPicker + inline "+ New secret" create flow // --------------------------------------------------------------------------- export function CreatableSecretPicker(props: { readonly value: string | null; readonly onSelect: (secretId: string, scopeId?: ScopeId) => void; readonly secrets: readonly SecretPickerSecret[]; readonly placeholder?: string; readonly targetScope: ScopeId; readonly credentialScopeOptions?: readonly CredentialTargetScopeOption[]; readonly onCreatedScope?: (scopeId: ScopeId) => void; readonly suggestedId?: string; /** * Display name of the source the secret belongs to (e.g. "Stripe"). * Combined with `secretLabel` to produce a suggested name/ID. */ readonly sourceName?: string; /** Role of this secret (e.g. "Client ID", "API Token"). */ readonly secretLabel: string; }) { const { value, onSelect, secrets, placeholder, sourceName, secretLabel, targetScope, credentialScopeOptions, onCreatedScope, suggestedId: suggestedIdProp, } = props; const [creating, setCreating] = useState(false); const suggestedName = [sourceName?.trim(), secretLabel].filter(Boolean).join(" "); const scopedSecrets = secretsForCredentialTarget(secrets, targetScope); if (creating) { return ( secret.id)} fallbackId={suggestedIdProp?.trim() || "secret"} onCreated={(id, scopeId) => { onCreatedScope?.(scopeId); onSelect(id, scopeId); setCreating(false); }} targetScope={targetScope} credentialScopeOptions={credentialScopeOptions} /> ); } return ( onSelect(id, ScopeId.make(scopeId))} secrets={secrets} placeholder={placeholder} onCreateNew={() => setCreating(true)} /> ); }