diff --git a/apps/http-backend/src/index.ts b/apps/http-backend/src/index.ts
index 816207a..5c829d7 100644
--- a/apps/http-backend/src/index.ts
+++ b/apps/http-backend/src/index.ts
@@ -1,3 +1,16 @@
+import dotenv from "dotenv";
+import { fileURLToPath } from "url";
+import { dirname, join } from "path";
+
+// Load .env file FIRST, before any other imports
+// This ensures our environment variables override any from other packages
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const envPath = join(__dirname, "../.env");
+dotenv.config({ path: envPath, override: true });
+console.log("📁 Loaded .env from:", envPath);
+console.log("🔐 GOOGLE_REDIRECT_URI:", process.env.GOOGLE_REDIRECT_URI || "NOT SET");
+
import cookieParser from 'cookie-parser'
import { NodeRegistry } from "@repo/nodes/nodeClient";
import express from "express";
@@ -20,7 +33,7 @@ app.use(cookieParser());
app.use("/user" , userRouter)
app.use('/node', sheetRouter)
-app.use('/auth/google', googleAuth) // ← CHANGED THIS LINE!
+app.use('/oauth/google', googleAuth) // ← CHANGED THIS LINE!
const PORT= 3002
diff --git a/apps/http-backend/src/routes/google_callback.ts b/apps/http-backend/src/routes/google_callback.ts
index baa4ee4..82d06d6 100644
--- a/apps/http-backend/src/routes/google_callback.ts
+++ b/apps/http-backend/src/routes/google_callback.ts
@@ -3,6 +3,28 @@ 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");
export const googleAuth: Router = Router();
googleAuth.get(
@@ -11,12 +33,22 @@ googleAuth.get(
async (req: AuthRequest, res: Response) => {
// const userId = req.user?.id || "test_user"; // Get from auth middleware
const userId = req.user?.id;
+ const workflowId = req.query.workflowId as string | undefined;
+
+ // Ensure redirect URI matches Google Cloud Console configuration
+ const redirectUri = REDIRECT_URI;
+
+ console.log("🔐 OAuth Initiate - Redirect URI:", redirectUri);
+ console.log("🔐 OAuth Initiate - User ID:", userId);
+ console.log("🔐 OAuth Initiate - Workflow ID:", workflowId || "NOT PROVIDED");
+
+ // Encode userId and workflowId in state (format: userId|workflowId)
+ const state = workflowId ? `${userId}|${workflowId}` : userId;
const oauth2 = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
- process.env.GOOGLE_REDIRECT_URI ||
- "http://localhost:3002/auth/google/callback"
+ redirectUri
);
const authUrl = oauth2.generateAuthUrl({
@@ -27,10 +59,11 @@ googleAuth.get(
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.readonly",
],
- state: userId,
+ state: state,
prompt: "consent",
});
+ console.log("🔐 OAuth Initiate - Generated Auth URL:", authUrl);
return res.redirect(authUrl);
}
);
@@ -46,11 +79,29 @@ googleAuth.get(
return res.json({ error: "Missing or invalid authorization code" });
}
+ // Ensure redirect URI matches Google Cloud Console configuration
+ const redirectUri = REDIRECT_URI;
+
+ // Parse state: format is "userId" or "userId|workflowId"
+ let userId: string | undefined;
+ let workflowId: string | undefined;
+
+ if (state && typeof state === "string") {
+ const parts = state.split("|");
+ userId = parts[0];
+ workflowId = parts[1]; // Will be undefined if not provided
+ }
+
+ console.log("🔐 OAuth Callback - Redirect URI:", redirectUri);
+ console.log("🔐 OAuth Callback - Received code:", code ? "✅ Present" : "❌ Missing");
+ console.log("🔐 OAuth Callback - State:", state);
+ console.log("🔐 OAuth Callback - Parsed User ID:", userId);
+ console.log("🔐 OAuth Callback - Parsed Workflow ID:", workflowId || "NOT PROVIDED");
+
const oauth2 = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
- process.env.GOOGLE_REDIRECT_URI ||
- "http://localhost:3002/auth/google/callback"
+ redirectUri
);
try {
@@ -69,23 +120,82 @@ googleAuth.get(
console.warn(' To force new refresh_token, user needs to revoke access at: https://myaccount.google.com/permissions');
}
- // Save tokens to database if userId (state) is provided
- if (state && typeof state === 'string') {
- console.log(' Saving tokens for userId:', state);
- await Oauth.saveCredentials(state, tokens as OAuthTokens)
+ // Save tokens to database if userId is provided
+ if (userId) {
+ console.log(' Saving tokens for userId:', userId);
+ await Oauth.saveCredentials(userId, tokens as OAuthTokens)
console.log(' ✅ Tokens saved to database');
}
- // Redirect to success page
- return res.redirect("http://localhost:3000/workflow");
+ // 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";
+ 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);
}
})
+// 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 stored credentials
googleAuth.get('/debug/credentials', async(req: Request, res: Response)=>{
try {
diff --git a/apps/http-backend/src/routes/userRoutes/userRoutes.ts b/apps/http-backend/src/routes/userRoutes/userRoutes.ts
index 51e115e..84c073c 100644
--- a/apps/http-backend/src/routes/userRoutes/userRoutes.ts
+++ b/apps/http-backend/src/routes/userRoutes/userRoutes.ts
@@ -213,20 +213,33 @@ router.get(
},
});
+ // 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) {
- // No credentials found - return the correct auth URL
- const authUrl = `${process.env.BACKEND_URL || "http://localhost:3002"}/auth/google/initiate`;
- return res.status(statusCodes.OK).json({
- message:
- "Credentials not found create credentials using this auth url",
- Data: authUrl,
+ return res.status(200).json({
+ message: "No credentials found",
+ data: [], // always array
+ hasCredentials: false,
});
}
- // Credentials found - return them
- return res.status(statusCodes.OK).json({
- message: "Credentials Fetched successfully",
- Data: credentials,
+ return res.status(200).json({
+ message: "Credentials fetched",
+ data: credentials,
+ hasCredentials: true,
});
} catch (e) {
console.log(
diff --git a/apps/web/app/hooks/useCredential.ts b/apps/web/app/hooks/useCredential.ts
index 24d6b11..2abea42 100644
--- a/apps/web/app/hooks/useCredential.ts
+++ b/apps/web/app/hooks/useCredential.ts
@@ -1,34 +1,53 @@
"use client";
import { useEffect, useState } from "react";
import { getCredentials } from "../workflow/lib/config";
+import { BACKEND_URL } from "@repo/common/zod";
+
+export const useCredentials = (type: string, workflowId?: string): any => {
+ const [cred, setCred] = useState
- Connect your Google account to use Gmail & Sheets
-