fix: Cross-User Write, strict CORS, rate limiting
All checks were successful
Deploy Brew Application / deploy (push) Successful in 11s
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:
@@ -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,
|
||||
|
||||
28
server/package-lock.json
generated
28
server/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user