fix: prewiev Bot expires links
This commit is contained in:
parent
ca52b2115b
commit
cbf41d5022
2 changed files with 41 additions and 1 deletions
|
|
@ -4,7 +4,7 @@ import { pipeline } from 'node:stream/promises';
|
|||
import express from 'express';
|
||||
import { queryOne, execute } from '../db.js';
|
||||
import { rawPath, gzPath } from '../storage.js';
|
||||
import { gonePage } from '../views.js';
|
||||
import { gonePage, downloadLandingPage } from '../views.js';
|
||||
import { ah } from '../util.js';
|
||||
import log from '../log.js';
|
||||
|
||||
|
|
@ -15,8 +15,35 @@ function contentDisposition(name) {
|
|||
return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(name)}`;
|
||||
}
|
||||
|
||||
// GET shows a landing page and does NOT consume the link. This is deliberate: chat
|
||||
// apps and email scanners (WhatsApp, Slack, iMessage, ...) fetch the URL to build a
|
||||
// preview, and if GET burned the token the link would be "used" before the human
|
||||
// clicks. The one-time consumption happens on the POST below.
|
||||
router.get('/d/:token', ah(async (req, res) => {
|
||||
const token = req.params.token || '';
|
||||
const row = await queryOne(
|
||||
`SELECT f.original_name, f.size_bytes, f.is_bundle, dl.used, dl.expires_at > NOW() AS valid
|
||||
FROM download_links dl JOIN files f ON f.id = dl.file_id
|
||||
WHERE dl.token = ?`,
|
||||
[token]
|
||||
);
|
||||
if (!row || row.used || !row.valid) {
|
||||
return res.status(410).send(gonePage());
|
||||
}
|
||||
res.send(
|
||||
downloadLandingPage({
|
||||
token,
|
||||
name: row.original_name,
|
||||
size: row.size_bytes,
|
||||
isBundle: row.is_bundle,
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
// POST performs the actual one-time download (triggered by the button on the landing
|
||||
// page). Preview bots issue GET, never POST, so the link survives until a real click.
|
||||
router.post('/d/:token', ah(async (req, res) => {
|
||||
const token = req.params.token || '';
|
||||
|
||||
// Atomically reserve the one-time link: succeeds only if unused AND unexpired.
|
||||
const result = await execute(
|
||||
|
|
|
|||
13
src/views.js
13
src/views.js
|
|
@ -203,6 +203,19 @@ export function dashboardPage({ user, files, disk, flash, newLink }) {
|
|||
return layout({ title: 'My files', body, user, flash, active: 'dash' });
|
||||
}
|
||||
|
||||
export function downloadLandingPage({ token, name, size, isBundle }) {
|
||||
const body = `<section class="card narrow">
|
||||
<h1>You've received a file</h1>
|
||||
<p class="fname">${esc(name)}</p>
|
||||
<p class="sub">${humanBytes(size)}${isBundle ? ' · zip bundle' : ''}</p>
|
||||
<form method="post" action="${u(`/d/${token}`)}">
|
||||
<button class="btn" type="submit">Download</button>
|
||||
</form>
|
||||
<p class="muted" style="margin-top:14px">This is a one-time link — it works once, then expires.</p>
|
||||
</section>`;
|
||||
return layout({ title: 'Download', body, user: null });
|
||||
}
|
||||
|
||||
export function gonePage() {
|
||||
return messagePage({
|
||||
title: 'Link unavailable',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue