c3c487a8fb
Rewrite absolute tkmind asset download links to /api/... at delivery time and strip base tags that violate public page CSP, so images load on any host. Co-authored-by: Cursor <cursoragent@cursor.com>
57 lines
2.1 KiB
JavaScript
57 lines
2.1 KiB
JavaScript
import crypto from 'node:crypto';
|
|
|
|
function normalizeSecret(secret) {
|
|
return String(secret ?? '');
|
|
}
|
|
|
|
export function publicAssetToken(assetId, secret) {
|
|
const normalizedAssetId = String(assetId ?? '').trim();
|
|
if (!normalizedAssetId) return '';
|
|
return crypto
|
|
.createHmac('sha256', normalizeSecret(secret))
|
|
.update(`mindspace-public-asset:v1:${normalizedAssetId}`)
|
|
.digest('base64url');
|
|
}
|
|
|
|
export function verifyPublicAssetToken(assetId, token, secret) {
|
|
const value = Array.isArray(token) ? token[0] : token;
|
|
const provided = String(value ?? '').trim();
|
|
if (!provided) return false;
|
|
const expected = publicAssetToken(assetId, secret);
|
|
const providedBuffer = Buffer.from(provided);
|
|
const expectedBuffer = Buffer.from(expected);
|
|
return (
|
|
providedBuffer.length === expectedBuffer.length &&
|
|
crypto.timingSafeEqual(providedBuffer, expectedBuffer)
|
|
);
|
|
}
|
|
|
|
const PUBLIC_ASSET_DOWNLOAD_PATTERN =
|
|
/((?:https?:\/\/[^"'<>\\\s]+)?\/api\/mindspace\/v1\/assets\/([a-z0-9-]+)\/download)(\?[^"'<>\\\s)]*)?/gi;
|
|
|
|
export function stripPublicHtmlBaseTag(html) {
|
|
return String(html ?? '').replace(/<base\b[^>]*>/gi, '');
|
|
}
|
|
|
|
export function normalizePublicAssetReferences(html) {
|
|
const source = String(html ?? '');
|
|
if (!source.includes('/api/mindspace/v1/assets/')) return source;
|
|
return source.replace(PUBLIC_ASSET_DOWNLOAD_PATTERN, (_value, _base, assetId, query = '') => {
|
|
return `/api/mindspace/v1/assets/${assetId}/download${query ?? ''}`;
|
|
});
|
|
}
|
|
|
|
export function appendPublicAssetTokens(html, secret) {
|
|
const source = normalizePublicAssetReferences(html);
|
|
if (!source || !source.includes('/api/mindspace/v1/assets/')) return source;
|
|
return source.replace(PUBLIC_ASSET_DOWNLOAD_PATTERN, (value, _base, assetId, query = '') => {
|
|
if (/(?:\?|&|&)public_token=/.test(query)) return value;
|
|
const separator = query ? (query.includes('&') ? '&' : '&') : '?';
|
|
return `/api/mindspace/v1/assets/${assetId}/download${query}${separator}public_token=${publicAssetToken(assetId, secret)}`;
|
|
});
|
|
}
|
|
|
|
export function preparePublicHtmlAssetDelivery(html, secret) {
|
|
return appendPublicAssetTokens(stripPublicHtmlBaseTag(html), secret);
|
|
}
|