first commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# token-auth
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user