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
16 changes: 9 additions & 7 deletions backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ app.set('trust proxy', 1);
const GC = require('./utils/GC');
const { getPublicIp } = require('./utils/network');

// Initialize Queue Workers
require('./queues/emailQueue');

Comment on lines +19 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling for Redis connection at startup.

The synchronous require('./queues/emailQueue') initializes an IORedis connection immediately. If Redis is unavailable when the server starts, the connection will throw synchronously and crash the Express process before it can bind to a port.

Consider adding connection error handlers or lazy initialization to allow the app to start gracefully and degrade when Redis is down.

🛡️ Suggested approach: wrap initialization with error handling
 // Initialize Queue Workers
-require('./queues/emailQueue');
+try {
+    require('./queues/emailQueue');
+} catch (err) {
+    console.error('❌ Failed to initialize email queue:', err.message);
+    // Optionally: set a flag to disable email features
+}

Alternatively, add .on('error') handlers in emailQueue.js to the IORedis connection to prevent unhandled exceptions.

📝 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
// Initialize Queue Workers
require('./queues/emailQueue');
// Initialize Queue Workers
try {
require('./queues/emailQueue');
} catch (err) {
console.error('❌ Failed to initialize email queue:', err.message);
// Optionally: set a flag to disable email features
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app.js` around lines 19 - 21, The app currently
requires('./queues/emailQueue') at startup which creates an IORedis connection
synchronously and can throw if Redis is down; change the initialization so the
Redis client in emailQueue.js is created with proper error handling or lazily
initialized: add a top-level .on('error', handler) and .on('connect'/'ready')
handlers to the IORedis client inside emailQueue.js, and wrap the initial
connection attempt in a try/catch or export an async init function (e.g.,
initEmailQueue) that backend/app.js can call after the Express server binds,
falling back to degraded behavior if the connection fails; ensure any unhandled
errors are caught and logged via your logger instead of letting the require
crash the process.

// Middleware
app.use(cors());
app.use(express.json());
Expand All @@ -34,7 +37,7 @@ const { authLimiter } = require('./middleware/auth_limiter');

const adminWhitelist = ['https://urbackend.bitbros.in'];

// to allow localhost in developmentt
// DEV LOCALHOST
if (process.env.NODE_ENV === 'development') {
adminWhitelist.push('http://localhost:5173');
}
Expand All @@ -46,8 +49,6 @@ const adminCorsOptions = {
const allowed = !origin || adminWhitelist.includes(origin);

const end = process.hrtime.bigint();
console.log("Pure CORS check time:",
Number(end - start) / 1e6, "ms");

if (allowed) {
callback(null, true);
Expand All @@ -65,7 +66,7 @@ if (process.env.NODE_ENV !== 'test') {
}


// rate limiter and loggerr IMPORTS
// LOGGING
const { limiter, logger } = require('./middleware/api_usage');

// Route Imports
Expand All @@ -75,6 +76,7 @@ const dataRoute = require('./routes/data');
const userAuthRoute = require('./routes/userAuth');
const storageRoute = require('./routes/storage');
const schemaRoute = require('./routes/schemas');
const releaseRoute = require('./routes/releases');

// ROUTES SETUP
app.use('/api/auth/login', authLimiter); // Strict limiter on login
Expand All @@ -85,6 +87,7 @@ app.use('/api/userAuth', limiter, logger, userAuthRoute);
app.use('/api/data', limiter, cors(adminCorsOptions), logger, dataRoute);
app.use('/api/schemas', limiter, cors(adminCorsOptions), logger, schemaRoute);
app.use('/api/storage', limiter, cors(adminCorsOptions), logger, storageRoute);
app.use('/api/releases', releaseRoute);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing authentication middleware on release routes causes runtime error and security bypass.

The /api/releases route is mounted without authentication middleware. According to backend/routes/releases.js, the authMiddleware is imported but never applied to the POST / endpoint. The createRelease controller (line 24 in release.controller.js) assumes req.user is populated:

if (req.user.email !== ADMIN_EMAIL)

Without auth middleware, req.user is undefined, causing a TypeError: Cannot read property 'email' of undefined instead of a proper 401 response. Additionally, unauthenticated requests can reach the controller logic.

🔒 Proposed fix: Apply auth middleware in the route file

In backend/routes/releases.js, apply the imported authorization middleware to the POST route:

 // CREATE RELEASE (Admin Only)
-router.post('/', createReleaseLimiter, createRelease);
+router.post('/', createReleaseLimiter, authorization, createRelease);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app.js` at line 92, The releases routes are mounted without
authentication (app.use('/api/releases', releaseRoute)), so createRelease in
release.controller.js assumes req.user exists and throws; update the routes in
releases.js to apply the imported auth/authorization middleware to the POST '/'
endpoint (or to the router globally) so that req.user is populated before
createRelease runs; ensure the middleware symbol used (e.g., authMiddleware or
authorization) is the same one imported in releases.js and that the POST route
points to the createRelease controller.


app.get('/api/server-ip', async (req, res) => {
const ip = await getPublicIp();
Expand All @@ -111,8 +114,7 @@ app.use((err, req, res, next) => {
message: err.message
});
});
// DB and server initialization
// (Only connect if NOT in Test Mode)
// INITIALIZATION
if (process.env.NODE_ENV !== 'test') {

const PORT = process.env.PORT || 1234;
Expand Down Expand Up @@ -145,7 +147,7 @@ if (process.env.NODE_ENV !== 'test') {
console.log(`Server running on port ${PORT}`);
});

// handle gracefll shutdwn
// SHUTDOWN
const gracefulShutdown = async () => {
console.log('🛑 SIGTERM/SIGINT received. Shutting down gracefully...');

Expand Down
22 changes: 13 additions & 9 deletions backend/controllers/auth.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const Project = require("../models/Project")
const bcrypt = require("bcryptjs");
const z = require("zod");
const jwt = require("jsonwebtoken");
const sendOtp = require("../utils/emailService");
const { sendOtp } = require("../utils/emailService");
const crypto = require("crypto");
const {
loginSchema,
Expand Down Expand Up @@ -55,7 +55,7 @@ async function validateOtp(userId, passedOtp) {

module.exports.register = async (req, res) => {
try {
// Validate with Zod
// POST FOR - REGISTER
const { email, password } = loginSchema.parse(req.body);

const existingUser = await Developer.findOne({ email });
Expand Down Expand Up @@ -86,9 +86,9 @@ module.exports.login = async (req, res) => {
const validPass = await bcrypt.compare(password, dev.password);
if (!validPass) return res.status(400).json({ error: "Invalid password" });

// FIX 1: JWT now expires in 7 days
// JWT EXPIRE
const token = jwt.sign(
{ _id: dev._id, isVerified: dev.isVerified },
{ _id: dev._id, isVerified: dev.isVerified, maxProjects: dev.maxProjects },
process.env.JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
Expand All @@ -108,6 +108,7 @@ module.exports.login = async (req, res) => {

module.exports.changePassword = async (req, res) => {
try {
// POST FOR - CHANGE PASSWORD
const { currentPassword, newPassword } = changePasswordSchema.parse(req.body);

const dev = await Developer.findById(req.user._id);
Expand All @@ -132,6 +133,7 @@ module.exports.changePassword = async (req, res) => {

module.exports.deleteAccount = async (req, res) => {
try {
// POST FOR - DELETE ACCOUNT
const { password } = deleteAccountSchema.parse(req.body);

const dev = await Developer.findById(req.user._id);
Expand Down Expand Up @@ -174,6 +176,7 @@ module.exports.sendOtp = async (req, res) => {

module.exports.verifyOtp = async (req, res) => {
try {
// POST FOR - VERIFY OTP
const { email, otp } = verifyOtpSchema.parse(req.body);

const existingUser = await Developer.findOne({ email });
Expand All @@ -185,9 +188,9 @@ module.exports.verifyOtp = async (req, res) => {
existingUser.isVerified = true;
await existingUser.save();

// FIX 1: JWT with expiry
// JWT
const token = jwt.sign(
{ _id: existingUser._id, isVerified: true },
{ _id: existingUser._id, isVerified: true, maxProjects: existingUser.maxProjects },
process.env.JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
Expand All @@ -202,7 +205,7 @@ module.exports.verifyOtp = async (req, res) => {
}


// FIX 5: Forgot Password — generate + send reset OTP
// FORGOT PASSWORD
module.exports.forgotPassword = async (req, res) => {
try {
const { email } = onlyEmailSchema.parse(req.body);
Expand All @@ -223,17 +226,18 @@ module.exports.forgotPassword = async (req, res) => {
}


// FIX 5: Reset Password — verify OTP then set new password
// RESET PASSWORD
module.exports.resetPassword = async (req, res) => {
try {
// POST FOR - RESET PASSWORD
const { email, otp, newPassword } = resetPasswordSchema.parse(req.body);

const dev = await Developer.findOne({ email });
if (!dev) return res.status(400).json({ error: "User not found" });

const otpDoc = await validateOtp(dev._id, otp);

// OTP matched — update password
// UPDATE PASSWORD
await otpDoc.deleteOne();
const salt = await bcrypt.genSalt(10);
dev.password = await bcrypt.hash(newPassword, salt);
Expand Down
Loading