Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/http-backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { userRouter } from "./routes/userRoutes/userRoutes.js";
import cors from "cors"
import { sheetRouter } from "./routes/nodes.routes.js";
import { googleAuth } from "./routes/google_callback.js";
import { tokenScheduler } from "./scheduler/token-scheduler.js";

const app = express()

Expand Down Expand Up @@ -40,7 +41,7 @@ app.use('/google', googleAuth)
const PORT= 3002
async function startServer() {
await NodeRegistry.registerAll()

tokenScheduler.start();
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

Add error handling and graceful shutdown for the token scheduler.

The scheduler starts without error handling, and there's no cleanup on server shutdown. If the scheduler fails to start, the server continues anyway, potentially leaving tokens unrefreshed. Additionally, without graceful shutdown, the scheduler may continue running after the server stops.

🔎 Recommended improvements
 async function startServer() {
   await NodeRegistry.registerAll()
-  tokenScheduler.start();
+  try {
+    tokenScheduler.start();
+    console.log('✅ Token scheduler started');
+  } catch (error) {
+    console.error('❌ Failed to start token scheduler:', error);
+    // Decide: should this be fatal? For now, log and continue
+  }
+  
   app.listen(PORT, () => {
     console.log(`Server running on port ${PORT}`);
   })
+  
+  // Graceful shutdown
+  process.on('SIGTERM', () => {
+    console.log('SIGTERM received, shutting down gracefully');
+    tokenScheduler.stop();
+    process.exit(0);
+  });
+  
+  process.on('SIGINT', () => {
+    console.log('SIGINT received, shutting down gracefully');
+    tokenScheduler.stop();
+    process.exit(0);
+  });
 }
📝 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
tokenScheduler.start();
async function startServer() {
await NodeRegistry.registerAll()
try {
tokenScheduler.start();
console.log('✅ Token scheduler started');
} catch (error) {
console.error('❌ Failed to start token scheduler:', error);
// Decide: should this be fatal? For now, log and continue
}
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
})
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
tokenScheduler.stop();
process.exit(0);
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
tokenScheduler.stop();
process.exit(0);
});
}
🤖 Prompt for AI Agents
In apps/http-backend/src/index.ts around line 44, tokenScheduler.start() is
invoked with no error handling or shutdown cleanup; wrap the start call in a
try/catch (await if start is async) and log the error and stop server or exit if
it fails to start, and register graceful shutdown handlers (process
SIGINT/SIGTERM and server close) that call tokenScheduler.stop() (or
cancel/cleanup) to ensure the scheduler is stopped before exit; finally ensure
start/stop calls are idempotent and errors during shutdown are caught and
logged.

app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
})
Expand Down
55 changes: 53 additions & 2 deletions apps/http-backend/src/routes/google_callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,24 @@ googleAuth.get('/callback', async(req: Request, res: Response)=>{
try {
const { tokens } = await oauth2.getToken(code);

// DEBUG: Log tokens received from Google
console.log('\n🔐 Google OAuth Callback - Tokens received:');
console.log(' access_token:', tokens.access_token ? '✅ Present' : '❌ Missing');
console.log(' refresh_token:', tokens.refresh_token ? '✅ Present' : '❌ Missing');
console.log(' expiry_date:', tokens.expiry_date);
console.log(' token_type:', tokens.token_type);
console.log(' scope:', tokens.scope);

if (!tokens.refresh_token) {
console.warn('⚠️ WARNING: No refresh_token received! User may have already authorized this app.');
console.warn(' To force new refresh_token, user needs to revoke access at: https://myaccount.google.com/permissions');
}
Comment on lines +26 to +37
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

Remove or sanitize debug logging in production.

The debug logging exposes detailed OAuth token information to console logs. In production environments, this could leak sensitive data to log aggregation systems.

Consider:

  • Using a debug flag to conditionally enable verbose logging
  • Sanitizing logs to avoid exposing token presence/length details
  • Moving these logs behind a proper debugging configuration
🔎 Suggested approach with environment-based logging
+        const isDebug = process.env.NODE_ENV === 'development';
+        
-        // DEBUG: Log tokens received from Google
-        console.log('\n🔐 Google OAuth Callback - Tokens received:');
-        console.log('   access_token:', tokens.access_token ? '✅ Present' : '❌ Missing');
-        console.log('   refresh_token:', tokens.refresh_token ? '✅ Present' : '❌ Missing');
-        console.log('   expiry_date:', tokens.expiry_date);
-        console.log('   token_type:', tokens.token_type);
-        console.log('   scope:', tokens.scope);
+        if (isDebug) {
+            console.log('\n🔐 Google OAuth Callback - Tokens received:');
+            console.log('   access_token:', tokens.access_token ? '✅ Present' : '❌ Missing');
+            console.log('   refresh_token:', tokens.refresh_token ? '✅ Present' : '❌ Missing');
+            console.log('   expiry_date:', tokens.expiry_date);
+        }
         
         if (!tokens.refresh_token) {
-            console.warn('⚠️  WARNING: No refresh_token received! User may have already authorized this app.');
-            console.warn('   To force new refresh_token, user needs to revoke access at: https://myaccount.google.com/permissions');
+            if (isDebug) {
+                console.warn('⚠️  WARNING: No refresh_token received!');
+            }
         }
📝 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: Log tokens received from Google
console.log('\n🔐 Google OAuth Callback - Tokens received:');
console.log(' access_token:', tokens.access_token ? '✅ Present' : '❌ Missing');
console.log(' refresh_token:', tokens.refresh_token ? '✅ Present' : '❌ Missing');
console.log(' expiry_date:', tokens.expiry_date);
console.log(' token_type:', tokens.token_type);
console.log(' scope:', tokens.scope);
if (!tokens.refresh_token) {
console.warn('⚠️ WARNING: No refresh_token received! User may have already authorized this app.');
console.warn(' To force new refresh_token, user needs to revoke access at: https://myaccount.google.com/permissions');
}
const isDebug = process.env.NODE_ENV === 'development';
if (isDebug) {
console.log('\n🔐 Google OAuth Callback - Tokens received:');
console.log(' access_token:', tokens.access_token ? '✅ Present' : '❌ Missing');
console.log(' refresh_token:', tokens.refresh_token ? '✅ Present' : '❌ Missing');
console.log(' expiry_date:', tokens.expiry_date);
}
if (!tokens.refresh_token) {
if (isDebug) {
console.warn('⚠️ WARNING: No refresh_token received!');
}
}
🤖 Prompt for AI Agents
In apps/http-backend/src/routes/google_callback.ts around lines 26 to 37, remove
or guard the DEBUG console.log/console.warn calls that print OAuth token
details; instead wrap verbose token logs behind a configurable debug flag (e.g.,
process.env.DEBUG_OAUTH === 'true') or the app logger's debug level, and if you
must log, only emit non-sensitive, sanitized info (e.g., token existence as
boolean or masked/length-only indicators) while never printing raw tokens,
refresh_token, or expiry values to production logs. Ensure the warning about
missing refresh_token remains but is also routed through the logger at an
appropriate level and sanitized, and update any related tests/config to set the
debug flag during local dev only.


// 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)
console.log(' ✅ Tokens saved to database');
}

// Redirect to success page
Expand All @@ -38,5 +52,42 @@ googleAuth.get('/callback', async(req: Request, res: Response)=>{
`http://localhost:3000/workflow?google=error&msg=${encodeURIComponent(err?.message ?? 'Token exchange failed')}`);
}
})


// Debug endpoint to check stored credentials
googleAuth.get('/debug/credentials', async(req: Request, res: Response)=>{
try {
const { prismaClient } = await import('@repo/db/client');

const credentials = await prismaClient.credential.findMany({
where: { type: 'google_oauth' },
select: {
id: true,
userId: true,
type: true,
config: true
}
});

const debugInfo = credentials.map(cred => {
const config = cred.config as any;
return {
id: cred.id,
userId: cred.userId,
hasAccessToken: !!config?.access_token,
hasRefreshToken: !!config?.refresh_token,
refreshTokenLength: config?.refresh_token?.length || 0,
expiryDate: config?.expiry_date,
expiresIn: config?.expiry_date ? Math.round((config.expiry_date - Date.now()) / 1000 / 60) + ' minutes' : 'N/A',
isInvalid: config?.invalid || false,
scope: config?.scope
};
});

console.log('\n📋 Stored Credentials Debug:');
console.table(debugInfo);

return res.json({ credentials: debugInfo });
} catch (err) {
return res.status(500).json({ error: err instanceof Error ? err.message : 'Unknown error' });
}
});
Comment on lines +56 to +93
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

CRITICAL: Debug endpoint exposes credentials without authentication.

The /debug/credentials endpoint returns sensitive OAuth credential information without any authentication middleware. This allows anyone to access stored credentials including token metadata and expiry information.

🔎 Required fix: Add authentication and restrict access
-// Debug endpoint to check stored credentials
-googleAuth.get('/debug/credentials', async(req: Request, res: Response)=>{
+// Debug endpoint to check stored credentials (protected)
+googleAuth.get('/debug/credentials', userMiddleware, async(req: AuthRequest, res: Response)=>{
     try {
+        // Only allow in development or for admin users
+        if (process.env.NODE_ENV === 'production') {
+            return res.status(403).json({ error: 'Debug endpoints disabled in production' });
+        }
+        
+        if (!req.user) {
+            return res.status(401).json({ error: 'Authentication required' });
+        }
+        
         const { prismaClient } = await import('@repo/db/client');
         
         const credentials = await prismaClient.credential.findMany({
-            where: { type: 'google_oauth' },
+            where: { 
+                type: 'google_oauth',
+                userId: req.user.id  // Only show user's own credentials
+            },
             select: {
                 id: true,
                 userId: true,

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/http-backend/src/routes/google_callback.ts around lines 56 to 93, the
/debug/credentials endpoint exposes sensitive OAuth credential data without any
authentication; secure it by removing or disabling the public debug route and/or
wrapping it with the application’s auth middleware, verifying the requester is
authenticated and has an admin or ops role before continuing, and ensure
returned data omits secrets (no access_token, refresh_token, or full config) —
only return non-sensitive metadata (e.g., id, userId, hasTokens true/false,
expiryDate) for authorized users; if you keep the endpoint, log full details
only server-side and never send tokens to clients.

78 changes: 77 additions & 1 deletion apps/http-backend/src/routes/userRoutes/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
TriggerSchema,
WorkflowSchema,
NodeSchema,
NodeUpdateSchema,
TriggerUpdateSchema,
} from "@repo/common/zod";
import { GoogleSheetsNodeExecutor } from "@repo/nodes";
const router: Router = Router();
Expand Down Expand Up @@ -336,6 +338,7 @@ router.get('/empty/workflow', userMiddleware, async(req:AuthRequest, res: Respon
});
}
})

router.get("/workflow/:workflowId",
userMiddleware,
async (req: AuthRequest, res: Response) => {
Expand Down Expand Up @@ -437,7 +440,7 @@ router.post('/create/node', userMiddleware, async(req: AuthRequest, res: Respons
});
}
const data = req.body;
console.log(data," from http-backeden" );
// console.log(data," from http-backeden" );

const dataSafe = NodeSchema.safeParse(data)
if(!dataSafe.success) {
Expand Down Expand Up @@ -469,6 +472,79 @@ router.post('/create/node', userMiddleware, async(req: AuthRequest, res: Respons
}
})

// ------------------------- UPDATE NODES AND TRIGGES ---------------------------

router.put('/update/node', userMiddleware, async(req: AuthRequest, res: Response)=>{
try{
if(!req.user){
return res.status(statusCodes.BAD_REQUEST).json({
message: "User is not logged in ",
});
}
const data = req.body;
const dataSafe = NodeUpdateSchema.safeParse(data)

if(!dataSafe.success) {
return res.status(statusCodes.BAD_REQUEST).json({
message: "Invalid input"
})
}

const updateNode = await prismaClient.node.update({
where: {id: dataSafe.data.NodeId},
data:{
config: dataSafe.data.Config
}
})

if(updateNode)
return res.status(statusCodes.CREATED).json({
message: "Node updated",
data: updateNode
})

}catch(e){
console.log("This is the error from Node updating", e);
return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({
message: "Internal server Error from Node Updation.",
});
}
})
Comment on lines +477 to +512
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use HTTP 200 instead of 201 for update operations.

The endpoint returns 201 CREATED on successful update (line 501), but 201 is semantically for resource creation. For update operations, 200 OK is the correct status code.

🔎 Proposed fix
     if(updateNode)
-      return res.status(statusCodes.CREATED).json({
+      return res.status(statusCodes.OK).json({
         message: "Node updated",
         data: updateNode
       })
📝 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
router.put('/update/node', userMiddleware, async(req: AuthRequest, res: Response)=>{
try{
if(!req.user){
return res.status(statusCodes.BAD_REQUEST).json({
message: "User is not logged in ",
});
}
const data = req.body;
const dataSafe = NodeUpdateSchema.safeParse(data)
if(!dataSafe.success) {
return res.status(statusCodes.BAD_REQUEST).json({
message: "Invalid input"
})
}
const updateNode = await prismaClient.node.update({
where: {id: dataSafe.data.NodeId},
data:{
config: dataSafe.data.Config
}
})
if(updateNode)
return res.status(statusCodes.CREATED).json({
message: "Node updated",
data: updateNode
})
}catch(e){
console.log("This is the error from Node updating", e);
return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({
message: "Internal server Error from Node Updation.",
});
}
})
router.put('/update/node', userMiddleware, async(req: AuthRequest, res: Response)=>{
try{
if(!req.user){
return res.status(statusCodes.BAD_REQUEST).json({
message: "User is not logged in ",
});
}
const data = req.body;
const dataSafe = NodeUpdateSchema.safeParse(data)
if(!dataSafe.success) {
return res.status(statusCodes.BAD_REQUEST).json({
message: "Invalid input"
})
}
const updateNode = await prismaClient.node.update({
where: {id: dataSafe.data.NodeId},
data:{
config: dataSafe.data.Config
}
})
if(updateNode)
return res.status(statusCodes.OK).json({
message: "Node updated",
data: updateNode
})
}catch(e){
console.log("This is the error from Node updating", e);
return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({
message: "Internal server Error from Node Updation.",
});
}
})
🤖 Prompt for AI Agents
In apps/http-backend/src/routes/userRoutes/userRoutes.ts around lines 477 to
512, the handler for PUT /update/node currently returns statusCodes.CREATED
(201) on successful update; change this to statusCodes.OK (200) to correctly
indicate an update operation; update the response line to use statusCodes.OK
while keeping the response body the same.


router.put('/update/trigger', userMiddleware, async(req: AuthRequest, res: Response)=>{
try{
if(!req.user){
return res.status(statusCodes.BAD_REQUEST).json({
message: "User is not logged in ",
});
}
const data = req.body;
const dataSafe = TriggerUpdateSchema.safeParse(data)

if(!dataSafe.success)
return res.status(statusCodes.BAD_REQUEST).json({
message: "Invalid input"
})

const updatedTrigger = await prismaClient.trigger.update({
where:{id: dataSafe.data.TriggerId},
data:{
config: dataSafe.data.Config
}
})

if(updatedTrigger)
return res.status(statusCodes.CREATED).json({
message: "Trigger updated",
data: updatedTrigger
})
}catch(e){
console.log("This is the error from Trigger updating", e);
return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({
message: "Internal server Error from Trigger Updation",
});
}
})
Comment on lines +514 to +547
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use HTTP 200 instead of 201 for update operations.

Similar to the node update endpoint, this trigger update endpoint returns 201 CREATED (line 537) but should return 200 OK for successful updates.

🔎 Proposed fix
     if(updatedTrigger)
-      return res.status(statusCodes.CREATED).json({
+      return res.status(statusCodes.OK).json({
         message: "Trigger updated",
         data: updatedTrigger
       })
📝 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
router.put('/update/trigger', userMiddleware, async(req: AuthRequest, res: Response)=>{
try{
if(!req.user){
return res.status(statusCodes.BAD_REQUEST).json({
message: "User is not logged in ",
});
}
const data = req.body;
const dataSafe = TriggerUpdateSchema.safeParse(data)
if(!dataSafe.success)
return res.status(statusCodes.BAD_REQUEST).json({
message: "Invalid input"
})
const updatedTrigger = await prismaClient.trigger.update({
where:{id: dataSafe.data.TriggerId},
data:{
config: dataSafe.data.Config
}
})
if(updatedTrigger)
return res.status(statusCodes.CREATED).json({
message: "Trigger updated",
data: updatedTrigger
})
}catch(e){
console.log("This is the error from Trigger updating", e);
return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({
message: "Internal server Error from Trigger Updation",
});
}
})
router.put('/update/trigger', userMiddleware, async(req: AuthRequest, res: Response)=>{
try{
if(!req.user){
return res.status(statusCodes.BAD_REQUEST).json({
message: "User is not logged in ",
});
}
const data = req.body;
const dataSafe = TriggerUpdateSchema.safeParse(data)
if(!dataSafe.success)
return res.status(statusCodes.BAD_REQUEST).json({
message: "Invalid input"
})
const updatedTrigger = await prismaClient.trigger.update({
where:{id: dataSafe.data.TriggerId},
data:{
config: dataSafe.data.Config
}
})
if(updatedTrigger)
return res.status(statusCodes.OK).json({
message: "Trigger updated",
data: updatedTrigger
})
}catch(e){
console.log("This is the error from Trigger updating", e);
return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({
message: "Internal server Error from Trigger Updation",
});
}
})
🤖 Prompt for AI Agents
In apps/http-backend/src/routes/userRoutes/userRoutes.ts around lines 514 to
547, the trigger update handler returns HTTP 201 CREATED on success; change it
to return HTTP 200 OK instead. Update the response that currently sends
statusCodes.CREATED to use statusCodes.OK and keep the same message and payload;
ensure the rest of the logic and error handling remain unchanged.


// ----------------------- GET WORKFLOW DATA(NODES, TRIGGER)---------------------

Expand Down
68 changes: 68 additions & 0 deletions apps/http-backend/src/scheduler/token-scheduler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { tokenRefreshService } from "../services/token-refresh.service.js";

class TokenScheduler {
private intervalId: NodeJS.Timeout | null = null;
private intervalMinutes: number;

constructor(intervalMinutes: number = 60) {
this.intervalMinutes = intervalMinutes;
}

/**
* Start the token refresh scheduler
*/
start(): void {
console.log(`\n🚀 Token Refresh Scheduler started`);
console.log(` Interval: Every ${this.intervalMinutes} minutes`);
console.log(` Next run: Immediately + every ${this.intervalMinutes} min\n`);

// Run immediately on start
this.runRefreshJob();

// Then run at specified interval
const intervalMs = this.intervalMinutes * 60 * 1000;
this.intervalId = setInterval(() => {
this.runRefreshJob();
}, intervalMs);
}

/**
* Stop the scheduler
*/
stop(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
console.log('🛑 Token Refresh Scheduler stopped');
}
}

/**
* Run the refresh job
*/
private async runRefreshJob(): Promise<void> {
try {
const timestamp = new Date().toISOString();
console.log(`\n⏱️ [${timestamp}] Running scheduled token refresh...`);

await tokenRefreshService.refreshAllExpiringTokens();

} catch (error) {
console.error('❌ Scheduled token refresh failed:', error);
}
}

/**
* Manually trigger a refresh (useful for testing or on-demand refresh)
*/
async triggerManualRefresh(): Promise<void> {
console.log('\n🔧 Manual token refresh triggered...');
await this.runRefreshJob();
}
}

// Default scheduler instance - runs every 30 minutes
export const tokenScheduler = new TokenScheduler(60);
Comment on lines +64 to +65
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent comment: says 30 minutes, code uses 60.

The comment states "runs every 30 minutes" but the constructor argument is 60.

🔎 Proposed fix
-// Default scheduler instance - runs every 30 minutes
+// Default scheduler instance - runs every 60 minutes
 export const tokenScheduler = new TokenScheduler(60);
📝 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
// Default scheduler instance - runs every 30 minutes
export const tokenScheduler = new TokenScheduler(60);
// Default scheduler instance - runs every 60 minutes
export const tokenScheduler = new TokenScheduler(60);
🤖 Prompt for AI Agents
In apps/http-backend/src/scheduler/token-scheduler.ts around lines 64-65, the
inline comment claims "runs every 30 minutes" while the TokenScheduler is
constructed with 60; make them consistent by either updating the comment to
reflect "runs every 60 minutes" or changing the constructor argument to 30
(depending on intended behavior)—pick the intended interval, apply the matching
change, and ensure any related docs/tests reflect the chosen interval.


// Export class for custom configurations
export { TokenScheduler };
Loading