-
Notifications
You must be signed in to change notification settings - Fork 0
feat: enhance Google OAuth integration and improve environment variab… #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…le handling - Added dotenv configuration to load environment variables from a .env file, ensuring proper overrides. - Updated Google OAuth routes to use '/oauth/google' instead of '/auth/google' for better clarity. - Enhanced logging for OAuth configuration and callback processes, including detailed output of redirect URIs and credentials. - Improved credential fetching logic in the frontend to handle workflow IDs and provide appropriate authentication URLs. - Refactored ConfigModal to utilize the new credential fetching mechanism, streamlining the user experience for connecting Google accounts.
📝 WalkthroughWalkthroughThis PR introduces workflow-aware OAuth authentication by threading a workflowId through the OAuth state and callback flow. Changes include environment loading at startup, refactored credential response endpoints with standardized structures, updated frontend credential management via a new hook pattern, and enhanced OAuth redirect URI handling. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant Frontend as Frontend (ConfigModal)
participant Backend as Backend (OAuth Routes)
participant Google as Google OAuth
User->>Frontend: Opens config modal with workflowId
Frontend->>Frontend: Calls useCredentials(type, workflowId)
Frontend->>Backend: GET /getCredentials/:type
Backend-->>Frontend: { data: [], hasCredentials: false }
Frontend->>Frontend: authUrl = /oauth/google?workflowId=...
Frontend->>User: Display "Connect Google Account" button
User->>Frontend: Click "Connect Google Account"
Frontend->>Backend: GET /oauth/google/initiate?workflowId=...
Backend->>Backend: state = "userId|workflowId"
Backend->>Google: Redirect to auth URL with state
Google->>User: Google login page
User->>Google: Authorize
Google->>Backend: Callback with code & state
Backend->>Backend: Parse state to userId & workflowId
Backend->>Google: Exchange code for tokens
Google-->>Backend: Access token, Refresh token
Backend->>Backend: Save tokens keyed by userId
Backend->>Frontend: Redirect to workflow-specific URL
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR enhances the Google OAuth integration by standardizing the OAuth route paths from /auth/google to /oauth/google and refactoring the credential fetching mechanism. The changes improve separation of concerns by moving OAuth URL construction from the backend to the frontend and add comprehensive logging for debugging OAuth flows.
Changes:
- Updated OAuth routes from
/auth/googleto/oauth/googleacross frontend and backend - Refactored credential fetching to use a custom React hook (
useCredentials) with cleaner state management - Modified backend credential API to return consistent response format with
data(lowercase) instead ofData - Added detailed OAuth logging and debug endpoints for troubleshooting
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/http-backend/src/index.ts | Updated OAuth route mount point and added environment variable logging |
| apps/http-backend/src/routes/google_callback.ts | Enhanced OAuth flow with workflowId support, comprehensive logging, and debug endpoints |
| apps/http-backend/src/routes/userRoutes/userRoutes.ts | Changed credential response format from Data to data (lowercase) |
| apps/web/app/hooks/useCredential.ts | Refactored to construct OAuth URLs on frontend and improved state management |
| apps/web/app/workflows/[id]/page.tsx | Added workflowId prop to ConfigModal |
| apps/web/app/workflows/[id]/components/ConfigModal.tsx | Refactored to use useCredentials hook instead of direct API calls |
| packages/nodes/src/common/google-oauth-service.ts | Added fallback for redirect URI environment variable |
Comments suppressed due to low confidence (1)
packages/nodes/src/common/google-oauth-service.ts:56
- Corrected spelling of 'Unknow' to 'Unknown'.
`Failed to exchange code for token: ${error instanceof Error ? error.message : "Unknow error"}`
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| if (state && typeof state === "string") { | ||
| const parts = state.split("|"); | ||
| userId = parts[0]; | ||
| workflowId = parts[1]; // Will be undefined if not provided | ||
| } | ||
|
|
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If state is undefined or not a string, userId remains undefined, but the code proceeds to use it without validation. This could cause the OAuth callback to fail silently when credentials cannot be saved. Add validation and error handling when userId is not available from the state parameter.
| if (state && typeof state === "string") { | |
| const parts = state.split("|"); | |
| userId = parts[0]; | |
| workflowId = parts[1]; // Will be undefined if not provided | |
| } | |
| // Validate state parameter | |
| if (!state || typeof state !== "string") { | |
| console.error("🔐 OAuth Callback - Missing or invalid state parameter:", state); | |
| return res.status(400).json({ error: "Missing or invalid state parameter" }); | |
| } | |
| const parts = state.split("|"); | |
| userId = parts[0] || undefined; | |
| workflowId = parts[1]; // Will be undefined if not provided | |
| if (!userId) { | |
| console.error("🔐 OAuth Callback - Missing userId in state parameter:", state); | |
| return res.status(400).json({ error: "Missing user identifier in state parameter" }); | |
| } |
| // Parse state to get workflowId for error redirect | ||
| let workflowId: string | undefined; | ||
| if (state && typeof state === "string") { | ||
| const parts = state.split("|"); | ||
| workflowId = parts[1]; | ||
| } |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable workflowId is re-declared in the catch block even though it was already declared in the outer scope at line 87. This shadowing is unnecessary and can be confusing. Remove the duplicate declaration and use the existing variable.
| // Parse state to get workflowId for error redirect | |
| let workflowId: string | undefined; | |
| if (state && typeof state === "string") { | |
| const parts = state.split("|"); | |
| workflowId = parts[1]; | |
| } |
| console.log("🔐 OAuth Initiate - Workflow ID:", workflowId || "NOT PROVIDED"); | ||
|
|
||
| // Encode userId and workflowId in state (format: userId|workflowId) | ||
| const state = workflowId ? `${userId}|${workflowId}` : userId; |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If userId is undefined (which can happen if authentication middleware fails), the state will be 'undefined' as a string. This creates an invalid state parameter that will fail to parse properly in the callback. Add validation to ensure userId exists before proceeding with OAuth initiation.
| import { getCredentials } from "../workflow/lib/config"; | ||
| import { BACKEND_URL } from "@repo/common/zod"; | ||
|
|
||
| export const useCredentials = (type: string, workflowId?: string): any => { |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type is any, which defeats the purpose of TypeScript's type safety. Define a proper return type interface such as { cred: any[], authUrl: string | null } to provide better type checking and IDE support.
| export const useCredentials = (type: string, workflowId?: string): any => { | |
| interface UseCredentialsResult { | |
| cred: any[]; | |
| authUrl: string | null; | |
| } | |
| export const useCredentials = ( | |
| type: string, | |
| workflowId?: string | |
| ): UseCredentialsResult => { |
| // if (credentials.length === 0) { | ||
| // // No credentials found - return the correct auth URL | ||
| // const authUrl = `${process.env.BACKEND_URL || "http://localhost:3002"}/oauth/google/initiate`; | ||
| // return res.status(statusCodes.OK).json({ | ||
| // message: | ||
| // "Credentials not found create credentials using this auth url", | ||
| // Data: authUrl, | ||
| // }); | ||
| // } | ||
|
|
||
| // // Credentials found - return them | ||
| // return res.status(statusCodes.OK).json({ | ||
| // message: "Credentials Fetched successfully", | ||
| // Data: credentials, | ||
| // }); |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Large block of commented-out code should be removed rather than left in the codebase. If this code needs to be preserved for reference, it should be documented in version control history or migration notes.
| // if (credentials.length === 0) { | |
| // // No credentials found - return the correct auth URL | |
| // const authUrl = `${process.env.BACKEND_URL || "http://localhost:3002"}/oauth/google/initiate`; | |
| // return res.status(statusCodes.OK).json({ | |
| // message: | |
| // "Credentials not found create credentials using this auth url", | |
| // Data: authUrl, | |
| // }); | |
| // } | |
| // // Credentials found - return them | |
| // return res.status(statusCodes.OK).json({ | |
| // message: "Credentials Fetched successfully", | |
| // Data: credentials, | |
| // }); |
| // Save tokens to database if userId is provided | ||
| if (userId) { | ||
| console.log(' Saving tokens for userId:', userId); | ||
| await Oauth.saveCredentials(userId, tokens as OAuthTokens) |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid automated semicolon insertion (97% of all statements in the enclosing function have an explicit semicolon).
| await Oauth.saveCredentials(userId, tokens as OAuthTokens) | |
| await Oauth.saveCredentials(userId, tokens as OAuthTokens); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/http-backend/src/routes/google_callback.ts (1)
199-236: Debug endpoint exposes all stored credentials without authentication.The
/debug/credentialsendpoint is unprotected and exposes all Google OAuth credentials in the database, including token presence and expiry information. This is a significant security vulnerability.Suggested fix - protect or disable in production
// Debug endpoint to check stored credentials -googleAuth.get('/debug/credentials', async(req: Request, res: Response)=>{ +googleAuth.get('/debug/credentials', userMiddleware, async(req: AuthRequest, res: Response)=>{ + // Only allow in development + if (process.env.NODE_ENV === 'production') { + return res.status(403).json({ error: 'Debug endpoints disabled in production' }); + } try {apps/web/app/workflows/[id]/components/ConfigModal.tsx (1)
67-123: Prevent saving when required credentials are missing.When no credentials exist, the required
credentialIdinput is no longer rendered, so users can click “Save” and close the modal without selecting/connecting any credential. Consider disabling Save until a credential is selected or connected.🔧 Proposed fix (disable Save when credentialId is required but missing)
- let credType: string | null = null; - if (selectedNode) { - const nodeConfig = getNodeConfig(selectedNode.name || selectedNode.actionType); - if (nodeConfig && nodeConfig.credentials) credType = nodeConfig.credentials; - } - const { cred: credentials = [], authUrl } = useCredentials(credType ?? "", workflowId); + const selectedNodeConfig = selectedNode + ? getNodeConfig(selectedNode.name || selectedNode.actionType) + : null; + const credType: string | null = selectedNodeConfig?.credentials ?? null; + const { cred: credentials = [], authUrl } = useCredentials(credType ?? "", workflowId); + const requiresCredential = !!selectedNodeConfig?.fields?.some( + (f: any) => f.name === "credentialId" && f.required + ); + const isCredentialMissing = requiresCredential && !config.credentialId; ... - disabled={loading} + disabled={loading || isCredentialMissing}Also applies to: 354-360
🤖 Fix all issues with AI agents
In `@apps/http-backend/src/routes/google_callback.ts`:
- Around line 151-197: The /debug/config route (googleAuth.get('/debug/config'))
currently returns sensitive OAuth info (REDIRECT_URI, GOOGLE_CLIENT_ID/SECRET,
testAuthUrl) without authentication; fix by either removing the endpoint from
production or protecting it: wrap the handler with the existing userMiddleware
(or another auth middleware) so only authenticated users can access it, or gate
the entire route behind an environment check (e.g., only register/return when
NODE_ENV === 'development' or a DEBUG_CONFIG flag) and ensure
GOOGLE_CLIENT_SECRET is never returned; update the route registration
accordingly.
- Around line 131-147: Replace the hardcoded frontend base URL with an
environment-driven value (e.g. process.env.FRONTEND_URL) when building
redirectUrl and errorUrl in google_callback.ts so production deployments use the
correct host; also stop redeclaring workflowId inside the catch block (remove
the inner "let workflowId" and assign to the existing workflowId variable parsed
from state) so you don't shadow the outer variable—use the existing workflowId
plus encodeURIComponent(err?.message ?? "Token exchange failed") when
constructing the errorUrl.
In `@apps/web/app/hooks/useCredential.ts`:
- Around line 20-27: The getCredentials function is returning the wrong property
due to a case mismatch causing useCredential's Array.isArray check to fail; in
the getCredentials implementation (the function named getCredentials in
workflow/lib/config.ts) change the return to use the lowercase nested field
(return response.data.data) instead of response.data.Data so that getCredentials
returns the actual array the useCredential hook expects and setCred receives the
correct value.
🧹 Nitpick comments (7)
apps/http-backend/src/index.ts (1)
36-36: Remove the inline comment.The comment
// ← CHANGED THIS LINE!is development noise and should be removed before merging.Suggested fix
-app.use('/oauth/google', googleAuth) // ← CHANGED THIS LINE! +app.use('/oauth/google', googleAuth)apps/http-backend/src/routes/userRoutes/userRoutes.ts (2)
216-230: Remove commented-out code.This dead code should be removed rather than left commented. It adds noise and the git history preserves the old implementation if needed.
Suggested fix
- // if (credentials.length === 0) { - // // No credentials found - return the correct auth URL - // const authUrl = `${process.env.BACKEND_URL || "http://localhost:3002"}/oauth/google/initiate`; - // return res.status(statusCodes.OK).json({ - // message: - // "Credentials not found create credentials using this auth url", - // Data: authUrl, - // }); - // } - - // // Credentials found - return them - // return res.status(statusCodes.OK).json({ - // message: "Credentials Fetched successfully", - // Data: credentials, - // }); if (credentials.length === 0) {
231-243: Inconsistent response field naming and status code usage.Two issues with the new response shape:
- Uses magic number
200instead ofstatusCodes.OKwhich is already imported and used elsewhere in this file.- Uses lowercase
datawhile other endpoints in this file useData. This inconsistency may cause issues for frontend consumers expecting a consistent API.Suggested fix for consistency
if (credentials.length === 0) { - return res.status(200).json({ + return res.status(statusCodes.OK).json({ message: "No credentials found", - data: [], // always array + Data: [], // always array hasCredentials: false, }); } - return res.status(200).json({ + return res.status(statusCodes.OK).json({ message: "Credentials fetched", - data: credentials, + Data: credentials, hasCredentials: true, });apps/web/app/hooks/useCredential.ts (2)
6-8: Consider adding proper TypeScript types.The return type
: anyloses type safety. Consider defining an interface for the return value and credential type.Suggested improvement
+interface Credential { + id: string; + userId: string; + type: string; + config: unknown; +} + +interface UseCredentialsResult { + cred: Credential[]; + authUrl: string | null; +} + -export const useCredentials = (type: string, workflowId?: string): any => { - const [cred, setCred] = useState<any[]>([]); +export const useCredentials = (type: string, workflowId?: string): UseCredentialsResult => { + const [cred, setCred] = useState<Credential[]>([]); const [authUrl, setAuthUrl] = useState<string | null>(null);
30-36: Hardcoded provider type check.The OAuth URL is only constructed for the
"google"type. If other OAuth providers are added in the future, this will need modification. Consider making this more extensible or documenting the limitation.apps/http-backend/src/routes/google_callback.ts (2)
6-27: Redundant dotenv loading and sensitive data logging.Two concerns:
Redundant loading:
dotenv.config()is already called inindex.tsbefore this module is imported. The duplicate loading here is unnecessary.Sensitive logging: Logging partial client IDs and the presence of secrets at module load time could expose sensitive information in logs. Consider removing or gating behind a DEBUG flag.
Suggested fix - remove redundant dotenv loading
import { google } from "googleapis"; import { GoogleOAuthService } from "@repo/nodes"; import type { OAuthTokens } from "@repo/nodes"; import { AuthRequest, userMiddleware } from "./userRoutes/userMiddleware.js"; import { Router, Request, Response } from "express"; -import dotenv from "dotenv"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; - -// Get the directory of this file -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -// Load environment variables from the http-backend directory -// When running from dist/, we need to go up to the src directory, then to the root -// Use override: true to ensure this .env file takes precedence over others -const envPath = join(__dirname, "../../.env"); -dotenv.config({ path: envPath, override: true }); -// Log OAuth configuration at module load const REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || "http://localhost:3002/oauth/google/callback"; -console.log("🔐 OAuth Configuration loaded:"); -console.log(" .env path:", envPath); -console.log(" GOOGLE_REDIRECT_URI from env:", process.env.GOOGLE_REDIRECT_URI || "NOT SET (using default)"); -console.log(" Using Redirect URI:", REDIRECT_URI); -console.log(" GOOGLE_CLIENT_ID:", process.env.GOOGLE_CLIENT_ID ? `${process.env.GOOGLE_CLIENT_ID.substring(0, 20)}...` : "NOT SET"); -console.log(" GOOGLE_CLIENT_SECRET:", process.env.GOOGLE_CLIENT_SECRET ? "SET" : "NOT SET");
45-46: State encoding with pipe separator is fragile.If
userIdcontains a pipe character (|), the state parsing in the callback will break. Consider using URL-safe base64 encoding or JSON encoding for the state.Suggested fix using JSON encoding
- // Encode userId and workflowId in state (format: userId|workflowId) - const state = workflowId ? `${userId}|${workflowId}` : userId; + // Encode userId and workflowId in state as base64 JSON + const statePayload = workflowId + ? JSON.stringify({ userId, workflowId }) + : JSON.stringify({ userId }); + const state = Buffer.from(statePayload).toString('base64url');And update the callback parsing accordingly:
- if (state && typeof state === "string") { - const parts = state.split("|"); - userId = parts[0]; - workflowId = parts[1]; // Will be undefined if not provided - } + if (state && typeof state === "string") { + try { + const decoded = JSON.parse(Buffer.from(state, 'base64url').toString()); + userId = decoded.userId; + workflowId = decoded.workflowId; + } catch { + console.error('Failed to parse OAuth state'); + } + }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/http-backend/src/index.tsapps/http-backend/src/routes/google_callback.tsapps/http-backend/src/routes/userRoutes/userRoutes.tsapps/web/app/hooks/useCredential.tsapps/web/app/workflows/[id]/components/ConfigModal.tsxapps/web/app/workflows/[id]/page.tsxpackages/nodes/src/common/google-oauth-service.ts
🧰 Additional context used
🧬 Code graph analysis (3)
apps/http-backend/src/index.ts (1)
apps/http-backend/src/routes/google_callback.ts (1)
googleAuth(29-29)
apps/web/app/hooks/useCredential.ts (4)
packages/nodes/src/common/google-oauth-service.ts (1)
getCredentials(85-112)apps/web/app/workflow/lib/config.ts (1)
getCredentials(28-46)packages/common/src/index.ts (1)
BACKEND_URL(4-4)packages/nodes/src/google-sheets/repl.js (1)
url(8-12)
apps/web/app/workflows/[id]/components/ConfigModal.tsx (3)
apps/web/app/hooks/redux.ts (1)
useAppSelector(7-7)apps/web/app/lib/nodeConfigs/index.ts (1)
getNodeConfig(25-27)apps/web/app/hooks/useCredential.ts (1)
useCredentials(6-53)
🔇 Additional comments (5)
packages/nodes/src/common/google-oauth-service.ts (1)
18-24: LGTM! Fallback redirect URI aligns with the route changes.The fallback to
http://localhost:3002/oauth/google/callbackprovides a sensible default for local development and matches the route path changes in the backend.apps/http-backend/src/index.ts (1)
1-12: LGTM! Early environment loading ensures proper configuration.Loading dotenv before other imports is the correct pattern to ensure environment variables are available when other modules initialize. The
override: trueoption is appropriate for ensuring local .env takes precedence.apps/web/app/workflows/[id]/components/ConfigModal.tsx (2)
7-39: Hook-driven credential sourcing and workflowId plumbing look solid.The flow is consistent and keeps credential state centralized in the hook.
43-53: Save error handling simplification is fine.The catch block is still clear and user feedback is preserved.
apps/web/app/workflows/[id]/page.tsx (1)
420-424: Passing workflowId into ConfigModal looks correct.This keeps the modal aligned with the workflow-scoped OAuth flow.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| const redirectUrl = workflowId | ||
| ? `http://localhost:3000/workflows/${workflowId}` | ||
| : "http://localhost:3000/workflow"; | ||
| console.log(' Redirecting to:', redirectUrl); | ||
| return res.redirect(redirectUrl); | ||
| } catch (err: any) { | ||
| console.error("Google token exchange error:", err); | ||
| return res.redirect( | ||
| `http://localhost:3000/workflow?google=error&msg=${encodeURIComponent(err?.message ?? "Token exchange failed")}` | ||
| ); | ||
| // Parse state to get workflowId for error redirect | ||
| let workflowId: string | undefined; | ||
| if (state && typeof state === "string") { | ||
| const parts = state.split("|"); | ||
| workflowId = parts[1]; | ||
| } | ||
| const errorUrl = workflowId | ||
| ? `http://localhost:3000/workflows/${workflowId}?google=error&msg=${encodeURIComponent(err?.message ?? "Token exchange failed")}` | ||
| : `http://localhost:3000/workflow?google=error&msg=${encodeURIComponent(err?.message ?? "Token exchange failed")}`; | ||
| return res.redirect(errorUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded frontend URLs and variable shadowing.
Two issues:
-
Hardcoded URLs:
http://localhost:3000won't work in production. Use an environment variable likeFRONTEND_URL. -
Variable shadowing:
workflowIdis re-declared on line 139 but was already declared on line 87 and is in scope. This causes the outer variable to be shadowed and could lead to confusion.
Suggested fix
+const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000";
+
// Redirect to workflow page if workflowId is provided, otherwise to general workflow page
const redirectUrl = workflowId
- ? `http://localhost:3000/workflows/${workflowId}`
- : "http://localhost:3000/workflow";
+ ? `${FRONTEND_URL}/workflows/${workflowId}`
+ : `${FRONTEND_URL}/workflow`;
console.log(' Redirecting to:', redirectUrl);
return res.redirect(redirectUrl);
} catch (err: any) {
console.error("Google token exchange error:", err);
- // Parse state to get workflowId for error redirect
- let workflowId: string | undefined;
- if (state && typeof state === "string") {
- const parts = state.split("|");
- workflowId = parts[1];
- }
+ // workflowId is already parsed above, no need to re-parse
const errorUrl = workflowId
- ? `http://localhost:3000/workflows/${workflowId}?google=error&msg=${encodeURIComponent(err?.message ?? "Token exchange failed")}`
- : `http://localhost:3000/workflow?google=error&msg=${encodeURIComponent(err?.message ?? "Token exchange failed")}`;
+ ? `${FRONTEND_URL}/workflows/${workflowId}?google=error&msg=${encodeURIComponent(err?.message ?? "Token exchange failed")}`
+ : `${FRONTEND_URL}/workflow?google=error&msg=${encodeURIComponent(err?.message ?? "Token exchange failed")}`;
return res.redirect(errorUrl);
}🤖 Prompt for AI Agents
In `@apps/http-backend/src/routes/google_callback.ts` around lines 131 - 147,
Replace the hardcoded frontend base URL with an environment-driven value (e.g.
process.env.FRONTEND_URL) when building redirectUrl and errorUrl in
google_callback.ts so production deployments use the correct host; also stop
redeclaring workflowId inside the catch block (remove the inner "let workflowId"
and assign to the existing workflowId variable parsed from state) so you don't
shadow the outer variable—use the existing workflowId plus
encodeURIComponent(err?.message ?? "Token exchange failed") when constructing
the errorUrl.
| // Debug endpoint to check OAuth configuration | ||
| googleAuth.get('/debug/config', async(req: Request, res: Response)=>{ | ||
| try { | ||
| const redirectUri = REDIRECT_URI; | ||
| const clientId = process.env.GOOGLE_CLIENT_ID; | ||
| const hasClientSecret = !!process.env.GOOGLE_CLIENT_SECRET; | ||
|
|
||
| // Create a test OAuth2 client to verify configuration | ||
| const oauth2 = new google.auth.OAuth2( | ||
| clientId, | ||
| process.env.GOOGLE_CLIENT_SECRET, | ||
| redirectUri | ||
| ); | ||
|
|
||
| const testAuthUrl = oauth2.generateAuthUrl({ | ||
| access_type: "offline", | ||
| scope: ["https://www.googleapis.com/auth/spreadsheets"], | ||
| state: "test", | ||
| prompt: "consent", | ||
| }); | ||
|
|
||
| const urlObj = new URL(testAuthUrl); | ||
| const redirectUriInUrl = urlObj.searchParams.get('redirect_uri'); | ||
|
|
||
| return res.json({ | ||
| environment: { | ||
| GOOGLE_REDIRECT_URI: redirectUri, | ||
| GOOGLE_CLIENT_ID: clientId ? `${clientId.substring(0, 20)}...` : "NOT SET", | ||
| GOOGLE_CLIENT_SECRET: hasClientSecret ? "SET" : "NOT SET", | ||
| }, | ||
| oauth2Client: { | ||
| redirectUri: redirectUri, | ||
| redirectUriInGeneratedUrl: redirectUriInUrl, | ||
| matches: redirectUri === redirectUriInUrl, | ||
| }, | ||
| testAuthUrl: testAuthUrl, | ||
| message: redirectUri === redirectUriInUrl | ||
| ? "✅ Configuration looks correct!" | ||
| : "❌ Redirect URI mismatch detected!" | ||
| }); | ||
| } catch (err) { | ||
| return res.status(500).json({ | ||
| error: err instanceof Error ? err.message : 'Unknown error', | ||
| stack: err instanceof Error ? err.stack : undefined | ||
| }); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug endpoint exposes sensitive OAuth configuration without authentication.
The /debug/config endpoint is not protected by userMiddleware and exposes OAuth client IDs and configuration details. This is a security risk in production.
Suggested fix - protect or disable in production
// Debug endpoint to check OAuth configuration
-googleAuth.get('/debug/config', async(req: Request, res: Response)=>{
+googleAuth.get('/debug/config', userMiddleware, async(req: AuthRequest, res: Response)=>{
+ // Only allow in development
+ if (process.env.NODE_ENV === 'production') {
+ return res.status(403).json({ error: 'Debug endpoints disabled in production' });
+ }
try {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Debug endpoint to check OAuth configuration | |
| googleAuth.get('/debug/config', async(req: Request, res: Response)=>{ | |
| try { | |
| const redirectUri = REDIRECT_URI; | |
| const clientId = process.env.GOOGLE_CLIENT_ID; | |
| const hasClientSecret = !!process.env.GOOGLE_CLIENT_SECRET; | |
| // Create a test OAuth2 client to verify configuration | |
| const oauth2 = new google.auth.OAuth2( | |
| clientId, | |
| process.env.GOOGLE_CLIENT_SECRET, | |
| redirectUri | |
| ); | |
| const testAuthUrl = oauth2.generateAuthUrl({ | |
| access_type: "offline", | |
| scope: ["https://www.googleapis.com/auth/spreadsheets"], | |
| state: "test", | |
| prompt: "consent", | |
| }); | |
| const urlObj = new URL(testAuthUrl); | |
| const redirectUriInUrl = urlObj.searchParams.get('redirect_uri'); | |
| return res.json({ | |
| environment: { | |
| GOOGLE_REDIRECT_URI: redirectUri, | |
| GOOGLE_CLIENT_ID: clientId ? `${clientId.substring(0, 20)}...` : "NOT SET", | |
| GOOGLE_CLIENT_SECRET: hasClientSecret ? "SET" : "NOT SET", | |
| }, | |
| oauth2Client: { | |
| redirectUri: redirectUri, | |
| redirectUriInGeneratedUrl: redirectUriInUrl, | |
| matches: redirectUri === redirectUriInUrl, | |
| }, | |
| testAuthUrl: testAuthUrl, | |
| message: redirectUri === redirectUriInUrl | |
| ? "✅ Configuration looks correct!" | |
| : "❌ Redirect URI mismatch detected!" | |
| }); | |
| } catch (err) { | |
| return res.status(500).json({ | |
| error: err instanceof Error ? err.message : 'Unknown error', | |
| stack: err instanceof Error ? err.stack : undefined | |
| }); | |
| } | |
| }); | |
| // Debug endpoint to check OAuth configuration | |
| googleAuth.get('/debug/config', userMiddleware, async(req: AuthRequest, res: Response)=>{ | |
| // Only allow in development | |
| if (process.env.NODE_ENV === 'production') { | |
| return res.status(403).json({ error: 'Debug endpoints disabled in production' }); | |
| } | |
| try { | |
| const redirectUri = REDIRECT_URI; | |
| const clientId = process.env.GOOGLE_CLIENT_ID; | |
| const hasClientSecret = !!process.env.GOOGLE_CLIENT_SECRET; | |
| // Create a test OAuth2 client to verify configuration | |
| const oauth2 = new google.auth.OAuth2( | |
| clientId, | |
| process.env.GOOGLE_CLIENT_SECRET, | |
| redirectUri | |
| ); | |
| const testAuthUrl = oauth2.generateAuthUrl({ | |
| access_type: "offline", | |
| scope: ["https://www.googleapis.com/auth/spreadsheets"], | |
| state: "test", | |
| prompt: "consent", | |
| }); | |
| const urlObj = new URL(testAuthUrl); | |
| const redirectUriInUrl = urlObj.searchParams.get('redirect_uri'); | |
| return res.json({ | |
| environment: { | |
| GOOGLE_REDIRECT_URI: redirectUri, | |
| GOOGLE_CLIENT_ID: clientId ? `${clientId.substring(0, 20)}...` : "NOT SET", | |
| GOOGLE_CLIENT_SECRET: hasClientSecret ? "SET" : "NOT SET", | |
| }, | |
| oauth2Client: { | |
| redirectUri: redirectUri, | |
| redirectUriInGeneratedUrl: redirectUriInUrl, | |
| matches: redirectUri === redirectUriInUrl, | |
| }, | |
| testAuthUrl: testAuthUrl, | |
| message: redirectUri === redirectUriInUrl | |
| ? "✅ Configuration looks correct!" | |
| : "❌ Redirect URI mismatch detected!" | |
| }); | |
| } catch (err) { | |
| return res.status(500).json({ | |
| error: err instanceof Error ? err.message : 'Unknown error', | |
| stack: err instanceof Error ? err.stack : undefined | |
| }); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In `@apps/http-backend/src/routes/google_callback.ts` around lines 151 - 197, The
/debug/config route (googleAuth.get('/debug/config')) currently returns
sensitive OAuth info (REDIRECT_URI, GOOGLE_CLIENT_ID/SECRET, testAuthUrl)
without authentication; fix by either removing the endpoint from production or
protecting it: wrap the handler with the existing userMiddleware (or another
auth middleware) so only authenticated users can access it, or gate the entire
route behind an environment check (e.g., only register/return when NODE_ENV ===
'development' or a DEBUG_CONFIG flag) and ensure GOOGLE_CLIENT_SECRET is never
returned; update the route registration accordingly.
| const response = await getCredentials(type); | ||
| if (response) { | ||
| console.log(typeof response); | ||
| if (typeof response === "string") setAuthUrl(response); | ||
| else setCred(response); | ||
|
|
||
| // console.log(response[0].nodeId) | ||
| console.log(response); | ||
| return cred; | ||
| } else return {}; | ||
| // Backend should ONLY return stored credentials | ||
| if (Array.isArray(response)) { | ||
| setCred(response); | ||
| } else { | ||
| setCred([]); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find and examine the getCredentials function implementation
echo "=== Searching for getCredentials function ==="
find . -name "config.ts" -path "*/workflow/lib/*" | head -5Repository: Dev-Pross/BuildFlow
Length of output: 146
🏁 Script executed:
#!/bin/bash
# Look at the useCredential.ts file to understand the context
find . -name "useCredential.ts" | head -5Repository: Dev-Pross/BuildFlow
Length of output: 100
🏁 Script executed:
#!/bin/bash
# Search for getCredentials definition and usage
rg "const getCredentials|export.*getCredentials" -t ts --type-add 'ts:*.ts' -B 2 -A 10Repository: Dev-Pross/BuildFlow
Length of output: 799
🏁 Script executed:
#!/bin/bash
# Get more context from config.ts to see the complete getCredentials function
cat -n apps/web/app/workflow/lib/config.ts | grep -A 20 "export const getCredentials"Repository: Dev-Pross/BuildFlow
Length of output: 691
🏁 Script executed:
#!/bin/bash
# Read the complete useCredential.ts file
cat -n apps/web/app/hooks/useCredential.tsRepository: Dev-Pross/BuildFlow
Length of output: 1948
🏁 Script executed:
#!/bin/bash
# Search for backend endpoint definition or API route
find . -type f \( -name "*.ts" -o -name "*.js" -o -name "*.py" \) -path "*/backend/*" -o -path "*/server/*" -o -path "*/api/*" | head -20Repository: Dev-Pross/BuildFlow
Length of output: 272
🏁 Script executed:
#!/bin/bash
# Search for mentions of getCredentials endpoint in the codebase
rg "getCredentials|user/getCredentials" -t ts -t js -t py -B 2 -A 5 | head -100Repository: Dev-Pross/BuildFlow
Length of output: 6388
🏁 Script executed:
#!/bin/bash
# Look for type definitions or interfaces related to credentials response
rg "data.*Data|hasCredentials" -t ts -B 2 -A 2Repository: Dev-Pross/BuildFlow
Length of output: 5701
Fix case mismatch in getCredentials response handling.
The backend endpoint returns { data, hasCredentials } with lowercase data, but getCredentials in apps/web/app/workflow/lib/config.ts accesses response.data.Data (uppercase). This causes the function to return undefined, making the Array.isArray(response) check in useCredential.ts fail and silently set credentials to an empty array.
Change line 40 in apps/web/app/workflow/lib/config.ts from:
return response.data.Data;
to:
return response.data.data;
🤖 Prompt for AI Agents
In `@apps/web/app/hooks/useCredential.ts` around lines 20 - 27, The getCredentials
function is returning the wrong property due to a case mismatch causing
useCredential's Array.isArray check to fail; in the getCredentials
implementation (the function named getCredentials in workflow/lib/config.ts)
change the return to use the lowercase nested field (return response.data.data)
instead of response.data.Data so that getCredentials returns the actual array
the useCredential hook expects and setCred receives the correct value.
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.