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,94 @@
# url-builder
## Purpose
Provides pure URL-building and body-encoding utilities used internally by all OAuth 2.0 grant flows. Keeping these helpers in a single module avoids duplication across the authorization-code, client-credentials, and token-refresh modules while keeping each flow module focused on business logic rather than HTTP serialization details.
These utilities are deliberately stateless and side-effect-free so they can be tested in complete isolation from network calls.
## Features
- `buildAuthorizationUrl` — constructs a complete authorization endpoint URL with PKCE, state, scopes, and optional OIDC parameters (`nonce`, `prompt`, `login_hint`).
- `buildTokenRequestBody` — serializes a flat string record to `application/x-www-form-urlencoded` using `encodeURIComponent`.
- `normalizeTokenResponse` — maps a raw token-endpoint JSON body to a `TokenSet`-compatible shape and derives `expiresAt` from `expires_in`.
## Usage: Basic Example
```ts
import { buildAuthorizationUrl, buildTokenRequestBody } from "@suaveplan/oauth-client/utils/url-builder";
const url = buildAuthorizationUrl(
{ clientId: "my-app", authorizationUrl: "https://auth.example.com/authorize", tokenUrl: "…" },
{ state: "abc", codeChallenge: "xyz", codeChallengeMethod: "S256" },
);
const body = buildTokenRequestBody({
grant_type: "authorization_code",
code: "AUTH_CODE",
redirect_uri: "https://app.example.com/callback",
client_id: "my-app",
code_verifier: "VERIFIER",
});
```
## Usage: Advanced Example
```ts
import {
buildAuthorizationUrl,
normalizeTokenResponse,
} from "@suaveplan/oauth-client/utils/url-builder";
// OIDC authorization request with nonce and prompt
const url = buildAuthorizationUrl(
{
clientId: "my-app",
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
tokenUrl: "https://oauth2.googleapis.com/token",
redirectUri: "https://app.example.com/callback",
scopes: ["openid", "profile", "email"],
},
{
state: "state123",
codeChallenge: "challenge456",
codeChallengeMethod: "S256",
nonce: "nonce789",
prompt: "consent",
loginHint: "user@example.com",
extra: { access_type: "offline" },
},
);
// Normalize raw token response
const raw = { access_token: "at", token_type: "Bearer", expires_in: 3600 };
const tokenSet = normalizeTokenResponse(raw, Date.now());
// tokenSet.expiresAt is set ~1 hour from now
```
## API Reference
### `buildAuthorizationUrl(config, params): string`
Builds a full authorization URL.
- `config` — `OAuthConfig` providing `clientId`, `authorizationUrl`, optional `redirectUri` and `scopes`.
- `params.state` — CSRF-protection state value.
- `params.codeChallenge` — PKCE code challenge.
- `params.codeChallengeMethod` — `"S256"` or `"plain"`.
- `params.scope?` — overrides `config.scopes` for this request.
- `params.nonce?` — OIDC nonce.
- `params.prompt?` — UI prompt hint.
- `params.loginHint?` — pre-fill login hint.
- `params.extra?` — additional query parameters.
### `buildTokenRequestBody(params: Record<string, string>): string`
Returns a percent-encoded `application/x-www-form-urlencoded` string. Both keys and values are encoded with `encodeURIComponent`.
### `normalizeTokenResponse(raw: Record<string, unknown>, now: number): TokenSet`
Maps a raw JSON body to `{ accessToken, tokenType, expiresIn?, expiresAt?, refreshToken?, idToken?, scope? }`. The `now` parameter (milliseconds since epoch) is used to compute `expiresAt = now + expiresIn * 1000`.
## Implementation Notes
`buildAuthorizationUrl` uses the platform `URL` constructor and `URLSearchParams` for correct percent-encoding of complex values. The URL constructor is universally available in all targeted runtimes (Node 10+, browsers, Bun, Deno, edge workers). `buildTokenRequestBody` uses `encodeURIComponent` directly rather than `URLSearchParams` to produce a simple string body that can be passed to fetch without additional wrappers. `normalizeTokenResponse` is intentionally defensive — it uses `typeof` guards rather than Zod for performance in hot paths, leaving schema validation to the caller.