fix: Cross-User Write, strict CORS, rate limiting
All checks were successful
Deploy Brew Application / deploy (push) Successful in 11s

- IDOR in sync api
- if server was run in prod without jwt secret var then it fell back to inscure string; added startup check
- restrict query requests to vite origin
- use `express-rate-limit`. 100 requests per 15-minute window for a client
This commit is contained in:
2026-06-06 22:14:53 +05:30
parent de9cbb14d0
commit a27bd118e5
3 changed files with 53 additions and 5 deletions

View File

@@ -12,12 +12,29 @@ const jwt = require('jsonwebtoken');
const pool = require('./db');
require('dotenv').config();
if (process.env.NODE_ENV === 'production' && (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'your_jwt_secret_here')) {
console.error("FATAL: JWT_SECRET environment variable is not securely set in production!");
process.exit(1);
}
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 5000;
app.use(cors());
const allowedOrigin = process.env.ALLOWED_ORIGIN || 'http://localhost:5173';
app.use(cors({
origin: allowedOrigin
}));
app.use(express.json());
// Rate limiter for authentication routes
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per window
message: { error: 'Too many authentication attempts. Please try again later.' }
});
// Request logger middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
@@ -44,7 +61,7 @@ const authenticateToken = (req, res, next) => {
};
// Registration
app.post('/api/register', async (req, res) => {
app.post('/api/register', authLimiter, async (req, res) => {
try {
const { username, email, password } = req.body;
@@ -76,7 +93,7 @@ app.post('/api/register', async (req, res) => {
});
// Login
app.post('/api/login', async (req, res) => {
app.post('/api/login', authLimiter, async (req, res) => {
try {
const { email, password } = req.body;
@@ -156,7 +173,8 @@ app.post('/api/sync', authenticateToken, async (req, res) => {
image = EXCLUDED.image,
updated_at = EXCLUDED.updated_at,
is_deleted = EXCLUDED.is_deleted
WHERE EXCLUDED.updated_at > beans.updated_at OR beans.user_id IS NULL
WHERE (EXCLUDED.updated_at > beans.updated_at OR beans.user_id IS NULL)
AND (beans.user_id = EXCLUDED.user_id OR beans.user_id IS NULL)
`, [
bean.id,
userId,
@@ -189,7 +207,8 @@ app.post('/api/sync', authenticateToken, async (req, res) => {
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at,
is_deleted = EXCLUDED.is_deleted
WHERE EXCLUDED.updated_at > brew_logs.updated_at OR brew_logs.user_id IS NULL
WHERE (EXCLUDED.updated_at > brew_logs.updated_at OR brew_logs.user_id IS NULL)
AND (brew_logs.user_id = EXCLUDED.user_id OR brew_logs.user_id IS NULL)
`, [
log.id,
userId,

View File

@@ -13,6 +13,7 @@
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"express-rate-limit": "^8.5.2",
"jsonwebtoken": "^9.0.3",
"pg": "^8.21.0"
}
@@ -333,6 +334,24 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
"version": "8.5.2",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
"integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
"license": "MIT",
"dependencies": {
"ip-address": "^10.2.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/finalhandler": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
@@ -496,6 +515,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ip-address": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",

View File

@@ -16,6 +16,7 @@
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"express-rate-limit": "^8.5.2",
"jsonwebtoken": "^9.0.3",
"pg": "^8.21.0"
}