From 2310030f5fd2af6da0391f29d6a77dfb9b0fee9c Mon Sep 17 00:00:00 2001 From: Pranav Katariya Date: Wed, 28 Jan 2026 02:39:15 -0800 Subject: [PATCH] zoom: add redirectUri config for OAuth Add redirectUri prompt in onboarding and update docs. Fixes OAuth token exchange by ensuring redirect_uri matches Zoom app settings. --- docs/channels/zoom.md | 8 +++++ src/channels/plugins/onboarding/zoom.ts | 47 +++++++++++++++++++++---- src/zoom/config.ts | 3 ++ src/zoom/monitor.ts | 5 ++- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/docs/channels/zoom.md b/docs/channels/zoom.md index d0cc0d5e1..7dcc5b699 100644 --- a/docs/channels/zoom.md +++ b/docs/channels/zoom.md @@ -25,6 +25,7 @@ Status: production-ready plugin for Zoom Team Chat direct messages via Team Chat clientSecret: "YOUR_CLIENT_SECRET", botJid: "YOUR_BOT_JID@xmppdev.zoom.us", secretToken: "YOUR_SECRET_TOKEN", + redirectUri: "https://yourdomain.com/api/zoomapp/auth", dm: { policy: "open" } } } @@ -81,6 +82,12 @@ Replace `gateway-host` with: Subscribe to these event types: - `bot_notification` - Required for receiving messages +**Important**: The `redirectUri` in your Moltbot config must EXACTLY match the OAuth Redirect URL configured in your Zoom app. OAuth will fail if these don't match. + +Examples: +- Local dev: `https://abc123.ngrok.io/api/zoomapp/auth` +- Production: `https://yourdomain.com/api/zoomapp/auth` + ### 3. Install App to Your Account 1) In app settings, click "Install" @@ -100,6 +107,7 @@ Add to `~/.clawdbot/moltbot.json`: clientSecret: "YOUR_CLIENT_SECRET", botJid: "YOUR_BOT_JID@xmppdev.zoom.us", secretToken: "YOUR_SECRET_TOKEN", + redirectUri: "https://yourdomain.com/api/zoomapp/auth", // Must match Zoom app OAuth Redirect URL apiHost: "https://zoomdev.us", // Use https://api.zoom.us for production oauthHost: "https://zoomdev.us", // Use https://zoom.us for production dm: { diff --git a/src/channels/plugins/onboarding/zoom.ts b/src/channels/plugins/onboarding/zoom.ts index f6ea18100..34f1d97f4 100644 --- a/src/channels/plugins/onboarding/zoom.ts +++ b/src/channels/plugins/onboarding/zoom.ts @@ -129,6 +129,43 @@ export const zoomOnboardingAdapter: ChannelOnboardingAdapter = { }), ).trim(); + // Prompt for OAuth Redirect URI + await prompter.note( + [ + "OAuth Redirect URL is the public URL where Zoom will redirect after installation.", + "", + "Examples:", + "- Local dev: https://abc123.ngrok.io/api/zoomapp/auth (use ngrok http 3001)", + "- Production: https://yourdomain.com/api/zoomapp/auth", + "", + "This MUST exactly match the OAuth Redirect URL configured in your Zoom app.", + ].join("\n"), + "OAuth Redirect URL", + ); + + const redirectUri = String( + await prompter.text({ + message: "OAuth Redirect URL", + placeholder: "https://yourdomain.com/api/zoomapp/auth", + validate: (value) => { + const trimmed = String(value ?? "").trim(); + if (!trimmed) return "OAuth Redirect URL is required"; + try { + const url = new URL(trimmed); + if (!url.protocol.startsWith("http")) { + return "URL must start with http:// or https://"; + } + if (!trimmed.endsWith("/api/zoomapp/auth")) { + return "URL should end with /api/zoomapp/auth"; + } + return undefined; + } catch { + return "Invalid URL format"; + } + }, + }), + ).trim(); + // Determine environment from Bot JID const isDev = botJid.includes("@xmppdev"); const apiHost = isDev ? "https://zoomdev.us" : "https://api.zoom.us"; @@ -145,6 +182,7 @@ export const zoomOnboardingAdapter: ChannelOnboardingAdapter = { clientSecret, botJid, secretToken, + redirectUri, apiHost, oauthHost, }, @@ -152,19 +190,16 @@ export const zoomOnboardingAdapter: ChannelOnboardingAdapter = { }; // Show URL configuration instructions + const gatewayHost = redirectUri.replace(/\/api\/zoomapp\/auth$/, ""); await prompter.note( [ "IMPORTANT: Configure these URLs in your Zoom app:", "", "Webhook URL (for receiving messages):", - " https://gateway-host/webhooks/zoom", + ` ${gatewayHost}/webhooks/zoom`, "", "OAuth Redirect URL (for app installation):", - " https://gateway-host/api/zoomapp/auth", - "", - "Replace gateway-host with:", - "- For local dev: Use ngrok (ngrok http 3001)", - "- For production: Your server's public domain", + ` ${redirectUri}`, "", "Subscribe to event type: bot_notification", ].join("\n"), diff --git a/src/zoom/config.ts b/src/zoom/config.ts index 71e9336cc..6f73c9a75 100644 --- a/src/zoom/config.ts +++ b/src/zoom/config.ts @@ -7,6 +7,7 @@ export const ZoomConfigSchema = z.object({ clientSecret: z.string().optional(), botJid: z.string().optional(), secretToken: z.string().optional(), + redirectUri: z.string().optional(), apiHost: z.string().optional(), oauthHost: z.string().optional(), dm: z @@ -26,6 +27,7 @@ export type ResolvedZoomAccount = { clientSecret?: string; botJid?: string; secretToken?: string; + redirectUri?: string; apiHost: string; oauthHost: string; config: ZoomConfig; @@ -46,6 +48,7 @@ export function resolveZoomAccount(params: { clientSecret: config.clientSecret, botJid: config.botJid, secretToken: config.secretToken, + redirectUri: config.redirectUri, apiHost: config.apiHost || "https://api.zoom.us", oauthHost: config.oauthHost || "https://zoom.us", config, diff --git a/src/zoom/monitor.ts b/src/zoom/monitor.ts index 6dcfb757b..03086132b 100644 --- a/src/zoom/monitor.ts +++ b/src/zoom/monitor.ts @@ -178,7 +178,10 @@ export async function monitorZoomProvider(opts: MonitorZoomOpts = {}): Promise