Spaces:
Sleeping
Sleeping
File size: 3,373 Bytes
f6c14df acc0caa f6c14df | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const authRoutes = require('./routes/auth');
const taskRoutes = require('./routes/tasks');
const adminRoutes = require('./routes/admin');
const commentRoutes = require('./routes/comments');
const app = express();
// On HuggingFace, nginx proxies /api/* to this process on 127.0.0.1:5000
// so requests arrive without an Origin header — CORS is only needed for local dev
// HuggingFace Spaces embeds the app in an iframe — must disable frameguard
// so X-Frame-Options doesn't block the embed
app.use(helmet({
contentSecurityPolicy: false,
frameguard: false,
crossOriginEmbedderPolicy: false,
crossOriginOpenerPolicy: false,
crossOriginResourcePolicy: false,
}));
const allowedOrigins = [
process.env.FRONTEND_URL || 'http://localhost:3000',
'http://localhost:3000',
'http://127.0.0.1:3000',
];
app.use(cors({
origin: (origin, cb) => {
// Allow requests with no origin (like mobile apps or curl)
if (!origin) return cb(null, true);
// Allow Hugging Face domains or locally allowed origins
if (origin.endsWith('.hf.space') || origin.includes('huggingface.co') || allowedOrigins.includes(origin)) {
return cb(null, true);
}
// In production, be a bit more flexible if we're on HF
if (process.env.NODE_ENV === 'production' && origin.includes('hf.space')) {
return cb(null, true);
}
// Allow all in development for easier debugging
if (process.env.NODE_ENV !== 'production') {
return cb(null, true);
}
cb(new Error(`CORS blocked for origin: ${origin}`));
},
credentials: true,
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
}));
const generalLimiter = rateLimit({ windowMs: 15*60*1000, max: 500, standardHeaders: true, legacyHeaders: false });
const authLimiter = rateLimit({ windowMs: 15*60*1000, max: 20, message: { error: 'Too many attempts. Try again later.' } });
app.use(generalLimiter);
app.use(express.json({ limit: '2mb' }));
app.use(express.urlencoded({ extended: true }));
app.get('/health', (req, res) =>
res.json({ status: 'ok', service: 'taskflow-api', ts: new Date().toISOString() })
);
app.use('/api/auth', authLimiter, authRoutes);
app.use('/api/tasks', taskRoutes);
app.use('/api/tasks/:id/comments', commentRoutes);
app.use('/api/admin', adminRoutes);
app.use((req, res) => res.status(404).json({ error: 'Endpoint not found.' }));
// Global error handler
app.use((err, req, res, next) => {
console.error('[Global Error Manager]:', err.message || err);
// Specific handling for CORS errors to make them more helpful
if (err.message && err.message.includes('CORS')) {
return res.status(403).json({
error: 'Security block: origin not allowed.',
details: process.env.NODE_ENV === 'production' ? undefined : err.message
});
}
res.status(500).json({ error: 'An unexpected error occurred.' });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, '127.0.0.1', () =>
console.log(`Taskflow API on port ${PORT} [${process.env.NODE_ENV || 'production'}]`)
);
module.exports = app;
|