Files
skills/docs/persistence/storage/storage-cdn-bunnycdn/src/token-auth/token-auth.md
2026-05-29 08:20:56 +01:00

3.4 KiB

token-auth

(no submodules)

Purpose

Computes BunnyCDN Pull Zone Token Authentication query strings — ?token=<urlSafeBase64(sha256(key + path + expires))>&expires=<unix>, optionally with &token_path=<prefix> for path-scoped tokens. The signing scheme is BunnyCDN's documented Pull Zone protection: concatenate the per-Pull-Zone signing key with the path and expiry, SHA-256 hash, and URL-safe-base64 encode (no padding).

The adapter calls this from signUrl; consumers never invoke it directly unless they need to sign a URL outside the adapter's standard <pullZoneHostname>/<key> pattern.

Features

  • SHA-256 via crypto.subtle — runs in Node 18+, Bun, Deno, browsers, Workers.
  • URL-safe base64 output: + → -, / → _, no = padding (matches BunnyCDN's verifier).
  • Optional tokenPath query parameter for prefix-scoped tokens.
  • Throws STORAGE_CDN_BUNNYCDN.TOKEN_AUTH_KEY_MISSING when the key is empty or missing.
  • Pure async function — no state, no side effects.

Usage: Basic

import { buildTokenAuthQuery } from "@suaveplan/storage-cdn-bunnycdn";

const query = await buildTokenAuthQuery({
    tokenAuthKey: "per-zone-secret",
    path: "/photos/a.jpg",
    expires: Math.floor(Date.now() / 1000) + 60,
});
// query === "?token=<urlSafeBase64>&expires=<unix>"

Usage: Advanced

import { buildTokenAuthQuery } from "@suaveplan/storage-cdn-bunnycdn";

async function signedFolderUrl(hostname: string, folder: string, ttl: number): Promise<string> {
    const expires = Math.floor(Date.now() / 1000) + ttl;
    const tokenPath = `/${folder}/`;
    const query = await buildTokenAuthQuery({
        tokenAuthKey: process.env["BUNNY_TOKEN_KEY"]!,
        path: tokenPath,
        expires,
        tokenPath,
    });
    return `https://${hostname}${tokenPath}${query}`;
}

API Reference

interface TokenAuthInput

{ tokenAuthKey, path, expires, tokenPath? }. path is the absolute path on the Pull Zone (with leading /). expires is Unix seconds. tokenPath enables prefix-scoped tokens (the token authorises every URL under the prefix until expiry).

buildTokenAuthQuery(input): Promise<string>

Returns the ?token=...&expires=... query string to append to the absolute edge URL. Throws STORAGE_CDN_BUNNYCDN.TOKEN_AUTH_KEY_MISSING on empty tokenAuthKey.

Implementation Notes

  • BunnyCDN's signing scheme is SHA-256 (not HMAC). The secret is concatenated directly with path + expires and hashed; the URL-safe-base64 encoding makes the result safe for query-parameter inclusion.
  • Padding is stripped because BunnyCDN's verifier rejects tokens with = characters; this is a documented quirk of their implementation.
  • expires is Unix seconds — BunnyCDN's standard everywhere. Passing milliseconds produces verifier rejections that read like "token expired" but are actually "token format wrong".
  • The function is async because crypto.subtle.digest is Promise-returning. The underlying SHA-256 is fast (≪1ms).
  • The shipped tests assert determinism (same inputs → identical output), format correctness, and difference under varied inputs; byte-exact reference vectors are the responsibility of integration tests against a live BunnyCDN account.
  • btoa is part of the universal runtime baseline (Node 18+, Bun, Deno, browsers, Workers); a Node-only Buffer fallback is not needed.