import { StorageClient } from '@dcd-sdk/storage-client';
const storage = new StorageClient({
endpoint: process.env.DCD_STORAGE_ENDPOINT!,
apiKey: process.env.DCD_STORAGE_API_KEY!,
});
// 1. Upload confidential invoice as private
const asset = await storage.upload('./invoices/inv-2026-904.pdf', {
folder: 'billing/invoices',
access: 'private',
});
// 2. Generate a 15-minute temporary download link
const signedDownloadUrl = await storage.getSignedUrl(asset.publicId, {
expiresInSeconds: 900,
resourceType: 'raw',
});
console.log('Secure Link:', signedDownloadUrl);Not all files belong on an unrestricted public CDN. Customer invoices, tax filings, passport scans, internal contracts, and database snapshots must never be visible to search engine crawlers or brute-forced by scrapers.
DevCloudDesk includes cryptographic HMAC-SHA256 asset protection out of the box.
Public vs Private Assets#
| Feature | access: 'public' (Default) | access: 'private' |
|---|---|---|
| Direct URL Access | Allowed immediately | Returns 403 Forbidden |
| HTTP Caching | Cache-Control: public, max-age=31536000 | Cache-Control: private, no-store |
| CDN Edge Proxies | Cached indefinitely at Cloudflare/Nginx | Never stored in shared edge cache |
| Authentication | None required | Requires API Key or valid ?exp=...&sig=... |
| Typical Use Cases | User avatars, store merchandise, banners | Billing invoices, government IDs, audit dumps |
Step 1: Uploading Files as Private#
Set access: 'private' in the upload options. The storage server stores the file with private ACL flags in its local index.
import { StorageClient } from '@dcd-sdk/storage-client';
const storage = new StorageClient({
endpoint: 'https://cdn.mycompany.com',
apiKey: process.env.DCD_STORAGE_API_KEY!,
});
const file = await storage.upload('./documents/kyc-passport.png', {
folder: 'kyc-documents',
access: 'private', // 🔒 Access locked
});
console.log(file.publicId); // "kyc-documents/kyc-passport"Step 2: Generating Expiring Signed URLs#
When an authorized user requests to view or download their private file, generate a time-limited signed URL on your backend.
// Generate a link valid for 10 minutes (600 seconds)
const secureUrl = await storage.getSignedUrl('billing/invoices/inv-2026-904.pdf', {
expiresInSeconds: 600,
resourceType: 'raw',
});
// Output:
// https://cdn.mycompany.com/raw/upload/billing/invoices/inv-2026-904.pdf?exp=1792600600&sig=9a7bf3c0...Method Options#
| Parameter | Type | Default | Description |
|---|---|---|---|
publicId | string | (Required) | File identifier path without base URL |
expiresInSeconds | number | 900 (15 mins) | Number of seconds before the signature expires |
resourceType | 'image' | 'raw' | 'image' | Delivery pipeline (raw streams exact byte stream) |
How Signature Verification Works#
bash┌─────────────────────────────────────────────────────────────┐ │ 1. Client Browser requests URL: │ │ GET /raw/upload/invoices/inv-904.pdf?exp=1792600600&sig=..│ └───────────────────────────┬─────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 2. Storage Server Checks Expiration: │ │ Is Current Timestamp > 1792600600? │ │ YES ──► Return 401 Unauthorized ("Signed URL Expired") │ │ NO ──► Proceed to HMAC validation │ └───────────────────────────┬─────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 3. Storage Server Computes HMAC: │ │ hmac_sha256("raw/invoices/inv-904.pdf:1792600600", KEY) │ │ Matches provided ?sig query param? │ │ NO ──► Return 403 Forbidden ("Invalid Signature") │ │ YES ──► Stream file with "Cache-Control: private" │ └─────────────────────────────────────────────────────────────┘
- Tamper Resistance: If a client alters the expiration timestamp (
exp) or tries to change the file path, the calculated HMAC hash fails to match, resulting in an immediate403 Forbidden. - Zero In-Memory State: The server does not maintain an expiration database. Verification is pure cryptographic math executed in sub-millisecond CPU time.
- Cache Privacy: Responses include
Cache-Control: private, no-store, no-cacheheaders, ensuring public proxies never leak confidential customer data.
Changing File Visibility Later#
If an asset was originally uploaded as private and needs to become publicly accessible (or vice versa), update its access mode using the administrative API:
await storage.api.update('kyc-documents/kyc-passport', {
access: 'public',
});