Skip to content

Refactor auth controller into modular structure#1376

Merged
alexanmtz merged 4 commits intomasterfrom
copilot/refactor-auth-controller-structure
Feb 4, 2026
Merged

Refactor auth controller into modular structure#1376
alexanmtz merged 4 commits intomasterfrom
copilot/refactor-auth-controller-structure

Conversation

Copy link
Contributor

Copilot AI commented Feb 4, 2026

Description

Decomposed monolithic /src/app/controllers/auth.ts (442 lines) into domain-organized modules under auth/ and user/ folders, using ES6 import statements throughout.

Changes

New controller modules:

  • user/customers.ts - Customer management (customer, customerCreate, customerUpdate)
  • user/account.ts - Stripe Connect accounts (account, accountCreate, accountBalance, accountUpdate, accountDelete, accountCountries)
  • user/bank-account.ts - Bank account operations (createBankAccount, updateBankAccount, userBankAccount)
  • auth/callbacks.ts - OAuth callbacks (callbackGithub, callbackBitbucket)

Relocated to user/user.ts:

  • User lifecycle: register, activateUser, resendActivationEmail, deleteUserById
  • Password management: forgotPasswordNotification, resetPassword, changePassword
  • User data: preferences, organizations, userFetch
  • Private task creation: createPrivateTask

Relocated to auth/auth.ts:

  • OAuth flows: authorizeGithubPrivateIssue, connectGithub, disconnectGithub, authorizeLocal

Method renames (snake_case → camelCase):

  • activate_useractivateUser
  • resend_activation_emailresendActivationEmail

Route updates:

  • Updated imports in routes/auth/auth.ts, routes/auth/providers.ts, routes/user.ts
  • Maintained existing endpoint paths and behavior

Code modernization:

  • Replaced all require() statements with ES6 import statements across all new controller modules
  • Consistent use of modern import syntax for better type safety and tooling support
  • Fixed TypeScript build errors:
    • Imported userDisconnectGithub directly from module file to resolve export issue
    • Applied proper TypeScript pattern for models: import Models from '../../../models' with const models = Models as any

Removed:

  • /src/app/controllers/auth.ts (all methods migrated)

Thank you for your contribution!


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: alexanmtz <88840+alexanmtz@users.noreply.github.com>
router.get('/authorize/github/private', controllers.authorizeGithubPrivateIssue)
router.get('/authorize/github/disconnect', secure, controllers.disconnectGithub)
router.get('/authorize/github/private', authorizeGithubPrivateIssue)
router.get('/authorize/github/disconnect', secure, disconnectGithub)

Check failure

Code scanning / CodeQL

Missing rate limiting High

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

Copilot Autofix

AI 5 days ago

In general, the fix is to add a rate-limiting middleware to routes that perform expensive operations or sensitive authorization flows, so that a single client cannot bombard them with requests. In an Express-based TypeScript application, a common approach is to use the well-known express-rate-limit package, configure a limiter (e.g., max N requests per time window), and then add that limiter to the relevant routes as an extra middleware argument.

For this file, the smallest targeted change that preserves existing functionality is:

  • Import express-rate-limit.
  • Define one or more limiter instances (e.g., for auth-related routes), using reasonable defaults (for example, 100 requests per 15 minutes per IP).
  • Attach the limiter to the specific route that CodeQL flagged (/authorize/github/disconnect) and, optionally, to closely related authorization routes in this router. To keep the change minimal and focused, we will apply it to /authorize/github/disconnect only, since that is what CodeQL pointed out.
  • This is done by adding the limiter as a middleware parameter in router.get('/authorize/github/disconnect', secure, disconnectGithub) so that the call becomes router.get('/authorize/github/disconnect', githubDisconnectRateLimiter, secure, disconnectGithub) (order: limiter first, then existing middlewares/handlers).

Concretely:

  • At the top of src/app/routes/auth/providers.ts, add an import for express-rate-limit.
  • Near the router definition (after creating router), define a githubDisconnectRateLimiter with windowMs and max settings.
  • Update the /authorize/github/disconnect route line to include this limiter as an additional middleware.
Suggested changeset 2
src/app/routes/auth/providers.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/routes/auth/providers.ts b/src/app/routes/auth/providers.ts
--- a/src/app/routes/auth/providers.ts
+++ b/src/app/routes/auth/providers.ts
@@ -5,9 +5,15 @@
 import { callbackGithub, callbackBitbucket } from '../../controllers/auth/callbacks'
 import { createPrivateTask } from '../../controllers/user/user'
 import secure from '../secure'
+import rateLimit from 'express-rate-limit'
 
 const router = express.Router()
 
+const githubDisconnectRateLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 100
+})
+
 router.get('/authenticated', authenticationHelpers.isAuth)
 
 router.get('/authorize/google', passport.authenticate('google', { scope: ['email'] }))
@@ -41,7 +44,7 @@
 router.get('/connect/github', secure, connectGithub)
 
 router.get('/authorize/github/private', authorizeGithubPrivateIssue)
-router.get('/authorize/github/disconnect', secure, disconnectGithub)
+router.get('/authorize/github/disconnect', githubDisconnectRateLimiter, secure, disconnectGithub)
 
 router.get('/authorize/bitbucket', passport.authenticate('bitbucket', { scope: ['email'] }))
 router.get(
EOF
@@ -5,9 +5,15 @@
import { callbackGithub, callbackBitbucket } from '../../controllers/auth/callbacks'
import { createPrivateTask } from '../../controllers/user/user'
import secure from '../secure'
import rateLimit from 'express-rate-limit'

const router = express.Router()

const githubDisconnectRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100
})

router.get('/authenticated', authenticationHelpers.isAuth)

router.get('/authorize/google', passport.authenticate('google', { scope: ['email'] }))
@@ -41,7 +44,7 @@
router.get('/connect/github', secure, connectGithub)

router.get('/authorize/github/private', authorizeGithubPrivateIssue)
router.get('/authorize/github/disconnect', secure, disconnectGithub)
router.get('/authorize/github/disconnect', githubDisconnectRateLimiter, secure, disconnectGithub)

router.get('/authorize/bitbucket', passport.authenticate('bitbucket', { scope: ['email'] }))
router.get(
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -128,7 +128,8 @@
     "url": "^0.11.0",
     "url-search-params": "^1.0.2",
     "x-frame-options": "^1.0.0",
-    "zone.js": "^0.8.4"
+    "zone.js": "^0.8.4",
+    "express-rate-limit": "^8.2.1"
   },
   "devDependencies": {
     "@playwright/test": "^1.53.0",
EOF
@@ -128,7 +128,8 @@
"url": "^0.11.0",
"url-search-params": "^1.0.2",
"x-frame-options": "^1.0.0",
"zone.js": "^0.8.4"
"zone.js": "^0.8.4",
"express-rate-limit": "^8.2.1"
},
"devDependencies": {
"@playwright/test": "^1.53.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.2.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
router.get('/callback/github/private', createPrivateTask)

router.get('/connect/github', secure, controllers.connectGithub)
router.get('/connect/github', secure, connectGithub)

Check failure

Code scanning / CodeQL

Missing rate limiting High

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

Copilot Autofix

AI 5 days ago

In general, to fix missing rate limiting on an authorization endpoint, we add a rate-limiting middleware before the route handler. In an Express app, a common approach is to use a package like express-rate-limit to create a limiter (e.g., a small number of requests per IP per unit time for sensitive auth routes) and apply it specifically to the vulnerable route(s).

For this file, the least invasive fix is:

  1. Import express-rate-limit.
  2. Define a dedicated limiter for auth/provider routes (e.g., authProviderLimiter) near the router initialization.
  3. Apply that limiter to /connect/github by inserting it into the middleware chain: router.get('/connect/github', authProviderLimiter, secure, connectGithub) so existing behavior is preserved except for the new limit.
  4. Optionally, the same limiter could be reused for other /authorize/* or /callback/* routes, but to minimize change, we will only apply it to /connect/github as requested.

Concretely in src/app/routes/auth/providers.ts:

  • Add import rateLimit from 'express-rate-limit' at the top.
  • Add a constant const authProviderLimiter = rateLimit({ ... }) after creating router.
  • Modify line 41 so that authProviderLimiter is inserted as the first middleware for that route, preserving secure and connectGithub order.
Suggested changeset 2
src/app/routes/auth/providers.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/routes/auth/providers.ts b/src/app/routes/auth/providers.ts
--- a/src/app/routes/auth/providers.ts
+++ b/src/app/routes/auth/providers.ts
@@ -5,9 +5,15 @@
 import { callbackGithub, callbackBitbucket } from '../../controllers/auth/callbacks'
 import { createPrivateTask } from '../../controllers/user/user'
 import secure from '../secure'
+import rateLimit from 'express-rate-limit'
 
 const router = express.Router()
 
+const authProviderLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 50 // limit each IP to 50 requests per window for auth provider actions
+})
+
 router.get('/authenticated', authenticationHelpers.isAuth)
 
 router.get('/authorize/google', passport.authenticate('google', { scope: ['email'] }))
@@ -38,7 +41,7 @@
 
 router.get('/callback/github/private', createPrivateTask)
 
-router.get('/connect/github', secure, connectGithub)
+router.get('/connect/github', authProviderLimiter, secure, connectGithub)
 
 router.get('/authorize/github/private', authorizeGithubPrivateIssue)
 router.get('/authorize/github/disconnect', secure, disconnectGithub)
EOF
@@ -5,9 +5,15 @@
import { callbackGithub, callbackBitbucket } from '../../controllers/auth/callbacks'
import { createPrivateTask } from '../../controllers/user/user'
import secure from '../secure'
import rateLimit from 'express-rate-limit'

const router = express.Router()

const authProviderLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 50 // limit each IP to 50 requests per window for auth provider actions
})

router.get('/authenticated', authenticationHelpers.isAuth)

router.get('/authorize/google', passport.authenticate('google', { scope: ['email'] }))
@@ -38,7 +41,7 @@

router.get('/callback/github/private', createPrivateTask)

router.get('/connect/github', secure, connectGithub)
router.get('/connect/github', authProviderLimiter, secure, connectGithub)

router.get('/authorize/github/private', authorizeGithubPrivateIssue)
router.get('/authorize/github/disconnect', secure, disconnectGithub)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -128,7 +128,8 @@
     "url": "^0.11.0",
     "url-search-params": "^1.0.2",
     "x-frame-options": "^1.0.0",
-    "zone.js": "^0.8.4"
+    "zone.js": "^0.8.4",
+    "express-rate-limit": "^8.2.1"
   },
   "devDependencies": {
     "@playwright/test": "^1.53.0",
EOF
@@ -128,7 +128,8 @@
"url": "^0.11.0",
"url-search-params": "^1.0.2",
"x-frame-options": "^1.0.0",
"zone.js": "^0.8.4"
"zone.js": "^0.8.4",
"express-rate-limit": "^8.2.1"
},
"devDependencies": {
"@playwright/test": "^1.53.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.2.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
router.get('/callback/github/private', createPrivateTask)

router.get('/connect/github', secure, controllers.connectGithub)
router.get('/connect/github', secure, connectGithub)

Check failure

Code scanning / CodeQL

Missing rate limiting High

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

Copilot Autofix

AI 5 days ago

In general, the issue should be fixed by adding a rate-limiting middleware to sensitive routes that perform authorization or other expensive operations. In an Express-based app, this is commonly done with express-rate-limit, configuring limits that are appropriate for the route (e.g., low frequency for connect/link flows), and then inserting the limiter before the existing middleware/handler chain.

For this specific file, the minimal, non-breaking fix is to introduce a rate limiter and apply it to the /connect/github route (and, optionally, other sensitive auth routes if desired). We will import express-rate-limit, define a connectRateLimiter (for example, 10 requests per 15 minutes per IP, with standardHeaders and legacyHeaders configured), and add it as a middleware in the route definition, before secure and connectGithub. Concretely:

  • At the top of src/app/routes/auth/providers.ts, add import rateLimit from 'express-rate-limit'.
  • After initializing the router (right after const router = express.Router()), define a const connectRateLimiter = rateLimit({ ... }).
  • Modify the /connect/github route so that it becomes router.get('/connect/github', connectRateLimiter, secure, connectGithub).

This keeps existing behavior intact while adding a clear in-code rate-limiting layer on the route that CodeQL has flagged.

Suggested changeset 2
src/app/routes/auth/providers.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/routes/auth/providers.ts b/src/app/routes/auth/providers.ts
--- a/src/app/routes/auth/providers.ts
+++ b/src/app/routes/auth/providers.ts
@@ -5,9 +5,17 @@
 import { callbackGithub, callbackBitbucket } from '../../controllers/auth/callbacks'
 import { createPrivateTask } from '../../controllers/user/user'
 import secure from '../secure'
+import rateLimit from 'express-rate-limit'
 
 const router = express.Router()
 
+const connectRateLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 10, // limit each IP to 10 connect attempts per windowMs
+  standardHeaders: true,
+  legacyHeaders: false
+})
+
 router.get('/authenticated', authenticationHelpers.isAuth)
 
 router.get('/authorize/google', passport.authenticate('google', { scope: ['email'] }))
@@ -38,7 +43,7 @@
 
 router.get('/callback/github/private', createPrivateTask)
 
-router.get('/connect/github', secure, connectGithub)
+router.get('/connect/github', connectRateLimiter, secure, connectGithub)
 
 router.get('/authorize/github/private', authorizeGithubPrivateIssue)
 router.get('/authorize/github/disconnect', secure, disconnectGithub)
EOF
@@ -5,9 +5,17 @@
import { callbackGithub, callbackBitbucket } from '../../controllers/auth/callbacks'
import { createPrivateTask } from '../../controllers/user/user'
import secure from '../secure'
import rateLimit from 'express-rate-limit'

const router = express.Router()

const connectRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // limit each IP to 10 connect attempts per windowMs
standardHeaders: true,
legacyHeaders: false
})

router.get('/authenticated', authenticationHelpers.isAuth)

router.get('/authorize/google', passport.authenticate('google', { scope: ['email'] }))
@@ -38,7 +43,7 @@

router.get('/callback/github/private', createPrivateTask)

router.get('/connect/github', secure, connectGithub)
router.get('/connect/github', connectRateLimiter, secure, connectGithub)

router.get('/authorize/github/private', authorizeGithubPrivateIssue)
router.get('/authorize/github/disconnect', secure, disconnectGithub)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -128,7 +128,8 @@
     "url": "^0.11.0",
     "url-search-params": "^1.0.2",
     "x-frame-options": "^1.0.0",
-    "zone.js": "^0.8.4"
+    "zone.js": "^0.8.4",
+    "express-rate-limit": "^8.2.1"
   },
   "devDependencies": {
     "@playwright/test": "^1.53.0",
EOF
@@ -128,7 +128,8 @@
"url": "^0.11.0",
"url-search-params": "^1.0.2",
"x-frame-options": "^1.0.0",
"zone.js": "^0.8.4"
"zone.js": "^0.8.4",
"express-rate-limit": "^8.2.1"
},
"devDependencies": {
"@playwright/test": "^1.53.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.2.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.status(400).send(error)

Check warning

Code scanning / CodeQL

Information exposure through a stack trace Medium

This information exposed to the user depends on
stack trace information
.

Copilot Autofix

AI 5 days ago

To fix the problem, the controllers should avoid sending the raw error object (or any stack-trace-bearing details) back to the client. Instead, they should log the full error server-side and send a generic, non-sensitive error response body, such as a static message or a minimal structured JSON error, without including error.message, error.stack, or similar details.

Concretely, in src/app/controllers/user/user.ts, within the userFetch and deleteUserById handlers:

  • Keep logging error to the console (or a real logger) so developers can still debug.
  • Replace res.status(400).send(error) with a safe response, e.g. res.status(400).send({ success: false }) or a generic error message like "Unable to fetch user" / "Unable to delete user". This preserves the HTTP semantics (400 on failure) without exposing internal exception details.
  • No new imports or helper methods are strictly needed; using simple string or object literals is sufficient and least invasive.

Other catch blocks in the snippet (preferences, organizations) already send only false without exposing the error, so they do not require changes.

Suggested changeset 1
src/app/controllers/user/user.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/controllers/user/user.ts b/src/app/controllers/user/user.ts
--- a/src/app/controllers/user/user.ts
+++ b/src/app/controllers/user/user.ts
@@ -250,7 +250,7 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log(error)
-    res.status(400).send(error)
+    res.status(400).send({ success: false })
   }
 }
 
@@ -262,6 +262,6 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log(error)
-    res.status(400).send(error)
+    res.status(400).send({ success: false })
   }
 }
EOF
@@ -250,7 +250,7 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.status(400).send(error)
res.status(400).send({ success: false })
}
}

@@ -262,6 +262,6 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.status(400).send(error)
res.status(400).send({ success: false })
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.status(400).send(error)

Check warning

Code scanning / CodeQL

Information exposure through a stack trace Medium

This information exposed to the user depends on
stack trace information
.

Copilot Autofix

AI 5 days ago

In general, the fix is to avoid sending raw exception objects (or their stack traces) to the client. Instead, log the detailed error server-side, and return a generic, user‑friendly error message and code to the client that does not reveal implementation details. Where appropriate, you can include a simple error identifier or code that can be correlated with server logs.

For this specific case in src/app/controllers/user/user.ts, the problematic line is res.status(400).send(error) in the userFetch controller. To keep existing functionality as close as possible while fixing the leak, we should:

  • Continue logging the error on the server using console.log(error) (as already done).
  • Replace the response body error with a generic message object, such as { message: 'Unable to fetch user data.' } or simply false, depending on how the frontend expects errors. Because the success path sends data with a 200, the client is likely expecting JSON; sending a simple JSON error object with a generic message is a safe default.
  • Do not introduce new behavior that changes status codes beyond the existing 400.

No new imports are needed: we can construct a simple literal object/message inline. Concretely:

  • In userFetch, change line 253 from res.status(400).send(error) to something like res.status(400).send({ message: 'Unable to fetch user data.' }).

Other catch blocks (preferences, organizations, deleteUserById) already avoid sending the raw error and instead send false, so they do not need changes for this specific CodeQL alert.

Suggested changeset 1
src/app/controllers/user/user.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/controllers/user/user.ts b/src/app/controllers/user/user.ts
--- a/src/app/controllers/user/user.ts
+++ b/src/app/controllers/user/user.ts
@@ -250,7 +250,7 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log(error)
-    res.status(400).send(error)
+    res.status(400).send({ message: 'Unable to fetch user data.' })
   }
 }
 
EOF
@@ -250,7 +250,7 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.status(400).send(error)
res.status(400).send({ message: 'Unable to fetch user data.' })
}
}

Copilot is powered by AI and may make mistakes. Always verify output.
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.send(error)

Check warning

Code scanning / CodeQL

Information exposure through a stack trace Medium

This information exposed to the user depends on
stack trace information
.

Copilot Autofix

AI 5 days ago

In general, the fix is to avoid sending raw error/exception objects (including stack traces) to the client. Instead, log the detailed error on the server and return a generic, non-sensitive error message and appropriate HTTP status code (e.g., 500). This preserves debuggability while preventing information exposure.

For this file, the best low-impact fix is:

  • Keep console.log(error) so developers still see the full error on the server.
  • Change res.send(error) in all three catch blocks to a generic error response, e.g. res.status(500).send({ message: 'Internal server error' }) or similar.
  • Optionally, you can use res.status(500).json({ ... }) if JSON is the standard, but since the existing code uses res.send, we can keep that and only adjust the content and status code.
  • No new imports are required; we only modify the catch blocks.

Concretely:

  • In createBankAccount (around line 10–14), replace res.send(error) with res.status(500).send({ message: 'Internal server error' }).
  • In updateBankAccount (around line 21–25), perform the same replacement.
  • In userBankAccount (around line 32–36), perform the same replacement.

This does not change the functional success path, only the error payload and status code.

Suggested changeset 1
src/app/controllers/user/bank-account.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/controllers/user/bank-account.ts b/src/app/controllers/user/bank-account.ts
--- a/src/app/controllers/user/bank-account.ts
+++ b/src/app/controllers/user/bank-account.ts
@@ -10,7 +10,7 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log(error)
-    res.send(error)
+    res.status(500).send({ message: 'Internal server error' })
   }
 }
 
@@ -21,7 +21,7 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log(error)
-    res.send(error)
+    res.status(500).send({ message: 'Internal server error' })
   }
 }
 
@@ -32,6 +32,6 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log(error)
-    res.send(error)
+    res.status(500).send({ message: 'Internal server error' })
   }
 }
EOF
@@ -10,7 +10,7 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.send(error)
res.status(500).send({ message: 'Internal server error' })
}
}

@@ -21,7 +21,7 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.send(error)
res.status(500).send({ message: 'Internal server error' })
}
}

@@ -32,6 +32,6 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log(error)
res.send(error)
res.status(500).send({ message: 'Internal server error' })
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account delete', error)
res.status(401).send(error)

Check warning

Code scanning / CodeQL

Information exposure through a stack trace Medium

This information exposed to the user depends on
stack trace information
.

Copilot Autofix

AI 5 days ago

In general, to fix this issue you should never send raw Error objects or stack traces to the client. Instead, log the full error on the server side and return a generic, non-sensitive error response to the user (for example, a boolean flag, a simple message, or a structured error code that does not include stack trace or internal implementation details).

For this specific file, the minimal and consistent fix is:

  • Keep logging the full error object to the server console for debugging (as the rest of the file already does).
  • Replace res.status(401).send(error) in accountDelete with a generic response, such as res.status(401).send(false) or a simple message like "Unable to delete account". To preserve existing behavior patterns in this file, it is best to respond with false, as done in account, accountCreate, accountCountries, and accountBalance.
  • No new imports or helper methods are required; we only adjust what is returned to the client.

Concretely:

  • In src/app/controllers/user/account.ts, lines 55–56 and 65–66 are currently sending the raw error. Update both accountUpdate and accountDelete to send a generic failure (false) instead:
    • Change res.status(401).send(error) to res.status(401).send(false) in both catch blocks.
Suggested changeset 1
src/app/controllers/user/account.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/controllers/user/account.ts b/src/app/controllers/user/account.ts
--- a/src/app/controllers/user/account.ts
+++ b/src/app/controllers/user/account.ts
@@ -52,7 +52,7 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log('error on account update', error)
-    res.status(401).send(error)
+    res.status(401).send(false)
   }
 }
 
@@ -63,6 +63,6 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log('error on account delete', error)
-    res.status(401).send(error)
+    res.status(401).send(false)
   }
 }
EOF
@@ -52,7 +52,7 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account update', error)
res.status(401).send(error)
res.status(401).send(false)
}
}

@@ -63,6 +63,6 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account delete', error)
res.status(401).send(error)
res.status(401).send(false)
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account update', error)
res.status(401).send(error)

Check warning

Code scanning / CodeQL

Information exposure through a stack trace Medium

This information exposed to the user depends on
stack trace information
.

Copilot Autofix

AI 5 days ago

In general, to fix information exposure through stack traces you should avoid sending raw exception objects or stack traces to clients. Instead, log the details on the server and return a generic, non-sensitive error response to the client (optionally with a simple error code/message that doesn’t reveal internal structure).

For this file, the minimal, non-breaking fix is to change accountUpdate (and, for consistency and security, accountDelete) so that they no longer send the raw error object. They should instead log the error (as they already do) and respond with a generic message or a simple boolean as done in the other handlers. To preserve existing semantics as much as possible, we can keep the same HTTP status code (401) but replace send(error) with a safe payload such as send(false) or a generic string like "Unauthorized" or "An error occurred while updating the account". Since the other endpoints respond with false on error, the most consistent choice is res.status(401).send(false).

Concretely:

  • In src/app/controllers/user/account.ts, in accountUpdate, replace res.status(401).send(error) with res.status(401).send(false).
  • In accountDelete, replace res.status(401).send(error) with res.status(401).send(false) to avoid the same pattern of exposing the raw error and to keep behavior consistent.
    No new imports or helper methods are required; we only change what is sent in the response.
Suggested changeset 1
src/app/controllers/user/account.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/controllers/user/account.ts b/src/app/controllers/user/account.ts
--- a/src/app/controllers/user/account.ts
+++ b/src/app/controllers/user/account.ts
@@ -52,7 +52,7 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log('error on account update', error)
-    res.status(401).send(error)
+    res.status(401).send(false)
   }
 }
 
@@ -63,6 +63,6 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log('error on account delete', error)
-    res.status(401).send(error)
+    res.status(401).send(false)
   }
 }
EOF
@@ -52,7 +52,7 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account update', error)
res.status(401).send(error)
res.status(401).send(false)
}
}

@@ -63,6 +63,6 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account delete', error)
res.status(401).send(error)
res.status(401).send(false)
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account update', error)
res.status(401).send(error)

Check warning

Code scanning / CodeQL

Exception text reinterpreted as HTML Medium

Exception text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 5 days ago

In general, the problem should be fixed by ensuring that exception text or error objects that may contain user input are not sent back to clients in a form that can be interpreted as HTML/JS. Instead, return a controlled, neutral error structure (e.g., a generic message and optional error code), and avoid echoing raw exception messages. If you must include details, send them only in safe contexts (logging, monitoring) or after proper encoding/sanitization.

For this codebase, the best minimal fix is to change the accountUpdate and accountDelete controllers so they no longer send the raw error object. Instead, log the full error server-side (as already done), and return a generic, structured JSON error payload such as { success: false, error: '...' } with an appropriate content type and HTTP status code. This keeps existing semantics (401 status, indication of failure) but removes user-controlled content from the HTTP body. We will modify:

  • src/app/controllers/user/account.ts, line 55: replace res.status(401).send(error) with res.status(401).json({ error: 'Could not update account' }) (or similar generic message).
  • src/app/controllers/user/account.ts, line 66: replace res.status(401).send(error) with res.status(401).json({ error: 'Could not delete account' }).

No changes are needed in src/modules/users/userAccountUpdate.ts for this specific sink; the core issue is the unsafe exposure of error in the controller. No new imports are required, as res.json is part of Express’s response API and already available.


Suggested changeset 1
src/app/controllers/user/account.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/controllers/user/account.ts b/src/app/controllers/user/account.ts
--- a/src/app/controllers/user/account.ts
+++ b/src/app/controllers/user/account.ts
@@ -52,7 +52,7 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log('error on account update', error)
-    res.status(401).send(error)
+    res.status(401).json({ error: 'Could not update account' })
   }
 }
 
@@ -63,6 +63,6 @@
   } catch (error: any) {
     // eslint-disable-next-line no-console
     console.log('error on account delete', error)
-    res.status(401).send(error)
+    res.status(401).json({ error: 'Could not delete account' })
   }
 }
EOF
@@ -52,7 +52,7 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account update', error)
res.status(401).send(error)
res.status(401).json({ error: 'Could not update account' })
}
}

@@ -63,6 +63,6 @@
} catch (error: any) {
// eslint-disable-next-line no-console
console.log('error on account delete', error)
res.status(401).send(error)
res.status(401).json({ error: 'Could not delete account' })
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
export const account = async (req: any, res: any) => {
try {
const data = await user.userAccount({ id: req.user.id })
res.send(data)

Check warning

Code scanning / CodeQL

Information exposure through a stack trace Medium

This information exposed to the user depends on
stack trace information
.

Copilot Autofix

AI 5 days ago

General approach: do not propagate raw exceptions or stack traces to user-facing responses. Instead, log detailed error information on the server and return a generic, safe payload to the caller (controller), which in turn should send only that generic payload to the client. That means userAccount should never return e directly; it should return either an empty object or a normalized error structure that does not include stack traces or internal implementation details. The controller logic can remain the same if the returned data is guaranteed safe.

Best concrete fix for this code:

  1. In src/modules/users/userAccount.ts, change the catch block inside userAccount so that:
    • It still logs the full error to the server (console.log('could not find customer', e)).
    • It returns a safe, generic object instead of the raw error: for example, { error: 'ACCOUNT_RETRIEVAL_FAILED' } or {}. This prevents potential stack trace or internal Stripe information from being sent out.
  2. In src/app/controllers/user/account.ts, the account handler currently returns whatever userAccount yields via res.send(data). Once userAccount is returning a sanitized object, this is safe and we do not need to modify the controller logic for this issue. The other handlers (accountCreate, accountCountries, accountBalance, accountUpdate, accountDelete) are not part of the taint path identified for this specific alert, and we leave them as is per the instructions not to change functionality unnecessarily.

No new imports or helper methods are needed; we only adjust the return value in the existing catch block.

Suggested changeset 1
src/modules/users/userAccount.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/modules/users/userAccount.ts b/src/modules/users/userAccount.ts
--- a/src/modules/users/userAccount.ts
+++ b/src/modules/users/userAccount.ts
@@ -20,7 +20,7 @@
     } catch (e) {
       // eslint-disable-next-line no-console
       console.log('could not find customer', e)
-      return e
+      return { error: 'ACCOUNT_RETRIEVAL_FAILED' }
     }
   }
   return {}
EOF
@@ -20,7 +20,7 @@
} catch (e) {
// eslint-disable-next-line no-console
console.log('could not find customer', e)
return e
return { error: 'ACCOUNT_RETRIEVAL_FAILED' }
}
}
return {}
Copilot is powered by AI and may make mistakes. Always verify output.
Copilot AI changed the title [WIP] Refactor auth controller methods to new structured files Refactor auth controller into modular structure Feb 4, 2026
Copilot AI requested a review from alexanmtz February 4, 2026 13:09
@alexanmtz alexanmtz marked this pull request as ready for review February 4, 2026 14:08
@dosubot dosubot bot added the refactoring label Feb 4, 2026
Co-authored-by: alexanmtz <88840+alexanmtz@users.noreply.github.com>
Copy link
Member

@alexanmtz alexanmtz left a comment

Choose a reason for hiding this comment

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

Many build errors

my-gitpay@0.0.0 build
tsc -p tsconfig.json

src/app/controllers/auth/auth.ts:55:29 - error TS2339: Property 'userDisconnectGithub' does not exist on type 'typeof import("/home/circleci/repo/src/modules/users/index")'.

55 const data = await user.userDisconnectGithub({ userId: req.user.id })
~~~~~~~~~~~~~~~~~~~~

src/app/controllers/user/user.ts:68:28 - error TS2339: Property 'User' does not exist on type 'typeof db'.

68 const token = models.User.generateToken()
~~~~

src/app/controllers/user/user.ts:69:20 - error TS2339: Property 'User' does not exist on type 'typeof db'.

69 await models.User.update({ recover_password_token: token }, { where: { email } })
~~~~

src/app/controllers/user/user.ts:92:36 - error TS2339: Property 'User' does not exist on type 'typeof db'.

92 const foundUser = await models.User.findOne({
~~~~

src/app/controllers/user/user.ts:96:33 - error TS2339: Property 'User' does not exist on type 'typeof db'.

96 const passwordHash = models.User.generateHash(req.body.password)
~~~~

src/app/controllers/user/user.ts:98:20 - error TS2339: Property 'User' does not exist on type 'typeof db'.

98 await models.User.update(
~~~~

src/app/controllers/user/user.ts:181:36 - error TS2339: Property 'User' does not exist on type 'typeof db'.

181 const foundUser = await models.User.findOne({ where: { activation_token: token, id: userId } })
~~~~

src/app/controllers/user/user.ts:185:39 - error TS2339: Property 'User' does not exist on type 'typeof db'.

185 const userUpdate = await models.User.update(
~~~~

src/app/controllers/user/user.ts:201:36 - error TS2339: Property 'User' does not exist on type 'typeof db'.

201 const foundUser = await models.User.findOne({ where: { id: userId } })
~~~~

src/app/controllers/user/user.ts:204:26 - error TS2339: Property 'User' does not exist on type 'typeof db'.

204 const token = models.User.generateToken()
~~~~

src/app/controllers/user/user.ts:207:21 - error TS2339: Property 'User' does not exist on type 'typeof db'.

207 (await models.User.update(
~~~~

Found 11 errors in 2 files.

Errors Files
1 src/app/controllers/auth/auth.ts:55
10 src/app/controllers/user/user.ts:68

Exited with code exit status 1

Co-authored-by: alexanmtz <88840+alexanmtz@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 4, 2026

Many build errors

my-gitpay@0.0.0 build...

Fixed in cc059d2. Resolved all 11 build errors:

userDisconnectGithub error: Import changed to direct import from the file:

import userDisconnectGithub from '../../../modules/users/userDisconectGithub'

models.User errors (10 instances): Fixed by using the proper TypeScript pattern:

import Models from '../../../models'
const models = Models as any

This matches the pattern used throughout the codebase (e.g., in src/queries/user/findUserById.ts).

Copilot AI requested a review from alexanmtz February 4, 2026 14:34
@alexanmtz alexanmtz merged commit 2499fc1 into master Feb 4, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants