3.8 KiB
3.8 KiB
auth-client
(no submodules)
Purpose
RackspaceAuthClient wraps Rackspace identity v2 (POST /v2.0/tokens) with the three behaviours every consumer of the storage / CDN endpoints needs: a cached token reused across requests, a proactive refresh that fires before the documented expiry, and a service-catalog lookup that resolves the per-region cloudFiles and cloudFilesCDN endpoints in one round-trip. The adapter wires this client behind every public call so application code never thinks about token state or endpoint discovery.
Features
- Single-shot auth — POSTs
apiKeyCredentialsand caches the result. - Token cache reuses an in-memory
CachedTokenacross calls. - Proactive refresh five minutes before the documented expiry — no caller is blocked waiting for an in-flight re-auth on a near-expired token.
- Concurrent-call deduplication: parallel
getTokencalls share one auth request. - Region-aware service-catalog lookup; rejects with
REGION_NOT_IN_CATALOGwhen the response lacks an entry for the configured region. invalidate()hook so an outer 401 handler can drop the cache before re-trying.- AbortSignal threaded into the auth fetch.
Usage: Basic
import { RackspaceAuthClient } from "@suaveplan/storage-cdn-rackspace";
const auth = new RackspaceAuthClient({ username: "me", apiKey: "secret" });
const { tokenId, storageEndpoint, cdnEndpoint } = await auth.getToken();
Usage: Advanced
import { RackspaceAuthClient } from "@suaveplan/storage-cdn-rackspace";
const auth = new RackspaceAuthClient({
authUrl: "https://lon.identity.api.rackspacecloud.com/v2.0",
username: process.env["RAX_USER"]!,
apiKey: process.env["RAX_KEY"]!,
region: "LON",
fetch: customFetch,
now: () => clock.now(),
});
async function withFreshToken<T>(op: (tokenId: string) => Promise<T>): Promise<T> {
try {
const { tokenId } = await auth.getToken();
return await op(tokenId);
} catch (err) {
auth.invalidate();
const { tokenId } = await auth.getToken();
return op(tokenId); // single retry
}
}
API Reference
class RackspaceAuthClient
constructor(options)—username,apiKeyrequired;authUrl,region,fetch,nowoptional with sensible defaults (DEFAULT_AUTH_URL,"LON",globalThis.fetch,Date.now).getToken(signal?): Promise<CachedToken>— returns the cached token when still fresh; otherwise authenticates.invalidate(): void— drops the cache; the nextgetTokenre-auths.
CachedToken
{ tokenId, expiresMs, storageEndpoint, cdnEndpoint, region } — both endpoints carry a trailing slash so consumers can compose URLs via new URL(key, endpoint) safely.
Implementation Notes
- Concurrent dedup uses a single
#pendingpromise; the first caller initiates the request, subsequent callers await the same promise. The promise is cleared infinallyso a rejected auth doesn't poison future calls. - Proactive refresh is calculated against
now()rather than wall-clock time so tests can pin the boundary precisely with an injectednow. - The trailing-slash invariant on endpoints is enforced inside
resolveEndpoints— the Rackspace catalog historically returns URLs without trailing slashes in some regions, and downstreamnew URL(rel, base)would otherwise truncate the last path segment. invalidate()does not affect any in-flight#pendingrequest — it only clears#cache. This matches the documented "re-auth once, retry once" pattern from the adapter's 401 recovery path.- The auth request body uses the documented
RAX-KSKEY:apiKeyCredentialsshape — Rackspace's API-key variant of identity v2. Username/password is supported by identity v2 but is not the recommended Rackspace path.