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,63 @@
# /token endpoint
## Purpose
`handleToken` implements the OAuth 2.0 token endpoint (RFC-6749 §4.1.3
and §6) for the mock IdP. It supports two grant types:
`authorization_code` (exchanging a freshly issued code for an
access+refresh-token pair) and `refresh_token` (rotating an existing
refresh-token).
PKCE verification (RFC-7636) is enforced when the matching auth-code
record carries a `code_challenge`; both `plain` and `S256` challenge
methods are supported, and mismatches produce
`{ error: "invalid_grant" }` responses with HTTP 400 per spec.
## Features
- `authorization_code` grant with single-use code semantics.
- `refresh_token` grant with rotation — the previous refresh token is
revoked on each successful refresh.
- PKCE S256 verification using a real `node:crypto` `sha256` hash.
- Configurable `expires_in` via `accessTokenTtlSeconds`.
- RFC-6749 error envelopes: `invalid_request`, `invalid_grant`,
`unsupported_grant_type`.
## Usage: Basic
```ts
const tokenResponse = handleToken(state, {
grant_type: "authorization_code",
code: "abc",
code_verifier: "verifier-43-chars-min",
});
```
## Usage: Advanced
Rotate a refresh token (the original is revoked):
```ts
const refreshed = handleToken(state, {
grant_type: "refresh_token",
refresh_token: previous.refresh_token,
});
```
## API Reference
- `handleToken(state, form): TokenResult`
- `interface TokenSuccess` — `{ status: 200, body: { access_token,
token_type: "Bearer", expires_in, refresh_token, scope? } }`.
- `interface TokenError` — `{ status: 400|401, body: { error,
error_description? } }`.
## Implementation Notes
PKCE S256 base64url encoding is performed with `Buffer.toString('base64')`
followed by manual `=` stripping and `+`/`/` substitution — this matches
RFC-4648 §5 and is what every conforming client SDK produces. Auth codes
are marked `used = true` on first successful exchange, and reuse is
rejected as `invalid_grant`. Refresh tokens carry forward the original
`userPayload` so subsequent `/userinfo` calls keep returning the same
identity, modelling production IdP behaviour faithfully.