Skip to content

Add Zoom OAuth Integration + Zoom Transcript Loader#239

Open
bradtaylorsf wants to merge 12 commits intostagingfrom
codex/create-zoom-oauth-credential-and-node
Open

Add Zoom OAuth Integration + Zoom Transcript Loader#239
bradtaylorsf wants to merge 12 commits intostagingfrom
codex/create-zoom-oauth-credential-and-node

Conversation

@bradtaylorsf
Copy link
Collaborator

@bradtaylorsf bradtaylorsf commented May 23, 2025

Zoom OAuth2 Integration PR

This PR introduces full OAuth2 support for Zoom, enabling users to authenticate their Zoom accounts and pull transcript recordings from selected Zoom meetings. It includes the UI, backend, and document loader components to support this integration end-to-end.


✅ Features Included

1. Zoom OAuth Credential Integration

  • New credential type: ZoomOAuth

  • OAuth login flow via /zoom-auth and /zoom-auth/callback routes

  • Authenticated user details + tokens are stored in a credential object:

    • zoomAccessToken
    • zoomRefreshToken
    • provider, providerId, email, fullName

2. Zoom Transcript Document Loader

  • New loader: Zoom Transcripts
  • Allows users to select Zoom meetings from the past 30 days
  • Downloads and parses .vtt transcript files from the meeting recordings
  • Supports optional text splitting and metadata filtering
  • Built-in handling for access token refresh on expiry

3. UI Components

  • ZoomAuthButton: Trigger Zoom OAuth popup
  • ZoomMeetingPicker: Dialog for selecting past meetings with available recordings
  • Dynamic field handling inside the DocStore and Credential UI

📦 New Dependencies

Added to packages/server/package.json:

"@giorgosavgeris/passport-zoom-oauth2": "^1.2.0"

🔐 New Environment Variables

Add the following to your .env file or hosting provider config:

ZOOM_CLIENT_ID=your-zoom-client-id
ZOOM_CLIENT_SECRET=your-zoom-client-secret
ZOOM_CALLBACK_URL=https://yourdomain.com/api/v1/zoom-auth/callback

This PR includes a sample Zoom App configured at:
👉 [Zoom Marketplace – Registered App](https://marketplace.zoom.us/develop/applications/DtYZJ0veRjCMo4BCuWDNbg/information)


🔍 Local Testing with Ngrok

To test OAuth locally:

  1. Run your local server:

    pnpm dev
  2. Start ngrok on the same port:

    npx ngrok http 3000
  3. Copy the HTTPS ngrok URL and update your .env:

    ZOOM_CALLBACK_URL=https://your-ngrok-id.ngrok.io/api/v1/zoom-auth/callback
  4. Update your Zoom App’s Redirect URL for OAuth in the [Zoom App Marketplace](https://marketplace.zoom.us/develop/applications/DtYZJ0veRjCMo4BCuWDNbg/information) to match your ngrok callback URL.


🧪 How to Test

  1. Go to “Add Credential” → Choose “Zoom OAuth” → Click “Authorize with Zoom”.
  2. Complete Zoom sign-in and approve access.
  3. Use the “Zoom Transcripts” loader in the DocStore → Select meetings with transcripts.
  4. Preview or Save to verify document ingestion works correctly.

@vercel
Copy link

vercel bot commented May 23, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
answerai-docs 🔄 Building (Inspect) Visit Preview May 23, 2025 0:26am

const router = express.Router()

// GET /api/v1/zoom-auth
router.get('/zoom-auth', zoomAuthController.authenticate)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix

AI 7 months ago

To address the missing rate limiting, we will introduce a rate-limiting middleware specifically for the zoom-auth route. We'll use the well-known express-rate-limit package, which is designed for this purpose. A rate limiter will be configured to allow a reasonable number of requests per minute for the /zoom-auth route. This ensures that the endpoint is protected from excessive or abusive traffic while maintaining legitimate user access.

Steps:

  1. Install the express-rate-limit package if it is not already included in the project.
  2. Import the express-rate-limit package into the file.
  3. Configure a rate limiter with appropriate settings (e.g., 100 requests per 15 minutes).
  4. Apply the rate limiter specifically to the /zoom-auth route.

Suggested changeset 1
packages/server/src/routes/zoom-auth/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/server/src/routes/zoom-auth/index.ts b/packages/server/src/routes/zoom-auth/index.ts
--- a/packages/server/src/routes/zoom-auth/index.ts
+++ b/packages/server/src/routes/zoom-auth/index.ts
@@ -1,11 +1,17 @@
 import express from 'express'
 import zoomAuthController from '../../controllers/zoom-auth/index'
 import passport from 'passport'
-
+import rateLimit from 'express-rate-limit'
 const router = express.Router()
 
+// Rate limiter configuration for zoom-auth
+const zoomAuthRateLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+   max: 100, // limit each IP to 100 requests per windowMs
+})
+
 // GET /api/v1/zoom-auth
-router.get('/zoom-auth', zoomAuthController.authenticate)
+router.get('/zoom-auth', zoomAuthRateLimiter, zoomAuthController.authenticate)
 
 // GET /api/v1/zoom-auth/callback
 router.get(
EOF
@@ -1,11 +1,17 @@
import express from 'express'
import zoomAuthController from '../../controllers/zoom-auth/index'
import passport from 'passport'

import rateLimit from 'express-rate-limit'
const router = express.Router()

// Rate limiter configuration for zoom-auth
const zoomAuthRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
})

// GET /api/v1/zoom-auth
router.get('/zoom-auth', zoomAuthController.authenticate)
router.get('/zoom-auth', zoomAuthRateLimiter, zoomAuthController.authenticate)

// GET /api/v1/zoom-auth/callback
router.get(
Copilot is powered by AI and may make mistakes. Always verify output.
@bradtaylorsf bradtaylorsf changed the title Add Zoom transcript loader with OAuth support Add Zoom OAuth Integration + Zoom Transcript Loader May 23, 2025
@ct3685
Copy link

ct3685 commented May 23, 2025

BWS on AAI Local is showing the following vars, is BWS missing one? According to the PR here its stating that a callback url is required, is this different on staging and production and hard coded in so we're just using ngrok locally to check?

ZOOM_CLIENT_ID
ZOOM_CLIENT_SECRET
ZOOM_SECRET

@ct3685 ct3685 self-requested a review May 23, 2025 19:56
@bradtaylorsf
Copy link
Collaborator Author

@diecoscai Please look at this and test it and fix any remaining issues with being able to pull zoom meetings in from using OAuth. thanks!

@bradtaylorsf bradtaylorsf marked this pull request as ready for review July 1, 2025 07:16
@bradtaylorsf
Copy link
Collaborator Author

It will need to be rebased, btw

Comment on lines +153 to +163
const response = await axios.get(endpoint, {
headers: {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json'
},
params: {
from: fromDate,
to: toDate,
page_size: params.pageSize || 30
}
})

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.
The
URL
of this request depends on a
user-provided value
.
The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To mitigate the SSRF vulnerability, we will restrict user input by:

  1. Validating params.accountId and params.userId against an allow-list or ensuring they conform to a strict format (e.g., alphanumeric and specific lengths).
  2. Optionally, we can hardcode or derive accountId and userId from a trusted source rather than directly using user-provided values.

For this fix:

  • Add validation logic for params.accountId and params.userId to ensure they are safe.
  • Reject requests with inappropriate or malicious values.
  • The validation logic will be added in the getOrganizationMeetings function before constructing the endpoint.

Suggested changeset 1
packages/server/src/controllers/zoom/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/server/src/controllers/zoom/index.ts b/packages/server/src/controllers/zoom/index.ts
--- a/packages/server/src/controllers/zoom/index.ts
+++ b/packages/server/src/controllers/zoom/index.ts
@@ -146,9 +146,19 @@
         const fromDate = params.fromDate || defaultFromDate.toISOString().split('T')[0]
         const toDate = params.toDate || new Date().toISOString().split('T')[0]
 
+        // Validate accountId and userId
+        const accountId = params.accountId;
+        if (!/^[a-zA-Z0-9_-]+$/.test(accountId)) {
+            throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid Account ID')
+        }
+
+        const userId = params.userId || 'me';
+        if (userId !== 'me' && !/^[a-zA-Z0-9_-]+$/.test(userId)) {
+            throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid User ID')
+        }
+
         // Use account-level endpoint for organization meetings
-        const userId = params.userId || 'me'
-        const endpoint = `https://api.zoom.us/v2/accounts/${params.accountId}/users/${userId}/recordings`
+        const endpoint = `https://api.zoom.us/v2/accounts/${accountId}/users/${userId}/recordings`
 
         const response = await axios.get(endpoint, {
             headers: {
EOF
@@ -146,9 +146,19 @@
const fromDate = params.fromDate || defaultFromDate.toISOString().split('T')[0]
const toDate = params.toDate || new Date().toISOString().split('T')[0]

// Validate accountId and userId
const accountId = params.accountId;
if (!/^[a-zA-Z0-9_-]+$/.test(accountId)) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid Account ID')
}

const userId = params.userId || 'me';
if (userId !== 'me' && !/^[a-zA-Z0-9_-]+$/.test(userId)) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid User ID')
}

// Use account-level endpoint for organization meetings
const userId = params.userId || 'me'
const endpoint = `https://api.zoom.us/v2/accounts/${params.accountId}/users/${userId}/recordings`
const endpoint = `https://api.zoom.us/v2/accounts/${accountId}/users/${userId}/recordings`

const response = await axios.get(endpoint, {
headers: {
Copilot is powered by AI and may make mistakes. Always verify output.
diecoscai and others added 6 commits July 14, 2025 22:41
…ist hidden unless no chief sidekick, and left navbar opens by default (closes #316)
- Add user context parameter to saveCustomTemplate service method
- Pass req.user from controller to service for proper authorization
- Add enforceAbility middleware to custom template route
- Fix 'Unauthorized public access to non-public chatflow' error

Resolves: Diego Ticket 1 - Template save functionality
- Simplify generateExportFlowData to pass complete chatflow object
- Add 404 error handling for missing chatflows
- Clean up marketplace-specific descriptions on import
- Improve error handling in canvas flow loading

Resolves: Diego Ticket 1 - Template export functionality
… approach

This PR refactors the default chatflow redirection mechanism by removing
server-side cookie dependency and implementing a cleaner client-side solution.

## Changes Made

### Removed Cookie Implementation
- Removed defaultChatflowId cookie reading from getCachedSession.ts
- Removed cookie setting functionality from authentication middleware
- Eliminated server-side cookie dependency for default chatflow handling

### Implemented Client-Side Solution
- Added ChatRedirectHandler component for managing chat redirects
- Created useRedirectToDefaultChatflow hook for redirect logic
- Added auth API client for /auth/me endpoint calls
- Enhanced authentication middleware with direct /auth/me endpoint

### Architecture Improvements
- Replaced server-side cookie management with API-based approach
- Improved debugging capabilities with client-side logic
- Reduced security concerns related to cookie handling
- Simplified state management by removing cookie dependencies

## Benefits
- Better separation of concerns (client handles UI, server provides data)
- Easier debugging and testing of redirect logic
- No cookie security implications
- More maintainable and modern approach
- Cleaner code architecture
router.get('/templates/:id', marketplacesController.getMarketplaceTemplate)

router.post('/custom', marketplacesController.saveCustomTemplate)
router.post('/custom', enforceAbility('CustomTemplate'), marketplacesController.saveCustomTemplate)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix

AI 7 months ago

To address the issue, we will add rate limiting to the route handler using the express-rate-limit package. This package allows us to define a maximum number of requests per time window for specific routes.

Steps to fix:

  1. Install the express-rate-limit package if it is not already installed.
  2. Import the package in the file.
  3. Define a rate limiter with appropriate settings (e.g., maximum requests per minute).
  4. Apply the rate limiter middleware to the specific route handler (router.post('/custom')).

This fix ensures that the route is protected against abuse while maintaining its functionality.

Suggested changeset 1
packages/server/src/routes/marketplaces/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/server/src/routes/marketplaces/index.ts b/packages/server/src/routes/marketplaces/index.ts
--- a/packages/server/src/routes/marketplaces/index.ts
+++ b/packages/server/src/routes/marketplaces/index.ts
@@ -3,4 +3,10 @@
 import enforceAbility from '../../middlewares/authentication/enforceAbility'
+import rateLimit from 'express-rate-limit'
 const router = express.Router()
 
+const limiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 100, // Limit each IP to 100 requests per windowMs
+});
+
 // READ
@@ -9,3 +15,3 @@
 
-router.post('/custom', enforceAbility('CustomTemplate'), marketplacesController.saveCustomTemplate)
+router.post('/custom', limiter, enforceAbility('CustomTemplate'), marketplacesController.saveCustomTemplate)
 
EOF
@@ -3,4 +3,10 @@
import enforceAbility from '../../middlewares/authentication/enforceAbility'
import rateLimit from 'express-rate-limit'
const router = express.Router()

const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
});

// READ
@@ -9,3 +15,3 @@

router.post('/custom', enforceAbility('CustomTemplate'), marketplacesController.saveCustomTemplate)
router.post('/custom', limiter, enforceAbility('CustomTemplate'), marketplacesController.saveCustomTemplate)

Copilot is powered by AI and may make mistakes. Always verify output.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots
9.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants