first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user