Add auto-refresh on the customer page and harden the new poll endpoints
The upload page now polls /info and /files every 20s while visible: new uploads (also via WebDAV), expiry changes and link deactivation appear without a manual reload. A pollBusy flag prevents overlapping fetches on slow connections, and visibilitychange stops the timer in backgrounded tabs. /info and /files get Cache-Control: no-store so the browser cannot serve stale state, plus a 60/min/IP customerPollLimiter to cap abuse from leaked tokens. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-2
@@ -64,6 +64,15 @@ const customerAuthLimiter = rateLimit({
|
||||
legacyHeaders: false,
|
||||
message: { error: 'too many attempts, try again later' },
|
||||
});
|
||||
// Polling endpoints — generous but not unlimited, mainly to cap abuse from
|
||||
// leaked tokens spamming the cheap read endpoints.
|
||||
const customerPollLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 60,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: 'rate limited' },
|
||||
});
|
||||
|
||||
// ---------- Helpers ----------
|
||||
function slugify(name) {
|
||||
@@ -551,9 +560,10 @@ app.post('/u/:token/auth', customerAuthLimiter, async (req, res) => {
|
||||
res.json({ ok });
|
||||
});
|
||||
|
||||
app.get('/u/:token/info', (req, res) => {
|
||||
app.get('/u/:token/info', customerPollLimiter, (req, res) => {
|
||||
const c = getCustomerByToken(req.params.token);
|
||||
if (!c || isArchived(c)) return res.status(404).json({ error: 'invalid' });
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
res.json({
|
||||
name: c.name,
|
||||
has_password: !!c.password_hash,
|
||||
@@ -676,7 +686,7 @@ function requireNotExpired(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
app.get('/u/:token/files', uploadAuth, requireNotExpired, (req, res) => {
|
||||
app.get('/u/:token/files', customerPollLimiter, uploadAuth, requireNotExpired, (req, res) => {
|
||||
const c = req._customer;
|
||||
const base = customerDir(c.slug);
|
||||
const sub = sanitizeRelPath(req.query.dir || '');
|
||||
@@ -684,6 +694,7 @@ app.get('/u/:token/files', uploadAuth, requireNotExpired, (req, res) => {
|
||||
try { entries = listCustomerDir(base, sub); }
|
||||
catch { return res.status(400).json({ error: 'invalid path' }); }
|
||||
if (entries === null) return res.status(404).json({ error: 'not found' });
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
res.json({ dir: sub, entries });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user