2.7 KiB
upload-token
Purpose
Implements CdnUploadToken issuance and verification (DTP-19). Tokens authorise a single chunk write to the CDN and carry a server signature over the canonical bytes plus a content-hash binding so the CDN can refuse to store anything other than what the server attested.
Features
signUploadToken(token, serverSecretKey, options?)— fillsexpiresAtfrom the injectedClock+ttlMsand signs.verifyUploadToken(token, serverPubkeyHex, clock)— full check: expiry, hex decode, Ed25519 verify.canonicalSerializeUploadToken(token)— deterministic byte-level encoding shared by issuer and verifier.UPLOAD_TOKEN_TTL_DEFAULT_MS(60 s),UPLOAD_TOKEN_TTL_MAX_MS(300 s) — pinned constants the spec dashboards can reference.
Usage: Basic Example
import {
signUploadToken,
verifyUploadToken,
} from "@suaveplan/drive-template";
const tok = signUploadToken(
{
fileId,
chunkId: 0,
chunkHash: "blake3:abc",
uploaderPubkey: peerId,
} as never,
serverSecretKey,
{ clock: () => Date.now() }
);
verifyUploadToken(tok, serverPubkeyHex, () => Date.now());
Usage: Advanced Example
import {
DTP_CODES,
DriveTemplateError,
signUploadToken,
verifyUploadToken,
} from "@suaveplan/drive-template";
async function handleCdnUpload(token, body) {
try {
verifyUploadToken(token, serverPubkeyHex, () => Date.now());
} catch (err) {
if (err instanceof DriveTemplateError && err.code === DTP_CODES.CDN_BAD_SIGNATURE) {
return new Response("forbidden", { status: 403 });
}
throw err;
}
if (`blake3:${blake3Hex(body)}` !== token.chunkHash) {
return new Response("hash mismatch", { status: 422 });
}
return store(token.fileId, token.chunkId, body);
}
API Reference
signUploadToken(token, serverSecretKey, options?: { clock: Clock; ttlMs?: number }): CdnUploadToken.verifyUploadToken(token: CdnUploadToken, serverPubkeyHex: string, clock: Clock): true.canonicalSerializeUploadToken(token: Omit<CdnUploadToken, "serverSignature">): Uint8Array.- Constants:
UPLOAD_TOKEN_TTL_DEFAULT_MS,UPLOAD_TOKEN_TTL_MAX_MS.
Implementation Notes
The TTL upper bound is enforced at sign time to prevent a misconfigured caller from minting tokens that outlive the request that asked for them. Verification trusts @noble/curves to return false for malformed signatures — there's no separate try/catch, which removes a dead branch and keeps the success path branch-free in the hot path. The CDN-side replay protection (the (fileId, chunkId, nonce) short LRU mentioned in DTP-19) lives in the CDN service rather than this module; the protocol only mandates the token shape.