parox / src /server.js
glutamatt's picture
glutamatt HF Staff
rate limiting password
a1011f7 verified
const express = require('express');
const bodyParser = require('body-parser');
const rateLimit = require('express-rate-limit');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000; // User preferred default
const DATA_FILE = path.join(__dirname, '../data/ressentis.json');
const APP_PASSWORD = process.env.APP_PASSWORD || 'parox'; // Default password
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'public')));
// Middleware to check authentication
const checkAuth = (req, res, next) => {
// For simplicity, we'll check a custom header 'x-auth-token' or query param
const token = req.headers['x-auth-token'];
if (token === APP_PASSWORD) {
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
};
// GCS Setup
const { Storage } = require('@google-cloud/storage');
const GCS_BUCKET_NAME = process.env.GCS_BUCKET_NAME;
// HF Spaces / General Secret support: Write key from ENV to file if available
if (process.env.GCS_KEY_JSON && !fs.existsSync('/app/gcs-key.json')) {
try {
console.log("> Writing GCS Key from ENV (GCS_KEY_JSON) to file...");
fs.writeFileSync('/app/gcs-key.json', process.env.GCS_KEY_JSON);
process.env.GOOGLE_APPLICATION_CREDENTIALS = '/app/gcs-key.json';
} catch (e) {
console.error("! Failed to write GCS Key from ENV:", e);
}
}
let gcsBucket = null;
if (GCS_BUCKET_NAME) {
try {
const storage = new Storage();
gcsBucket = storage.bucket(GCS_BUCKET_NAME);
console.log(`> GCS Enabled: Using bucket '${GCS_BUCKET_NAME}'`);
} catch (e) {
console.error("! GCS config found but failed to init:", e);
}
} else {
console.log("> GCS Disabled: Using local filesystem");
// Ensure data file exists locally
if (!fs.existsSync(DATA_FILE)) {
try {
fs.mkdirSync(path.dirname(DATA_FILE), { recursive: true });
fs.writeFileSync(DATA_FILE, JSON.stringify([], null, 2));
} catch (e) { console.error("FS Init Error:", e); }
}
}
// Data Handling Functions
async function readData() {
if (gcsBucket) {
try {
const [contents] = await gcsBucket.file('ressentis.json').download();
return JSON.parse(contents.toString());
} catch (e) {
if (e.code === 404) return []; // Auto-create on first write
throw e;
}
} else {
if (fs.existsSync(DATA_FILE)) {
return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
}
return [];
}
}
async function writeData(data) {
const jsonStr = JSON.stringify(data, null, 2);
if (gcsBucket) {
await gcsBucket.file('ressentis.json').save(jsonStr, {
contentType: 'application/json',
resumable: false
});
} else {
fs.writeFileSync(DATA_FILE, jsonStr);
}
}
// API Routes
// Login check
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 requests per windowMs
messsage: { error: 'Too many login attempts, please try again after 15 minutes' },
standardHeaders: true,
legacyHeaders: false,
});
app.post('/api/login', loginLimiter, (req, res) => {
const { password } = req.body;
if (password === APP_PASSWORD) {
res.json({ success: true, token: password });
} else {
res.status(401).json({ success: false, error: 'Invalid Password' });
}
});
// Get Data
app.get('/api/data', checkAuth, async (req, res) => {
try {
const data = await readData();
res.json(data);
} catch (e) {
console.error("Read Error:", e);
res.status(500).json({ error: 'Failed to read data' });
}
});
// Save Data
app.post('/api/data', checkAuth, async (req, res) => {
const newData = req.body;
try {
await writeData(newData);
res.json({ success: true });
} catch (e) {
console.error("Save Error:", e);
res.status(500).json({ error: 'Failed to save data' });
}
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});