Spaces:
Running
Running
Commit ·
0d8870e
1
Parent(s): ddd7f1c
Fix CORS and improved error handling for HF deployment
Browse files
backend/src/server.js
CHANGED
|
@@ -23,13 +23,29 @@ const allowedOrigins = [
|
|
| 23 |
|
| 24 |
app.use(cors({
|
| 25 |
origin: (origin, cb) => {
|
| 26 |
-
// Allow requests with no origin (
|
| 27 |
-
if (!origin
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
},
|
| 30 |
credentials: true,
|
| 31 |
-
methods: ['GET','POST','PATCH','PUT','DELETE','OPTIONS'],
|
| 32 |
-
allowedHeaders: ['Content-Type','Authorization'],
|
| 33 |
}));
|
| 34 |
|
| 35 |
const generalLimiter = rateLimit({ windowMs: 15*60*1000, max: 500, standardHeaders: true, legacyHeaders: false });
|
|
@@ -49,14 +65,25 @@ app.use('/api/tasks/:id/comments', commentRoutes);
|
|
| 49 |
app.use('/api/admin', adminRoutes);
|
| 50 |
|
| 51 |
app.use((req, res) => res.status(404).json({ error: 'Endpoint not found.' }));
|
|
|
|
|
|
|
| 52 |
app.use((err, req, res, next) => {
|
| 53 |
-
console.error('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
res.status(500).json({ error: 'An unexpected error occurred.' });
|
| 55 |
});
|
| 56 |
|
| 57 |
const PORT = process.env.PORT || 5000;
|
| 58 |
app.listen(PORT, '127.0.0.1', () =>
|
| 59 |
-
console.log(`Taskflow API on port ${PORT} [${process.env.NODE_ENV || '
|
| 60 |
);
|
| 61 |
|
| 62 |
module.exports = app;
|
|
|
|
| 23 |
|
| 24 |
app.use(cors({
|
| 25 |
origin: (origin, cb) => {
|
| 26 |
+
// Allow requests with no origin (like mobile apps or curl)
|
| 27 |
+
if (!origin) return cb(null, true);
|
| 28 |
+
|
| 29 |
+
// Allow Hugging Face domains or locally allowed origins
|
| 30 |
+
if (origin.endsWith('.hf.space') || origin.includes('huggingface.co') || allowedOrigins.includes(origin)) {
|
| 31 |
+
return cb(null, true);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
// In production, be a bit more flexible if we're on HF
|
| 35 |
+
if (process.env.NODE_ENV === 'production' && origin.includes('hf.space')) {
|
| 36 |
+
return cb(null, true);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Allow all in development for easier debugging
|
| 40 |
+
if (process.env.NODE_ENV !== 'production') {
|
| 41 |
+
return cb(null, true);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
cb(new Error(`CORS blocked for origin: ${origin}`));
|
| 45 |
},
|
| 46 |
credentials: true,
|
| 47 |
+
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'],
|
| 48 |
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
| 49 |
}));
|
| 50 |
|
| 51 |
const generalLimiter = rateLimit({ windowMs: 15*60*1000, max: 500, standardHeaders: true, legacyHeaders: false });
|
|
|
|
| 65 |
app.use('/api/admin', adminRoutes);
|
| 66 |
|
| 67 |
app.use((req, res) => res.status(404).json({ error: 'Endpoint not found.' }));
|
| 68 |
+
|
| 69 |
+
// Global error handler
|
| 70 |
app.use((err, req, res, next) => {
|
| 71 |
+
console.error('[Global Error Manager]:', err.message || err);
|
| 72 |
+
|
| 73 |
+
// Specific handling for CORS errors to make them more helpful
|
| 74 |
+
if (err.message && err.message.includes('CORS')) {
|
| 75 |
+
return res.status(403).json({
|
| 76 |
+
error: 'Security block: origin not allowed.',
|
| 77 |
+
details: process.env.NODE_ENV === 'production' ? undefined : err.message
|
| 78 |
+
});
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
res.status(500).json({ error: 'An unexpected error occurred.' });
|
| 82 |
});
|
| 83 |
|
| 84 |
const PORT = process.env.PORT || 5000;
|
| 85 |
app.listen(PORT, '127.0.0.1', () =>
|
| 86 |
+
console.log(`Taskflow API on port ${PORT} [${process.env.NODE_ENV || 'production'}]`)
|
| 87 |
);
|
| 88 |
|
| 89 |
module.exports = app;
|
frontend/src/pages/AcceptInvitePage.jsx
CHANGED
|
@@ -61,7 +61,7 @@ export default function AcceptInvitePage() {
|
|
| 61 |
type={showPw?'text':'password'} placeholder="Min 8 characters"
|
| 62 |
value={form.password} onChange={e=>setForm(f=>({...f,password:e.target.value}))}/>
|
| 63 |
<button type="button" className={styles.pwToggle} onClick={()=>setShowPw(v=>!v)}>
|
| 64 |
-
{showPw?<
|
| 65 |
</button>
|
| 66 |
</div>
|
| 67 |
{errors.password && <span className="field-error">{errors.password}</span>}
|
|
|
|
| 61 |
type={showPw?'text':'password'} placeholder="Min 8 characters"
|
| 62 |
value={form.password} onChange={e=>setForm(f=>({...f,password:e.target.value}))}/>
|
| 63 |
<button type="button" className={styles.pwToggle} onClick={()=>setShowPw(v=>!v)}>
|
| 64 |
+
{showPw?<EyeOff size={15}/>:<Eye size={15}/>}
|
| 65 |
</button>
|
| 66 |
</div>
|
| 67 |
{errors.password && <span className="field-error">{errors.password}</span>}
|