first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
# auth-client
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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 `apiKeyCredentials` and caches the result.
- Token cache reuses an in-memory `CachedToken` across 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 `getToken` calls share one auth request.
- Region-aware service-catalog lookup; rejects with `REGION_NOT_IN_CATALOG` when 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
```ts
import { RackspaceAuthClient } from "@suaveplan/storage-cdn-rackspace";
const auth = new RackspaceAuthClient({ username: "me", apiKey: "secret" });
const { tokenId, storageEndpoint, cdnEndpoint } = await auth.getToken();
```
## Usage: Advanced
```ts
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`, `apiKey` required; `authUrl`, `region`, `fetch`, `now` optional 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 next `getToken` re-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 `#pending` promise; the first caller initiates the request, subsequent callers await the same promise. The promise is cleared in `finally` so 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 injected `now`.
- The trailing-slash invariant on endpoints is enforced inside `resolveEndpoints` — the Rackspace catalog historically returns URLs without trailing slashes in some regions, and downstream `new URL(rel, base)` would otherwise truncate the last path segment.
- `invalidate()` does not affect any in-flight `#pending` request — 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:apiKeyCredentials` shape — Rackspace's API-key variant of identity v2. Username/password is supported by identity v2 but is not the recommended Rackspace path.