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,
|
||||
|
||||
Reference in New Issue
Block a user