134 lines
4.7 KiB
Plaintext
134 lines
4.7 KiB
Plaintext
/// <reference path="../pb_data/types.d.ts" />
|
|
|
|
/**
|
|
* onFileDownloadRequest hook is triggered before each API File download request. Could be used to validate or modify the file response before returning it to the client.
|
|
* @see https://pocketbase.io/docs/js-event-hooks/#onfiledownloadrequest
|
|
*
|
|
* We use this to return a 302 to the CDN asset instead of having the asset proxied via Pocketbase
|
|
* @see https://github.com/pocketbase/pocketbase/discussions/5995
|
|
*
|
|
*/
|
|
onFileDownloadRequest((event) => {
|
|
|
|
|
|
console.log('>>> We got a file download request event', JSON.stringify(event))
|
|
|
|
|
|
console.log('the below the following is event.request');
|
|
console.log(JSON.stringify(event.request));
|
|
console.log('the above the previous line is event.request');
|
|
// e.app
|
|
// e.collection
|
|
// e.record
|
|
// e.fileField
|
|
// e.servedPath
|
|
// e.servedName
|
|
// and all RequestEvent fields...
|
|
const securityKey = process.env?.BUNNY_TOKEN_KEY;
|
|
const baseUrl = process.env?.BUNNY_ZONE_URL;
|
|
|
|
console.log(`securityKey=${securityKey}, baseUrl=${baseUrl}`)
|
|
|
|
if (!securityKey) {
|
|
console.error('BUNNY_TOKEN_KEY was missing from env');
|
|
return event.next();
|
|
}
|
|
if (!baseUrl) {
|
|
console.error('BUNNY_ZONE_URL was missing from env');
|
|
return event.next();
|
|
}
|
|
|
|
/**
|
|
* Generate a signed BunnyCDN URL.
|
|
*
|
|
* @see https://support.bunny.net/hc/en-us/articles/360016055099-How-to-sign-URLs-for-BunnyCDN-Token-Authentication
|
|
*
|
|
* @param {string} securityKey - Your BunnyCDN security token
|
|
* @param {string} inputUrl - The base URL (protocol + host + path + (optional) qs)
|
|
* @param {number} expires - Unix timestamp for expiration
|
|
* @param {string} tokenPath - (optional) create a token that has access to any file within that path
|
|
*/
|
|
function signUrlCool(securityKey, inputUrl, expires, tokenPath = '') {
|
|
|
|
if (inputUrl.endsWith('/')) throw new Error(`url must not end with a slash. got inputUrl=${inputUrl}`);
|
|
|
|
|
|
// reading query parameters
|
|
// let search = e.request.url.query().get("search")
|
|
const URL = event.request.url
|
|
|
|
|
|
|
|
let parsedURL = URL.parse(inputUrl); // this is pre-parsed. no need to parse again
|
|
let params = (URL.parse(parsedURL)).searchParams; // these are pre-parsed as event.request.url.rawQuery. no need to parse again
|
|
let signaturePath = '';
|
|
let parameterData = '';
|
|
let parameterDataUrl = '';
|
|
|
|
console.log(`eyy we got parsedURL=${parsedURL}`);
|
|
|
|
throw new Error('@todo');
|
|
|
|
// Build parameter string (sort keys alphabetically)
|
|
// let parameterData = "";
|
|
// if (rawQuery) {
|
|
// const params = rawQuery
|
|
// .split("&")
|
|
// .map(p => p.split("="))
|
|
// .filter(([key]) => key && key !== "token" && key !== "expires")
|
|
// .sort(([a], [b]) => a.localeCompare(b));
|
|
|
|
// if (params.length) {
|
|
// parameterData = params.map(([k, v]) => `${k}=${v}`).join("&");
|
|
// }
|
|
// }
|
|
|
|
// Build hashable base
|
|
const hashableBase = securityKey + path + expires + parameterData;
|
|
console.log(`hashableBase`, hashableBase)
|
|
|
|
// Compute token using your $security.sha256 workflow
|
|
const tokenH = $security.sha256(hashableBase);
|
|
const token = Buffer.from(tokenH, "hex")
|
|
.toString("base64")
|
|
.replace(/\n/g, "")
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=/g, "");
|
|
|
|
// Build final signed URL
|
|
let tokenUrl = baseUrl + path + "?token=" + token;
|
|
if (parameterData) tokenUrl += "&" + parameterData;
|
|
tokenUrl += "&expires=" + expires;
|
|
if (tokenPath) tokenUrl += "&token_path=" + tokenPath;
|
|
|
|
return tokenUrl;
|
|
}
|
|
|
|
|
|
|
|
const rawQuery = event.requestEvent.request.url.rawQuery;
|
|
|
|
console.log(`record: ${JSON.stringify(event.record)}`)
|
|
// console.log(`collection: ${JSON.stringify(event.collection)}`)
|
|
console.log(`app: ${JSON.stringify(event.app)}`)
|
|
console.log(`fileField: ${JSON.stringify(event.fileField)}`)
|
|
console.log(`servedPath: ${JSON.stringify(event.servedPath)}`)
|
|
console.log(`servedName: ${JSON.stringify(event.servedName)}`)
|
|
|
|
// Our job here is to take the servedPath, and sign it using bunnycdn method
|
|
// Then serve a 302 redirect instead of serving the file proxied thru PB
|
|
|
|
const path = event.servedPath;
|
|
const expires = Math.round(Date.now() / 1000) + 7 * 24 * 3600; // 7 days
|
|
const signedUrl = signUrlCool(securityKey, baseUrl, path, rawQuery, expires);
|
|
console.log(`rawQuery`, rawQuery, 'path', path);
|
|
console.log(`signedUrl=${signedUrl}`);
|
|
|
|
// This redirect is a tricky thing. We do this to avoid proxying file requests via our pocketbase origin server.
|
|
// The idea is to reduce load.
|
|
// HOWEVER, this redirect slows down image loading because it now takes 2 requests per image.
|
|
event.redirect(302, signedUrl);
|
|
|
|
event.next()
|
|
}) |