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.
This commit is contained in:
parent
bc109c4ae3
commit
2310030f5f
@ -25,6 +25,7 @@ Status: production-ready plugin for Zoom Team Chat direct messages via Team Chat
|
|||||||
clientSecret: "YOUR_CLIENT_SECRET",
|
clientSecret: "YOUR_CLIENT_SECRET",
|
||||||
botJid: "YOUR_BOT_JID@xmppdev.zoom.us",
|
botJid: "YOUR_BOT_JID@xmppdev.zoom.us",
|
||||||
secretToken: "YOUR_SECRET_TOKEN",
|
secretToken: "YOUR_SECRET_TOKEN",
|
||||||
|
redirectUri: "https://yourdomain.com/api/zoomapp/auth",
|
||||||
dm: { policy: "open" }
|
dm: { policy: "open" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,6 +82,12 @@ Replace `gateway-host` with:
|
|||||||
Subscribe to these event types:
|
Subscribe to these event types:
|
||||||
- `bot_notification` - Required for receiving messages
|
- `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
|
### 3. Install App to Your Account
|
||||||
|
|
||||||
1) In app settings, click "Install"
|
1) In app settings, click "Install"
|
||||||
@ -100,6 +107,7 @@ Add to `~/.clawdbot/moltbot.json`:
|
|||||||
clientSecret: "YOUR_CLIENT_SECRET",
|
clientSecret: "YOUR_CLIENT_SECRET",
|
||||||
botJid: "YOUR_BOT_JID@xmppdev.zoom.us",
|
botJid: "YOUR_BOT_JID@xmppdev.zoom.us",
|
||||||
secretToken: "YOUR_SECRET_TOKEN",
|
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
|
apiHost: "https://zoomdev.us", // Use https://api.zoom.us for production
|
||||||
oauthHost: "https://zoomdev.us", // Use https://zoom.us for production
|
oauthHost: "https://zoomdev.us", // Use https://zoom.us for production
|
||||||
dm: {
|
dm: {
|
||||||
|
|||||||
@ -129,6 +129,43 @@ export const zoomOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
}),
|
}),
|
||||||
).trim();
|
).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
|
// Determine environment from Bot JID
|
||||||
const isDev = botJid.includes("@xmppdev");
|
const isDev = botJid.includes("@xmppdev");
|
||||||
const apiHost = isDev ? "https://zoomdev.us" : "https://api.zoom.us";
|
const apiHost = isDev ? "https://zoomdev.us" : "https://api.zoom.us";
|
||||||
@ -145,6 +182,7 @@ export const zoomOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
botJid,
|
botJid,
|
||||||
secretToken,
|
secretToken,
|
||||||
|
redirectUri,
|
||||||
apiHost,
|
apiHost,
|
||||||
oauthHost,
|
oauthHost,
|
||||||
},
|
},
|
||||||
@ -152,19 +190,16 @@ export const zoomOnboardingAdapter: ChannelOnboardingAdapter = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Show URL configuration instructions
|
// Show URL configuration instructions
|
||||||
|
const gatewayHost = redirectUri.replace(/\/api\/zoomapp\/auth$/, "");
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
"IMPORTANT: Configure these URLs in your Zoom app:",
|
"IMPORTANT: Configure these URLs in your Zoom app:",
|
||||||
"",
|
"",
|
||||||
"Webhook URL (for receiving messages):",
|
"Webhook URL (for receiving messages):",
|
||||||
" https://gateway-host/webhooks/zoom",
|
` ${gatewayHost}/webhooks/zoom`,
|
||||||
"",
|
"",
|
||||||
"OAuth Redirect URL (for app installation):",
|
"OAuth Redirect URL (for app installation):",
|
||||||
" https://gateway-host/api/zoomapp/auth",
|
` ${redirectUri}`,
|
||||||
"",
|
|
||||||
"Replace gateway-host with:",
|
|
||||||
"- For local dev: Use ngrok (ngrok http 3001)",
|
|
||||||
"- For production: Your server's public domain",
|
|
||||||
"",
|
"",
|
||||||
"Subscribe to event type: bot_notification",
|
"Subscribe to event type: bot_notification",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export const ZoomConfigSchema = z.object({
|
|||||||
clientSecret: z.string().optional(),
|
clientSecret: z.string().optional(),
|
||||||
botJid: z.string().optional(),
|
botJid: z.string().optional(),
|
||||||
secretToken: z.string().optional(),
|
secretToken: z.string().optional(),
|
||||||
|
redirectUri: z.string().optional(),
|
||||||
apiHost: z.string().optional(),
|
apiHost: z.string().optional(),
|
||||||
oauthHost: z.string().optional(),
|
oauthHost: z.string().optional(),
|
||||||
dm: z
|
dm: z
|
||||||
@ -26,6 +27,7 @@ export type ResolvedZoomAccount = {
|
|||||||
clientSecret?: string;
|
clientSecret?: string;
|
||||||
botJid?: string;
|
botJid?: string;
|
||||||
secretToken?: string;
|
secretToken?: string;
|
||||||
|
redirectUri?: string;
|
||||||
apiHost: string;
|
apiHost: string;
|
||||||
oauthHost: string;
|
oauthHost: string;
|
||||||
config: ZoomConfig;
|
config: ZoomConfig;
|
||||||
@ -46,6 +48,7 @@ export function resolveZoomAccount(params: {
|
|||||||
clientSecret: config.clientSecret,
|
clientSecret: config.clientSecret,
|
||||||
botJid: config.botJid,
|
botJid: config.botJid,
|
||||||
secretToken: config.secretToken,
|
secretToken: config.secretToken,
|
||||||
|
redirectUri: config.redirectUri,
|
||||||
apiHost: config.apiHost || "https://api.zoom.us",
|
apiHost: config.apiHost || "https://api.zoom.us",
|
||||||
oauthHost: config.oauthHost || "https://zoom.us",
|
oauthHost: config.oauthHost || "https://zoom.us",
|
||||||
config,
|
config,
|
||||||
|
|||||||
@ -178,7 +178,10 @@ export async function monitorZoomProvider(opts: MonitorZoomOpts = {}): Promise<v
|
|||||||
const tokenUrl = `${account.oauthHost}/oauth/token`;
|
const tokenUrl = `${account.oauthHost}/oauth/token`;
|
||||||
const tokenParams = new URLSearchParams();
|
const tokenParams = new URLSearchParams();
|
||||||
tokenParams.set("grant_type", "authorization_code");
|
tokenParams.set("grant_type", "authorization_code");
|
||||||
tokenParams.set("redirect_uri", `${req.protocol}://${req.get("host")}/api/zoomapp/auth`);
|
// Use configured redirectUri or fall back to dynamically built one
|
||||||
|
const redirectUri =
|
||||||
|
account.redirectUri || `${req.protocol}://${req.get("host")}/api/zoomapp/auth`;
|
||||||
|
tokenParams.set("redirect_uri", redirectUri);
|
||||||
tokenParams.set("code", code);
|
tokenParams.set("code", code);
|
||||||
|
|
||||||
const authHeader = Buffer.from(`${account.clientId}:${account.clientSecret}`).toString(
|
const authHeader = Buffer.from(`${account.clientId}:${account.clientSecret}`).toString(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user