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(/]*>/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); }