Skip to content

Commit fc3e69c

Browse files
khaliqgantclaude
andauthored
feat(cli): integration writeback-secret command (#342)
* feat(cli): integration writeback-secret command Adds `relayfile integration writeback-secret --channel CHANNEL [--json]`, which fetches the per-channel integration-bridge writeback signing secret + ingress URL from relayfile-cloud over the authenticated relayfile session (GET /v1/workspaces/:ws/integrations/relay/writeback-secret). The relay CLI calls this at `integration subscribe` time and signs the relay subscription with the returned secret, so the writeback ingress can verify deliveries without any statically-provisioned shared secret. The secret is derived server-side from the internal master key (per workspace+channel); this command just returns it to the authenticated workspace owner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): check resp.OK on writeback-secret response Addresses Gemini review on #342: explicitly fail when relayfile-cloud returns 200 with ok:false, instead of falling through to the generic empty-secret error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d25f478 commit fc3e69c

1 file changed

Lines changed: 72 additions & 1 deletion

File tree

cmd/relayfile-cli/main.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,8 @@ func printIntegrationUsage(w io.Writer, subcommand string) {
721721
relayfile integration adopt PROVIDER --connection-id ID [--workspace NAME] [--provider-config-key KEY] [--yes]
722722
relayfile integration set-metadata PROVIDER KEY=VALUE [KEY=VALUE...] [--workspace NAME] [--yes]
723723
relayfile integration bind PROVIDER PATH_GLOB --channel CHANNEL --webhook ID --webhook-token TOKEN
724-
relayfile integration unbind PROVIDER [PATH_GLOB|--resource PATH_GLOB]`)
724+
relayfile integration unbind PROVIDER [PATH_GLOB|--resource PATH_GLOB]
725+
relayfile integration writeback-secret --channel CHANNEL [--workspace WS] [--json]`)
725726
}
726727
}
727728

@@ -2286,6 +2287,8 @@ func runIntegration(args []string, stdin io.Reader, stdout io.Writer) error {
22862287
return runIntegrationBind(args[1:], stdout)
22872288
case "unbind":
22882289
return runIntegrationUnbind(args[1:], stdout)
2290+
case "writeback-secret":
2291+
return runIntegrationWritebackSecret(args[1:], stdout)
22892292
default:
22902293
return fmt.Errorf("unknown integration subcommand %q", args[0])
22912294
}
@@ -2436,6 +2439,74 @@ func runIntegrationUnbind(args []string, stdout io.Writer) error {
24362439
return nil
24372440
}
24382441

2442+
// writebackSecretResponse is the relayfile-cloud GET writeback-secret payload.
2443+
type writebackSecretResponse struct {
2444+
OK bool `json:"ok"`
2445+
Data struct {
2446+
URL string `json:"url"`
2447+
Secret string `json:"secret"`
2448+
} `json:"data"`
2449+
}
2450+
2451+
// runIntegrationWritebackSecret fetches the per-channel writeback signing secret
2452+
// (and ingress URL) for a relay channel from relayfile-cloud, over the
2453+
// authenticated relayfile session. The relay CLI calls this at `subscribe` time
2454+
// and signs the relay subscription with the returned secret, so there is no
2455+
// static shared secret to provision. The secret is derived server-side from the
2456+
// internal master key, so this endpoint just returns it to the workspace owner.
2457+
func runIntegrationWritebackSecret(args []string, stdout io.Writer) error {
2458+
fs := flag.NewFlagSet("integration writeback-secret", flag.ContinueOnError)
2459+
fs.SetOutput(io.Discard)
2460+
workspaceName := fs.String("workspace", "", "workspace name or id")
2461+
channel := fs.String("channel", "", "relay channel the binding delivers to")
2462+
jsonOutput := fs.Bool("json", false, "emit JSON")
2463+
if err := fs.Parse(normalizeFlagArgs(args, map[string]bool{
2464+
"workspace": true,
2465+
"channel": true,
2466+
"json": false,
2467+
})); err != nil {
2468+
return err
2469+
}
2470+
if fs.NArg() != 0 {
2471+
return errors.New("usage: relayfile integration writeback-secret --channel CHANNEL [--workspace WS] [--json]")
2472+
}
2473+
channelValue := strings.TrimSpace(*channel)
2474+
if channelValue == "" {
2475+
return errors.New("--channel is required")
2476+
}
2477+
2478+
commandClient, err := prepareWorkspaceCommandClient(strings.TrimSpace(*workspaceName), "", "", defaultJoinScopes)
2479+
if err != nil {
2480+
return err
2481+
}
2482+
workspaceID := strings.TrimSpace(commandClient.workspaceID)
2483+
if workspaceID == "" {
2484+
return errors.New("could not resolve relayfile workspace id")
2485+
}
2486+
2487+
path := fmt.Sprintf(
2488+
"/v1/workspaces/%s/integrations/relay/writeback-secret?channel=%s",
2489+
url.PathEscape(workspaceID),
2490+
url.QueryEscape(channelValue),
2491+
)
2492+
var resp writebackSecretResponse
2493+
if err := commandClient.client.getJSON(context.Background(), path, &resp); err != nil {
2494+
return fmt.Errorf("fetch writeback secret: %w", err)
2495+
}
2496+
if !resp.OK {
2497+
return errors.New("relayfile-cloud returned an unsuccessful writeback-secret response")
2498+
}
2499+
if strings.TrimSpace(resp.Data.Secret) == "" {
2500+
return errors.New("relayfile-cloud returned an empty writeback secret")
2501+
}
2502+
2503+
if *jsonOutput {
2504+
return writeJSON(stdout, resp.Data)
2505+
}
2506+
fmt.Fprintf(stdout, "url: %s\nsecret: %s\n", resp.Data.URL, resp.Data.Secret)
2507+
return nil
2508+
}
2509+
24392510
func runIntegrationConnect(args []string, stdin io.Reader, stdout io.Writer) error {
24402511
fs := flag.NewFlagSet("integration connect", flag.ContinueOnError)
24412512
fs.SetOutput(io.Discard)

0 commit comments

Comments
 (0)