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,