fix: audit issues #10 #14 #16 #19 — CORS errors, JSON middleware, Zoho fields, noValidate (batch 0.6.8)
This commit is contained in:
parent
80969e5aee
commit
e11aefd184
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "queuenorth-website",
|
"name": "queuenorth-website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.5.7",
|
"version": "0.6.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"vite\" \"node server/index.js\"",
|
"dev": "concurrently \"vite\" \"node server/index.js\"",
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,14 @@ const log = {
|
||||||
|
|
||||||
// --- Rate Limiting ---
|
// --- Rate Limiting ---
|
||||||
const rateLimitWindowMs = 60 * 1000 // 1 minute
|
const rateLimitWindowMs = 60 * 1000 // 1 minute
|
||||||
const rateLimitMax = parseInt(process.env.RATE_LIMIT_PER_MINUTE || '5', 10)
|
const rateLimitMax = (() => {
|
||||||
|
const val = parseInt(process.env.RATE_LIMIT_PER_MINUTE || '5', 10)
|
||||||
|
if (isNaN(val) || val < 1) {
|
||||||
|
log.warn('[RateLimit] Invalid RATE_LIMIT_PER_MINUTE, defaulting to 5')
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
})()
|
||||||
|
|
||||||
const apiLimiter = rateLimit({
|
const apiLimiter = rateLimit({
|
||||||
windowMs: rateLimitWindowMs,
|
windowMs: rateLimitWindowMs,
|
||||||
|
|
@ -103,8 +110,7 @@ const corsConfig = cors({
|
||||||
app.use(corsConfig)
|
app.use(corsConfig)
|
||||||
log.info(`[CORS] Enabled with origin: ${corsOrigin}`)
|
log.info(`[CORS] Enabled with origin: ${corsOrigin}`)
|
||||||
|
|
||||||
// Middleware
|
// Middleware — JSON body parsing only on POST routes (issue #14)
|
||||||
app.use(express.json({ limit: '1mb' }))
|
|
||||||
app.use(express.urlencoded({ extended: true, limit: '1mb' }))
|
app.use(express.urlencoded({ extended: true, limit: '1mb' }))
|
||||||
|
|
||||||
// Rate limiting for API routes only
|
// Rate limiting for API routes only
|
||||||
|
|
@ -366,7 +372,7 @@ app.get('/api/health', (req, res) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Submit lead
|
// Submit lead
|
||||||
app.post('/api/leads', (req, res) => {
|
app.post('/api/leads', express.json({ limit: '1mb' }), (req, res) => {
|
||||||
try {
|
try {
|
||||||
const parsed = leadSchema.safeParse(req.body)
|
const parsed = leadSchema.safeParse(req.body)
|
||||||
|
|
||||||
|
|
@ -440,7 +446,7 @@ app.post('/api/leads', (req, res) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Submit support request
|
// Submit support request
|
||||||
app.post('/api/support', (req, res) => {
|
app.post('/api/support', express.json({ limit: '1mb' }), (req, res) => {
|
||||||
try {
|
try {
|
||||||
const parsed = supportSchema.safeParse(req.body)
|
const parsed = supportSchema.safeParse(req.body)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,15 @@ const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api'
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
get: async (endpoint) => {
|
get: async (endpoint) => {
|
||||||
const response = await fetch(`${API_BASE_URL}${endpoint}`)
|
let response
|
||||||
|
try {
|
||||||
|
response = await fetch(`${API_BASE_URL}${endpoint}`)
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof TypeError && err.message === 'Failed to fetch') {
|
||||||
|
throw new Error('Unable to reach the server. This may be a network or CORS issue.')
|
||||||
|
}
|
||||||
|
throw new Error(`Network error: ${err.message}`)
|
||||||
|
}
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`API error: ${response.statusText}`)
|
throw new Error(`API error: ${response.statusText}`)
|
||||||
}
|
}
|
||||||
|
|
@ -12,19 +20,32 @@ export const api = {
|
||||||
},
|
},
|
||||||
|
|
||||||
post: async (endpoint, data) => {
|
post: async (endpoint, data) => {
|
||||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
let response
|
||||||
method: 'POST',
|
try {
|
||||||
headers: {
|
response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||||
'Content-Type': 'application/json',
|
method: 'POST',
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify(data),
|
'Content-Type': 'application/json',
|
||||||
})
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof TypeError && err.message === 'Failed to fetch') {
|
||||||
|
throw new Error('Unable to reach the server. This may be a network or CORS issue.')
|
||||||
|
}
|
||||||
|
throw new Error(`Network error: ${err.message}`)
|
||||||
|
}
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json()
|
let errorData
|
||||||
|
try {
|
||||||
|
errorData = await response.json()
|
||||||
|
} catch {
|
||||||
|
throw new Error(`Server error (${response.status}): ${response.statusText}`)
|
||||||
|
}
|
||||||
throw new Error(errorData.error || `API error: ${response.statusText}`)
|
throw new Error(errorData.error || `API error: ${response.statusText}`)
|
||||||
}
|
}
|
||||||
return response.json()
|
return response.json()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export { queryClient }
|
export { queryClient }
|
||||||
|
|
@ -172,7 +172,7 @@ const Contact = () => {
|
||||||
|
|
||||||
{/* Right - Form */}
|
{/* Right - Form */}
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={handleSubmit} className={`space-y-6 ${mutation.isPending ? 'opacity-70 pointer-events-none' : ''}`}>
|
<form onSubmit={handleSubmit} noValidate className={`space-y-6 ${mutation.isPending ? 'opacity-70 pointer-events-none' : ''}`}>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="company" className="block text-sm font-medium text-text mb-2">
|
<label htmlFor="company" className="block text-sm font-medium text-text mb-2">
|
||||||
Company Name <span className="text-red-600">*</span>
|
Company Name <span className="text-red-600">*</span>
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ const Support = () => {
|
||||||
|
|
||||||
{/* Right - Form */}
|
{/* Right - Form */}
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={handleSubmit} className={`space-y-6 ${mutation.isPending ? 'opacity-70 pointer-events-none' : ''}`}>
|
<form onSubmit={handleSubmit} noValidate className={`space-y-6 ${mutation.isPending ? 'opacity-70 pointer-events-none' : ''}`}>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="name" className="block text-sm font-medium text-text mb-2">
|
<label htmlFor="name" className="block text-sm font-medium text-text mb-2">
|
||||||
Name <span className="text-red-600">*</span>
|
Name <span className="text-red-600">*</span>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue