diff --git a/apps/dashboard-api/src/app.js b/apps/dashboard-api/src/app.js index a746f2f47..cfd4d86f5 100644 --- a/apps/dashboard-api/src/app.js +++ b/apps/dashboard-api/src/app.js @@ -137,7 +137,7 @@ app.use((err, req, res, next) => { app.use((req, res) => { const id = res.get("X-Kiroo-Replay-ID"); - res.json({error: "Not Found", replayId: id}) + res.status(404).json({error: "Not Found", replayId: id}) }) if (process.env.NODE_ENV !== 'test') { @@ -160,9 +160,14 @@ if (process.env.NODE_ENV !== 'test') { try { await mongoose.connection.close(false); console.log('✅ MongoDB connection closed.'); + + const { redis } = require('@urbackend/common'); + if (redis && typeof redis.quit === 'function') await redis.quit(); + console.log('✅ Redis connection closed.'); + process.exit(0); } catch (err) { - console.error('❌ Error closing MongoDB connection:', err); + console.error('❌ Error shutting down properly:', err); process.exit(1); } }); diff --git a/apps/dashboard-api/src/controllers/project.controller.js b/apps/dashboard-api/src/controllers/project.controller.js index 16fa244b2..51102f3f2 100644 --- a/apps/dashboard-api/src/controllers/project.controller.js +++ b/apps/dashboard-api/src/controllers/project.controller.js @@ -493,12 +493,8 @@ module.exports.updateExternalConfig = async (req, res) => { module.exports.deleteExternalDbConfig = async (req, res) => { try { - const parsedBody = z - .object({ - projectId: z.string(), - }) - .parse(req.body); - const { projectId } = parsedBody; + const projectId = String(req.params.projectId || '').trim(); + if (!projectId) return res.status(400).json({ error: "Project ID is required." }); const project = await Project.findOne({ _id: { $eq: projectId }, @@ -513,6 +509,9 @@ module.exports.deleteExternalDbConfig = async (req, res) => { project.resources.db.config = null; await project.save(); + await deleteProjectById(projectId); + await setProjectById(projectId, project.toObject()); + res .status(200) .json({ message: "External configuration deleted successfully." }); @@ -523,12 +522,8 @@ module.exports.deleteExternalDbConfig = async (req, res) => { module.exports.deleteExternalStorageConfig = async (req, res) => { try { - const parsedBody = z - .object({ - projectId: z.string(), - }) - .parse(req.body); - const { projectId } = parsedBody; + const projectId = String(req.params.projectId || '').trim(); + if (!projectId) return res.status(400).json({ error: "Project ID is required." }); const project = await Project.findOne({ _id: { $eq: projectId }, diff --git a/apps/dashboard-api/src/middlewares/authMiddleware.js b/apps/dashboard-api/src/middlewares/authMiddleware.js index 424740bd6..652a35c29 100644 --- a/apps/dashboard-api/src/middlewares/authMiddleware.js +++ b/apps/dashboard-api/src/middlewares/authMiddleware.js @@ -31,7 +31,6 @@ module.exports = function (req, res, next) { // Proceed to the next middleware or route handler next(); } catch (err) { - console.log("err---------------2") console.error(err); res.status(400).json({ error: 'Invalid Token' }); diff --git a/apps/dashboard-api/src/middlewares/loadProjectForAdmin.js b/apps/dashboard-api/src/middlewares/loadProjectForAdmin.js deleted file mode 100644 index 514c8afc7..000000000 --- a/apps/dashboard-api/src/middlewares/loadProjectForAdmin.js +++ /dev/null @@ -1,19 +0,0 @@ -// FUNCTION - LOAD PROJECT FOR ADMIN (MIDDLEWARE) -const Project = require('../models/Project'); - -module.exports = async (req, res, next) => { - try { - const { projectId } = req.params; - if (!projectId) return res.status(400).json({ error: "Project ID is required" }); - - const project = await Project.findOne({ _id: projectId, owner: req.user._id }); - if (!project) { - return res.status(404).json({ error: "Project not found or access denied" }); - } - - req.project = project; - next(); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}; diff --git a/apps/public-api/src/__tests__/storage.controller.test.js b/apps/public-api/src/__tests__/storage.controller.test.js index f03020604..adbd4cc57 100644 --- a/apps/public-api/src/__tests__/storage.controller.test.js +++ b/apps/public-api/src/__tests__/storage.controller.test.js @@ -219,7 +219,7 @@ describe('storage.controller', () => { { _id: 'project_id_1' }, { $inc: { storageUsed: -1024 } } ); - expect(res.json).toHaveBeenCalledWith({ message: 'File deleted successfully' }); + expect(res.json).toHaveBeenCalledWith({ success: true, data: {}, message: 'File deleted successfully' }); }); test('returns 200 on successful external deletion (skips internal usage list)', async () => { @@ -233,7 +233,7 @@ describe('storage.controller', () => { expect(mockStorageFrom.list).not.toHaveBeenCalled(); expect(Project.updateOne).not.toHaveBeenCalled(); - expect(res.json).toHaveBeenCalledWith({ message: 'File deleted successfully' }); + expect(res.json).toHaveBeenCalledWith({ success: true, data: {}, message: 'File deleted successfully' }); }); test('returns 500 when Supabase list fails', async () => { @@ -285,7 +285,7 @@ describe('storage.controller', () => { mockStorageFrom.remove.mockResolvedValue({ data: [{ path: 'project_id_1/file1.txt' }], error: null }); - const req = { project: makeProject() }; + const req = { project: makeProject(), keyRole: 'secret', body: { confirm: 'DELETE_ALL_FILES' } }; const res = makeRes(); await storageController.deleteAllFiles(req, res); @@ -311,7 +311,7 @@ describe('storage.controller', () => { mockStorageFrom.remove.mockResolvedValue({ data: [{}], error: null }); - const req = { project: makeProject() }; + const req = { project: makeProject(), keyRole: 'secret', body: { confirm: 'DELETE_ALL_FILES' } }; const res = makeRes(); await storageController.deleteAllFiles(req, res); @@ -325,7 +325,7 @@ describe('storage.controller', () => { mockStorageFrom.list.mockResolvedValue({ data: [{ name: 'file1.txt' }], error: null }); mockStorageFrom.remove.mockResolvedValue({ data: null, error: new Error('Remove failed') }); - const req = { project: makeProject() }; + const req = { project: makeProject(), keyRole: 'secret', body: { confirm: 'DELETE_ALL_FILES' } }; const res = makeRes(); await storageController.deleteAllFiles(req, res); @@ -338,7 +338,7 @@ describe('storage.controller', () => { isProjectStorageExternal.mockReturnValue(false); mockStorageFrom.list.mockResolvedValue({ data: null, error: new Error('Pagination error') }); - const req = { project: makeProject() }; + const req = { project: makeProject(), keyRole: 'secret', body: { confirm: 'DELETE_ALL_FILES' } }; const res = makeRes(); await storageController.deleteAllFiles(req, res); diff --git a/apps/public-api/src/__tests__/userAuth.refresh.test.js b/apps/public-api/src/__tests__/userAuth.refresh.test.js index cd44380e2..15f3d439a 100644 --- a/apps/public-api/src/__tests__/userAuth.refresh.test.js +++ b/apps/public-api/src/__tests__/userAuth.refresh.test.js @@ -148,9 +148,13 @@ describe('public userAuth refresh flow', () => { ); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ - token: 'signed_access_token', - accessToken: 'signed_access_token', - expiresIn: expect.any(String), + success: true, + message: "Login successful", + data: expect.objectContaining({ + token: 'signed_access_token', + accessToken: 'signed_access_token', + expiresIn: expect.any(String), + }) }) ); }); @@ -205,6 +209,7 @@ describe('public userAuth refresh flow', () => { expect.objectContaining({ token: 'signed_access_token', accessToken: 'signed_access_token', + expiresIn: expect.any(String), refreshToken: expect.any(String), }) ); diff --git a/apps/public-api/src/app.js b/apps/public-api/src/app.js index db5cbc362..9c6ee6c65 100644 --- a/apps/public-api/src/app.js +++ b/apps/public-api/src/app.js @@ -25,9 +25,11 @@ const {initWebhookWorker} = require('@urbackend/common'); const {initAuthEmailWorker} = require('@urbackend/common'); // Initialize webhook worker +let webhookWorkerRef = null; +let authEmailWorkerRef = null; if (process.env.NODE_ENV !== 'test') { - initWebhookWorker(); - initAuthEmailWorker(); + webhookWorkerRef = initWebhookWorker(); + authEmailWorkerRef = initAuthEmailWorker(); } app.use(express.json()); @@ -109,7 +111,7 @@ app.use((err, req, res, next) => { app.use((req, res) => { const id = res.get("X-Kiroo-Replay-ID"); - res.json({error: "Not Found", replayId: id}) + res.status(404).json({error: "Not Found", replayId: id}) }) // INITIALIZATION if (process.env.NODE_ENV !== 'test') { @@ -133,9 +135,19 @@ if (process.env.NODE_ENV !== 'test') { try { await mongoose.connection.close(false); console.log('✅ MongoDB connection closed.'); + + const { redis } = require('@urbackend/common'); + + if (webhookWorkerRef) await webhookWorkerRef.close(); + if (authEmailWorkerRef) await authEmailWorkerRef.close(); + console.log('✅ BullMQ Workers closed.'); + + if (redis && typeof redis.quit === 'function') await redis.quit(); + console.log('✅ Redis connection closed.'); + process.exit(0); } catch (err) { - console.error('❌ Error closing MongoDB connection:', err); + console.error('❌ Error shutting down properly:', err); process.exit(1); } }); diff --git a/apps/public-api/src/controllers/data.controller.js b/apps/public-api/src/controllers/data.controller.js index f93d2cde0..cd91abba9 100644 --- a/apps/public-api/src/controllers/data.controller.js +++ b/apps/public-api/src/controllers/data.controller.js @@ -84,7 +84,11 @@ module.exports.insertData = async (req, res) => { }); if (isDebug) console.log(`[DEBUG] insert data took ${(performance.now() - start).toFixed(2)}ms`); - res.status(201).json(result); + res.status(201).json({ + success: true, + data: result, + message: "Data inserted successfully." + }); } catch (err) { console.error(err); @@ -154,8 +158,7 @@ const mergedFilter = Object.keys(baseFilter).length > 0 const data = await features.query.lean(); if (isDebug) console.log(`[DEBUG] getall took ${(performance.now() - start).toFixed(2)}ms`); - - res.json({ + res.status(200).json({ success: true, data: { items: data, @@ -163,7 +166,7 @@ const mergedFilter = Object.keys(baseFilter).length > 0 page: parseInt(req.query.page, 10) || 1, limit: Math.min(parseInt(req.query.limit, 10) || 50, 100) }, - message: "Data fetched successfully" + message: "Data retrieved successfully." }); } catch (err) { console.error(err); @@ -292,8 +295,6 @@ module.exports.aggregateData = async (req, res) => { message: "Aggregation executed successfully.", }); } catch (err) { - console.error(err); - if (err instanceof z.ZodError) { return res.status(400).json({ success: false, @@ -302,6 +303,7 @@ module.exports.aggregateData = async (req, res) => { }); } + console.error(err); return res.status(500).json({ success: false, data: {}, diff --git a/apps/public-api/src/controllers/storage.controller.js b/apps/public-api/src/controllers/storage.controller.js index 5cd7b6171..21ba8adb5 100644 --- a/apps/public-api/src/controllers/storage.controller.js +++ b/apps/public-api/src/controllers/storage.controller.js @@ -133,7 +133,7 @@ module.exports.deleteFile = async (req, res) => { ); } - return res.json({ message: "File deleted successfully" }); + return res.json({ success: true, data: {}, message: "File deleted successfully" }); } catch (err) { return res.status(500).json({ error: "File deletion failed", @@ -152,6 +152,14 @@ module.exports.deleteAllFiles = async (req, res) => { return res.status(404).json({ error: "Project not found" }); } + if (req.keyRole !== "secret") { + return res.status(403).json({ error: "Access denied. Action requires a Secret Key." }); + } + + if (req.body?.confirm !== "DELETE_ALL_FILES") { + return res.status(400).json({ error: "Destructive action requires { confirm: 'DELETE_ALL_FILES' } in the JSON body." }); + } + const supabase = await getStorage(project); const bucket = getBucket(project); diff --git a/apps/public-api/src/controllers/userAuth.controller.js b/apps/public-api/src/controllers/userAuth.controller.js index b481fe8c5..8c20909d2 100644 --- a/apps/public-api/src/controllers/userAuth.controller.js +++ b/apps/public-api/src/controllers/userAuth.controller.js @@ -967,7 +967,7 @@ module.exports.signup = async (req, res) => { return res.status(cooldownErr.statusCode || 429).json({ error: cooldownErr.message }); } - const otp = Math.floor(100000 + Math.random() * 900000).toString(); + const otp = crypto.randomInt(100000, 1000000).toString(); await redis.set(`project:${project._id}:otp:verification:${email}`, otp, 'EX', 300); await setPublicOtpCooldown(project._id, email, 'verification'); @@ -992,7 +992,7 @@ module.exports.signup = async (req, res) => { const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); - const otp = Math.floor(100000 + Math.random() * 900000).toString(); + const otp = crypto.randomInt(100000, 1000000).toString(); const newUserPayload = buildAuthUserPayload( usersColConfig, @@ -1024,12 +1024,15 @@ module.exports.signup = async (req, res) => { }); res.status(201).json({ - message: "User registered successfully. Please verify your email.", - token: issuedTokens.accessToken, - accessToken: issuedTokens.accessToken, - expiresIn: issuedTokens.expiresIn, - ...(shouldExposeRefreshToken(req) ? { refreshToken: issuedTokens.refreshToken } : {}), - userId: result._id + success: true, + data: { + token: issuedTokens.accessToken, + accessToken: issuedTokens.accessToken, + expiresIn: issuedTokens.expiresIn, + ...(shouldExposeRefreshToken(req) ? { refreshToken: issuedTokens.refreshToken } : {}), + userId: result._id + }, + message: "User registered successfully. Please verify your email." }); } catch (err) { @@ -1037,7 +1040,7 @@ module.exports.signup = async (req, res) => { return res.status(400).json({ error: err.issues?.[0]?.message || err.errors?.[0]?.message || "Validation failed" }); } res.status(500).json({ error: err.message }); - console.log(err) + console.error(err) } } @@ -1073,10 +1076,14 @@ module.exports.login = async (req, res) => { }); res.json({ - token: issuedTokens.accessToken, - accessToken: issuedTokens.accessToken, - expiresIn: issuedTokens.expiresIn, - ...(shouldExposeRefreshToken(req) ? { refreshToken: issuedTokens.refreshToken } : {}) + success: true, + data: { + token: issuedTokens.accessToken, + accessToken: issuedTokens.accessToken, + expiresIn: issuedTokens.expiresIn, + ...(shouldExposeRefreshToken(req) ? { refreshToken: issuedTokens.refreshToken } : {}) + }, + message: "Login successful" }); } catch (err) { @@ -1258,7 +1265,7 @@ module.exports.verifyEmail = async (req, res) => { return res.status(500).json({ error: "No verification field found in users schema" }); } const result = await Model.updateOne( - { email }, + { email: normalizedEmail }, { $set: { [verificationField]: true } } ); @@ -1298,17 +1305,17 @@ module.exports.resendVerificationOtp = async (req, res) => { // Check cooldown try { - await checkPublicOtpCooldown(project._id, email, 'verification'); + await checkPublicOtpCooldown(project._id, normalizedEmail, 'verification'); } catch (cooldownErr) { return res.status(cooldownErr.statusCode || 429).json({ error: cooldownErr.message }); } - const otp = Math.floor(100000 + Math.random() * 900000).toString(); - await redis.set(`project:${project._id}:otp:verification:${email}`, otp, 'EX', 300); - await setPublicOtpCooldown(project._id, email, 'verification'); + const otp = crypto.randomInt(100000, 1000000).toString(); + await redis.set(`project:${project._id}:otp:verification:${normalizedEmail}`, otp, 'EX', 300); + await setPublicOtpCooldown(project._id, normalizedEmail, 'verification'); await authEmailQueue.add('send-verification-email', { - email, + email: normalizedEmail, otp, type: 'verification', pname: project.name, @@ -1341,7 +1348,7 @@ module.exports.requestPasswordReset = async (req, res) => { return res.json({ message: "If that email exists, a reset code has been sent." }); } - const otp = Math.floor(100000 + Math.random() * 900000).toString(); + const otp = crypto.randomInt(100000, 1000000).toString(); await redis.set(`project:${project._id}:otp:reset:${email}`, otp, 'EX', 300); await authEmailQueue.add('send-reset-email', { email, otp, type: 'password_reset', pname: project.name, projectId: String(project._id) }); diff --git a/apps/public-api/src/middlewares/api_usage.js b/apps/public-api/src/middlewares/api_usage.js index b4b574965..33bfb3bc6 100644 --- a/apps/public-api/src/middlewares/api_usage.js +++ b/apps/public-api/src/middlewares/api_usage.js @@ -9,8 +9,7 @@ const limiter = rateLimit({ message: { error: "Too many requests, please try again later." }, standardHeaders: true, legacyHeaders: false, - validate: { xForwardedForHeader: false }, - validate: { trustProxy: false } + validate: { xForwardedForHeader: false, trustProxy: false } }); // Logger diff --git a/apps/public-api/src/middlewares/verifyApiKey.js b/apps/public-api/src/middlewares/verifyApiKey.js index 1e97cb191..4c8eb704e 100644 --- a/apps/public-api/src/middlewares/verifyApiKey.js +++ b/apps/public-api/src/middlewares/verifyApiKey.js @@ -46,6 +46,7 @@ module.exports = async (req, res, next) => { allowedDomains isAuthEnabled siteUrl + authProviders `) .populate('owner', 'isVerified') .lean(); diff --git a/apps/public-api/src/utils/mailLimit.js b/apps/public-api/src/utils/mailLimit.js index c96e2f353..8da0cd1a1 100644 --- a/apps/public-api/src/utils/mailLimit.js +++ b/apps/public-api/src/utils/mailLimit.js @@ -15,7 +15,7 @@ const getEndOfMonthTtlSeconds = (now = new Date()) => { return Math.max(1, Math.ceil((nextMonthStart.getTime() - now.getTime()) / 1000)); }; -const getMonthlyMailLimit = () => { +const getMonthlyMailLimit = (project) => { // v0.9.0 default: free tier limit for all projects. return MONTHLY_FREE_MAIL_LIMIT; }; diff --git a/package-lock.json b/package-lock.json index 6184f34ec..add132010 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,6 @@ "tools/*", "sdks/*" ], - "dependencies": { - "path": "^0.12.7" - }, "devDependencies": { "concurrently": "^9.2.1" } @@ -1005,6 +1002,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1542,6 +1540,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -4501,6 +4500,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4610,6 +4610,7 @@ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", @@ -5275,6 +5276,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5685,6 +5687,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6316,7 +6319,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/csurf": { "version": "1.11.0", @@ -6790,6 +6794,7 @@ "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6860,6 +6865,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -11172,16 +11178,6 @@ "node": ">= 0.8" } }, - "node_modules/path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", - "license": "MIT", - "dependencies": { - "process": "^0.11.1", - "util": "^0.10.3" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11402,6 +11398,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11515,15 +11512,6 @@ "dev": true, "license": "MIT" }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -11671,6 +11659,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11680,6 +11669,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11743,6 +11733,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -11905,7 +11896,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -13182,6 +13174,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13420,15 +13413,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "license": "MIT", - "dependencies": { - "inherits": "2.0.3" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -13527,6 +13511,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13602,6 +13587,7 @@ "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", @@ -13909,6 +13895,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -14069,6 +14056,7 @@ "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", diff --git a/package.json b/package.json index eee8f10d5..6ab126d2c 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,6 @@ "devDependencies": { "concurrently": "^9.2.1" }, - "dependencies": { - "path": "^0.12.7" - }, "overrides": { "rollup": "npm:@rollup/wasm-node" } diff --git a/packages/common/src/config/redis.js b/packages/common/src/config/redis.js index a4482666e..50182aa3a 100644 --- a/packages/common/src/config/redis.js +++ b/packages/common/src/config/redis.js @@ -3,9 +3,6 @@ const dotenv = require("dotenv"); dotenv.config() if (!process.env.REDIS_URL) { - if (process.env.NODE_ENV !== 'production') { - console.log("DEBUG: ENV KEYS:", Object.keys(process.env)); - } throw new Error("REDIS_URL is not defined in .env"); } diff --git a/packages/common/src/queues/webhookQueue.js b/packages/common/src/queues/webhookQueue.js index b7663c2c2..5921dcef2 100644 --- a/packages/common/src/queues/webhookQueue.js +++ b/packages/common/src/queues/webhookQueue.js @@ -78,8 +78,12 @@ async function enqueueWebhookDelivery({ * Initialize the webhook worker * Call this once during app startup */ +let worker = null; + function initWebhookWorker() { - const worker = new Worker( + if (worker) return worker; + + worker = new Worker( "webhook-delivery-queue", async (job) => { const { deliveryId, webhookId, attemptNumber } = job.data; diff --git a/packages/common/src/utils/GC.js b/packages/common/src/utils/GC.js index 12d595b54..1df646d92 100644 --- a/packages/common/src/utils/GC.js +++ b/packages/common/src/utils/GC.js @@ -1,5 +1,6 @@ const { registry, storageRegistry } = require("./registry"); function garbageCollect() { + console.log("Garbage Collecting"); setInterval(() => { console.log("20 minutes passed"); const now = new Date(); @@ -27,7 +28,6 @@ function storageGarbageCollect() { } }, 24 * 60 * 60 * 1000); } -console.log("Garbage Collecting"); module.exports = { garbageCollect, storageGarbageCollect }; diff --git a/packages/common/src/utils/encryption.js b/packages/common/src/utils/encryption.js index df37002f3..bb8fbf7ec 100644 --- a/packages/common/src/utils/encryption.js +++ b/packages/common/src/utils/encryption.js @@ -3,9 +3,8 @@ const crypto = require("crypto"); const algorithm = "aes-256-gcm"; const ivLength = 16; -const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; - function encrypt(plainText) { + const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; if (!ENCRYPTION_KEY) throw new Error("ENCRYPTION_KEY missing in .env"); const iv = crypto.randomBytes(ivLength); @@ -25,6 +24,7 @@ function encrypt(plainText) { } function decrypt(encryptedData) { + const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; try { const iv = Buffer.from(encryptedData.iv, "hex"); const tag = Buffer.from(encryptedData.tag, "hex"); diff --git a/packages/common/src/utils/input.validation.js b/packages/common/src/utils/input.validation.js index 20092ed12..b36560c3a 100644 --- a/packages/common/src/utils/input.validation.js +++ b/packages/common/src/utils/input.validation.js @@ -285,13 +285,19 @@ module.exports.aggregateSchema = z.object({ }); module.exports.sanitize = (obj) => { - const clean = {}; - for (const key in obj) { - if (!key.startsWith("$")) { - clean[key] = obj[key]; + if (Array.isArray(obj)) { + return obj.map((item) => module.exports.sanitize(item)); + } + if (obj !== null && typeof obj === "object") { + const clean = {}; + for (const key in obj) { + if (!key.startsWith("$")) { + clean[key] = module.exports.sanitize(obj[key]); + } } + return clean; } - return clean; + return obj; }; const emptyToUndefined = z.preprocess(