Skip to content

Conversation

@Vamsi-o
Copy link
Contributor

@Vamsi-o Vamsi-o commented Jan 16, 2026

  • 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.

Summary by CodeRabbit

New Features

  • OAuth authentication flow now integrates with workflows via workflow-aware redirects and credential handling
  • Added configuration debugging endpoint for OAuth setup verification

Improvements

  • Standardized credential response structure and format across API endpoints
  • Enhanced error handling with workflow-specific redirects and improved OAuth flow logging

✏️ Tip: You can customize this high-level summary in your review settings.

…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.
Copilot AI review requested due to automatic review settings January 16, 2026 13:48
@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Backend Environment & OAuth Routes
apps/http-backend/src/index.ts, apps/http-backend/src/routes/google_callback.ts
Added dotenv initialization for environment loading with console logging. Modified OAuth route path from /auth/google to /oauth/google. Enhanced callback route to parse workflowId from state and redirect to workflow-specific URLs. Added debug endpoint /debug/config for OAuth configuration verification.
Credential Response Standardization
apps/http-backend/src/routes/userRoutes/userRoutes.ts
Refactored GET /getCredentials/:type to return standardized responses with { message, data, hasCredentials } structure instead of legacy conditional field names.
Frontend Credential Hook
apps/web/app/hooks/useCredential.ts
Expanded useCredentials signature to accept optional workflowId parameter. Added logic to construct OAuth authUrl for Google type. Restructured response handling to treat credentials strictly as array and manage authUrl separately.
Config Modal & Workflow Integration
apps/web/app/workflows/[id]/components/ConfigModal.tsx, apps/web/app/workflows/[id]/page.tsx
Updated ConfigModal to accept workflowId prop and replaced direct API calls with hook-based credential fetching. Modified credential field rendering to conditionally show dropdown, auth button, or no-credentials message. Updated page.tsx to pass workflowId to ConfigModal.
OAuth Service Utilities
packages/nodes/src/common/google-oauth-service.ts
Added fallback redirect URI computation to ensure consistent redirect URI handling in OAuth2 client initialization.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A workflow hops through OAuth's door,
With workflowId linked to every score,
Credentials dance in standardized light,
Env vars loaded—config's just right!
State now remembers which path to take,
One unified flow for every snowflake. ❄️

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title partially relates to the changeset, referring to real aspects (Google OAuth and environment variables), but is truncated and doesn't fully convey the main changes including route renaming, logging enhancements, and frontend refactoring. Expand the title to complete the truncated text and ensure it captures the primary changes: consider 'feat: enhance Google OAuth integration with environment config and workflow context'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a 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/google to /oauth/google across 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 of Data
  • 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.

Comment on lines +88 to +94

if (state && typeof state === "string") {
const parts = state.split("|");
userId = parts[0];
workflowId = parts[1]; // Will be undefined if not provided
}

Copy link

Copilot AI Jan 16, 2026

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.

Suggested change
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" });
}

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +143
// Parse state to get workflowId for error redirect
let workflowId: string | undefined;
if (state && typeof state === "string") {
const parts = state.split("|");
workflowId = parts[1];
}
Copy link

Copilot AI Jan 16, 2026

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.

Suggested change
// 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 uses AI. Check for mistakes.
console.log("🔐 OAuth Initiate - Workflow ID:", workflowId || "NOT PROVIDED");

// Encode userId and workflowId in state (format: userId|workflowId)
const state = workflowId ? `${userId}|${workflowId}` : userId;
Copy link

Copilot AI Jan 16, 2026

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.

Copilot uses AI. Check for mistakes.
import { getCredentials } from "../workflow/lib/config";
import { BACKEND_URL } from "@repo/common/zod";

export const useCredentials = (type: string, workflowId?: string): any => {
Copy link

Copilot AI Jan 16, 2026

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.

Suggested change
export const useCredentials = (type: string, workflowId?: string): any => {
interface UseCredentialsResult {
cred: any[];
authUrl: string | null;
}
export const useCredentials = (
type: string,
workflowId?: string
): UseCredentialsResult => {

Copilot uses AI. Check for mistakes.
Comment on lines +216 to +230
// 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,
// });
Copy link

Copilot AI Jan 16, 2026

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.

Suggested change
// 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 uses AI. Check for mistakes.
// Save tokens to database if userId is provided
if (userId) {
console.log(' Saving tokens for userId:', userId);
await Oauth.saveCredentials(userId, tokens as OAuthTokens)
Copy link

Copilot AI Jan 16, 2026

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).

Suggested change
await Oauth.saveCredentials(userId, tokens as OAuthTokens)
await Oauth.saveCredentials(userId, tokens as OAuthTokens);

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a 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/credentials endpoint 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 credentialId input 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:

  1. Uses magic number 200 instead of statusCodes.OK which is already imported and used elsewhere in this file.
  2. Uses lowercase data while other endpoints in this file use Data. 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 : any loses 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:

  1. Redundant loading: dotenv.config() is already called in index.ts before this module is imported. The duplicate loading here is unnecessary.

  2. 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 userId contains 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

📥 Commits

Reviewing files that changed from the base of the PR and between ef2943a and 3a6ee72.

📒 Files selected for processing (7)
  • apps/http-backend/src/index.ts
  • apps/http-backend/src/routes/google_callback.ts
  • apps/http-backend/src/routes/userRoutes/userRoutes.ts
  • apps/web/app/hooks/useCredential.ts
  • apps/web/app/workflows/[id]/components/ConfigModal.tsx
  • apps/web/app/workflows/[id]/page.tsx
  • packages/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/callback provides 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: true option 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.

Comment on lines +131 to +147
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded frontend URLs and variable shadowing.

Two issues:

  1. Hardcoded URLs: http://localhost:3000 won't work in production. Use an environment variable like FRONTEND_URL.

  2. Variable shadowing: workflowId is 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.

Comment on lines +151 to +197
// 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
});
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
// 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.

Comment on lines 20 to +27
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([]);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -5

Repository: 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 -5

Repository: 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 10

Repository: 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.ts

Repository: 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 -20

Repository: 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 -100

Repository: 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 2

Repository: 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.

@Vamsi-o Vamsi-o merged commit f83ceec into main Jan 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants