@hawcx/oauth-client 6.0.0 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -0
- package/README.md +217 -0
- package/dist/errors.d.ts +11 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/url.d.ts +7 -0
- package/dist/internal/url.d.ts.map +1 -1
- package/dist/internal/url.js +55 -0
- package/dist/internal/url.js.map +1 -1
- package/dist/oauth.d.ts +88 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +230 -10
- package/dist/oauth.js.map +1 -1
- package/dist/profile/client.d.ts +99 -0
- package/dist/profile/client.d.ts.map +1 -0
- package/dist/profile/client.js +209 -0
- package/dist/profile/client.js.map +1 -0
- package/dist/profile/errors.d.ts +62 -0
- package/dist/profile/errors.d.ts.map +1 -0
- package/dist/profile/errors.js +90 -0
- package/dist/profile/errors.js.map +1 -0
- package/dist/profile/http.d.ts +57 -0
- package/dist/profile/http.d.ts.map +1 -0
- package/dist/profile/http.js +295 -0
- package/dist/profile/http.js.map +1 -0
- package/dist/profile/tokens.d.ts +50 -0
- package/dist/profile/tokens.d.ts.map +1 -0
- package/dist/profile/tokens.js +137 -0
- package/dist/profile/tokens.js.map +1 -0
- package/dist/profile/types.d.ts +99 -0
- package/dist/profile/types.d.ts.map +1 -0
- package/dist/profile/types.js +7 -0
- package/dist/profile/types.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,135 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [6.2.0] - 2026-11-02
|
|
11
|
+
|
|
12
|
+
Additive, apart from these `exchangeCode` / token-endpoint behaviour changes
|
|
13
|
+
(details under Changed): a 3xx from the token endpoint is no longer followed
|
|
14
|
+
and throws `TokenExchangeError`; a 2xx token response that is not a JSON
|
|
15
|
+
object throws `TokenExchangeError` instead of a raw `SyntaxError`/`TypeError`
|
|
16
|
+
(or being returned as-is); a 2xx body cut off mid-read throws a network
|
|
17
|
+
`TokenExchangeError` carrying that 2xx `statusCode`.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Profile service client.** `ProfileClient` (`oauth.profile(options?)` or
|
|
22
|
+
`new ProfileClient(oauth, options?)`), built from a discovery-mode
|
|
23
|
+
`HawcxOAuth` with a `private_key_jwt` signer. Machine methods (RFC 6749 §4.4
|
|
24
|
+
`client_credentials`): `getUser`, `listUsers`/`iterUsers`, `createUser`,
|
|
25
|
+
`updateUser`, `replaceUser`, `deleteUser`, `getConsents`, `setConsent`,
|
|
26
|
+
`getAudit`/`iterAudit`. `forUser(idToken)` returns a `UserProfileClient`
|
|
27
|
+
(RFC 8693 exchange of the ID token for a `profile:self` token): `getMe`,
|
|
28
|
+
`updateMe`, `getMyConsents`, `setMyConsent`, `getMyAudit`/`iterMyAudit`.
|
|
29
|
+
Tokens are cached with early refresh and single-flight minting; expiry is
|
|
30
|
+
counted from when the token request was sent. The early refresh runs in the
|
|
31
|
+
background, so every caller (the one that triggered it included) gets the
|
|
32
|
+
still-valid cached token; if it fails (any error, e.g. a signer error too)
|
|
33
|
+
that token stays in use and no other early refresh is tried for 30 s (or
|
|
34
|
+
until expiry, if sooner). `forUser` tokens are not refreshed early (the OP
|
|
35
|
+
caps their `exp` at the ID token's), only at expiry. A 401 is
|
|
36
|
+
replayed once with a fresh token; for `forUser`, if that re-exchange fails
|
|
37
|
+
with a non-transient token error (e.g. `invalid_grant`, the ID token has
|
|
38
|
+
expired), the original 401 `ProfileApiError` is thrown (token error as
|
|
39
|
+
`cause`); GET/PUT/DELETE retry on transient
|
|
40
|
+
failures (Retry-After honoured, capped at 30 s); a token-endpoint network
|
|
41
|
+
error, 408, 429, 502, 503 or 504 is retried for every method within the same
|
|
42
|
+
budget (no profile request was sent), with backoff only (its Retry-After is
|
|
43
|
+
ignored); POST/PATCH are otherwise never
|
|
44
|
+
retried; a DELETE answered 404 counts as success once any earlier attempt of
|
|
45
|
+
the call may have run (no HTTP response, or 504; not 502, which is
|
|
46
|
+
ambiguous but left out because a false "deleted" is worse). Errors that
|
|
47
|
+
mean nothing was sent do not count: `fetch()` rejecting with a connect/DNS
|
|
48
|
+
or TLS-handshake code (ECONNREFUSED, ENOTFOUND, EAI_*, connect timeouts,
|
|
49
|
+
CERT_*/ERR_TLS_*/ERR_SSL_*), or a routing code (EHOSTUNREACH, ENETUNREACH,
|
|
50
|
+
EHOSTDOWN, ENETDOWN, EADDRNOTAVAIL) with `syscall: 'connect'`; an injected
|
|
51
|
+
`fetch` whose errors carry none of these counts as may-have-run. A failure
|
|
52
|
+
reading a 2xx body always counts, and its error keeps the 2xx status
|
|
53
|
+
(title `Response body read failed`) rather than 0, since the call ran.
|
|
54
|
+
Redirects are not
|
|
55
|
+
followed; path parameters, and `createUser`'s `user_id`, must be one segment
|
|
56
|
+
(`/`, `\` and control characters refused). `deleteUser` counts any 2xx as
|
|
57
|
+
success, whatever its body (empty, whitespace, not JSON, not an object).
|
|
58
|
+
Iterators stop on a null, missing or empty-string `next_cursor`. An
|
|
59
|
+
explicitly empty `scopes` list throws `HawcxError` when the client is built
|
|
60
|
+
(omit `scopes` for the client's default). `timeout` (default 10000) bounds
|
|
61
|
+
each token-endpoint call too, not only profile requests, whatever the
|
|
62
|
+
`HawcxOAuth` client's own `timeout`. `ProfilePatch.status` is `string` (no
|
|
63
|
+
`null`: the server answers 422 to a null status).
|
|
64
|
+
- **`forUser` is for right after sign-in.** The OP caps the delegated token's
|
|
65
|
+
`exp` at the ID token's (`id_token_ttl_seconds`, 60 s by default), so pass
|
|
66
|
+
the ID token `exchangeCode` just returned; for later work on a user's
|
|
67
|
+
profile use the machine methods with their `user_id` (the `sub` claim).
|
|
68
|
+
- **`ProfileApiError`** (`HawcxError` subclass) mapped from RFC 9457
|
|
69
|
+
problem+json: `status`, `title`, `detail`, `type`, `instance`, `requestId`,
|
|
70
|
+
`retryAfter`, `missing`, `fields`, `body`, `rawBody` (the body text, capped
|
|
71
|
+
at 4096 UTF-16 code units, kept out of `message`; on a malformed 2xx only
|
|
72
|
+
when the body is a markup page starting with `<`, since any other body may
|
|
73
|
+
be a mangled profile, i.e. PII). A Kong-style `{"message"}` body
|
|
74
|
+
(no non-blank `title`/`detail`) uses `message` as `detail`; `requestId`
|
|
75
|
+
falls back to the body's `request_id`. A non-204 2xx profile body that is
|
|
76
|
+
not a JSON object (or lacks a required field), and a 204, to any method but
|
|
77
|
+
`deleteUser`, throw `ProfileApiError` "Unexpected response body"; an
|
|
78
|
+
unparseable body "Invalid JSON response".
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- `exchangeCode` no longer follows redirects from the token endpoint: a 3xx is
|
|
83
|
+
now a `TokenExchangeError`, so the code, PKCE verifier and client assertion
|
|
84
|
+
are never re-posted to another host. A token-endpoint network error now
|
|
85
|
+
carries the underlying error as `cause` and sets the new
|
|
86
|
+
`TokenExchangeError.network` flag (`true` when no complete HTTP response
|
|
87
|
+
came back, including a 2xx body cut off mid-read, which keeps the 2xx in
|
|
88
|
+
`statusCode`). A 2xx token response
|
|
89
|
+
that was read in full but is not a JSON object now throws a non-network
|
|
90
|
+
`TokenExchangeError` ("Invalid JSON in token response") instead of a raw
|
|
91
|
+
`SyntaxError`/`TypeError`.
|
|
92
|
+
|
|
93
|
+
## [6.1.0] - 2026-10-12
|
|
94
|
+
|
|
95
|
+
Additive; no behaviour change for existing callers.
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **Custom domains.** Optional `customDomain` on
|
|
100
|
+
`HawcxOAuth.fromIssuer(...)` (e.g. `https://auth.example.com`), validated as
|
|
101
|
+
a raw string (same rules as the Python and Java SDKs): `scheme://host[:port]`
|
|
102
|
+
only, https (http only for loopback), ASCII/punycode hostname (no `*`, `_`,
|
|
103
|
+
trailing dot, numeric-looking last label) or canonical IP literal, port
|
|
104
|
+
1-65535 without leading zeros, no userinfo, path, query, fragment,
|
|
105
|
+
whitespace, `\` or `%`; IPv6 without embedded IPv4. Canonicalised
|
|
106
|
+
(lower-cased, default port dropped, IPv6 in RFC 5952 form, loopback checked
|
|
107
|
+
on the canonical host).
|
|
108
|
+
`null`/`undefined` = unset. An invalid value throws `HawcxOAuthError`
|
|
109
|
+
(`error: 'invalid_custom_domain'`) from `fromIssuer` before any fetch. It
|
|
110
|
+
changes only the browser URLs below; discovery, token exchange, JWKS and the
|
|
111
|
+
`iss` check stay on the issuer.
|
|
112
|
+
- **`buildAuthorizeUrl(options)`** builds the `/authorize` URL from the
|
|
113
|
+
discovered `authorization_endpoint`: origin (custom domain if set, else the
|
|
114
|
+
endpoint's raw scheme and authority) + the
|
|
115
|
+
endpoint's raw path and query, copied verbatim, then `response_type=code`,
|
|
116
|
+
`client_id`, `redirect_uri`, `scope` (blank → `openid`), `state`,
|
|
117
|
+
`code_challenge`, `code_challenge_method=S256`, and optional `nonce`,
|
|
118
|
+
`prompt` (blank = empty or ASCII whitespace only → omitted) and `extraParams` (non-empty keys, string values,
|
|
119
|
+
insertion order kept). `extraParams` cannot set any SDK parameter, `request`
|
|
120
|
+
or `request_uri`, nor a name already in the endpoint query. Missing or blank `redirectUri` / `state` / `codeChallenge`
|
|
121
|
+
or a bad `extraParams` entry throws `HawcxOAuthError`
|
|
122
|
+
(`error: 'invalid_request'`).
|
|
123
|
+
- **`buildEndSessionUrl(options?)`** builds the logout URL from the discovered
|
|
124
|
+
`end_session_endpoint` the same way; always sends `client_id`, plus
|
|
125
|
+
`post_logout_redirect_uri`, `id_token_hint` and `state` when non-blank.
|
|
126
|
+
`null` options = `{}`.
|
|
127
|
+
- Both helpers are discovery-mode only (legacy mode throws `HawcxOAuthError`,
|
|
128
|
+
`error: 'issuer_mode_required'`). A discovered endpoint that is missing, not
|
|
129
|
+
https (loopback http ok), has userinfo, no host, any `#`, a character
|
|
130
|
+
outside printable ASCII or any of `` \ " < > ^ ` { | } ``, or a query that
|
|
131
|
+
already sets one of the helper's SDK parameters (authorize: also `request`,
|
|
132
|
+
`request_uri`) throws `DiscoveryError`
|
|
133
|
+
(`error: 'invalid_discovery_document'`) when the helper is called, never
|
|
134
|
+
from `fromIssuer`, so discovery documents without these fields keep working.
|
|
135
|
+
- URLs are semantically, not byte-for-byte, equal across the Node, Python and
|
|
136
|
+
Java SDKs (parameter encoding differs on e.g. `~`, `*`, space).
|
|
137
|
+
- New exported types `AuthorizeUrlOptions` and `EndSessionUrlOptions`.
|
|
138
|
+
|
|
10
139
|
## [6.0.0] - 2026-10-10
|
|
11
140
|
|
|
12
141
|
First public release since 5.2.0. **Major version** because of the BREAKING
|
package/README.md
CHANGED
|
@@ -108,6 +108,7 @@ is the discovered `token_endpoint`, and EdDSA is the only accepted algorithm.
|
|
|
108
108
|
| Module | Purpose |
|
|
109
109
|
|--------|---------|
|
|
110
110
|
| **OAuth** | Token exchange and JWT verification |
|
|
111
|
+
| **Profile** | Hawcx profile service: users, consents, audit (machine or on behalf of a user) |
|
|
111
112
|
| **Delegation** | Server-to-server user management (MFA, devices) |
|
|
112
113
|
| **Step-Up** | Step-up authentication flows |
|
|
113
114
|
|
|
@@ -130,11 +131,56 @@ const oauth = await HawcxOAuth.fromIssuer({
|
|
|
130
131
|
clientId: 'your-client-id', // Required: expected `aud` on id_tokens
|
|
131
132
|
timeout: 10000, // Optional: per-request timeout in ms
|
|
132
133
|
discoveryTimeout: 5000, // Optional: discovery fetch timeout in ms
|
|
134
|
+
customDomain: 'https://auth.example.com', // Optional: custom domain origin for browser URLs (a CNAME to Hawcx)
|
|
133
135
|
});
|
|
134
136
|
|
|
135
137
|
oauth.discoveryMetadata; // resolved OIDC discovery doc, for diagnostics
|
|
136
138
|
```
|
|
137
139
|
|
|
140
|
+
`customDomain` must be `scheme://host[:port]` only: https (http only for
|
|
141
|
+
loopback), an ASCII/punycode hostname (`xn--...` for IDN) or an IP literal,
|
|
142
|
+
port 1-65535 without leading zeros, no userinfo, path, query or fragment (one
|
|
143
|
+
trailing `/` allowed). IPv6 literals may not embed IPv4 (no `.`). It is
|
|
144
|
+
canonicalised (lower-cased, default port dropped, IPv6 in RFC 5952 form, so
|
|
145
|
+
`http://[0:0::1]` becomes `http://[::1]` and counts as loopback). `null`/`undefined` means unset. An invalid value (including `''`,
|
|
146
|
+
whitespace, `*.example.com` or a trailing dot) throws `HawcxOAuthError`
|
|
147
|
+
(`error: 'invalid_custom_domain'`) from `fromIssuer`, before any network call.
|
|
148
|
+
The SDK only checks the form: `customDomain` must also be the TXT-verified
|
|
149
|
+
domain registered for your client's own tenant, set up as a DNS CNAME to Hawcx
|
|
150
|
+
(Hawcx serves its certificate). Another tenant's verified domain is always refused; when the tenant has
|
|
151
|
+
custom-domain enforcement on, every host other than its own verified domains
|
|
152
|
+
(including IP literals and single-label hosts) is refused too.
|
|
153
|
+
|
|
154
|
+
##### Custom domain
|
|
155
|
+
|
|
156
|
+
When you serve Hawcx sign-in on your own domain (`auth.example.com`, a DNS
|
|
157
|
+
CNAME to Hawcx), the browser must go to `/authorize` and end-session on that
|
|
158
|
+
domain, while token exchange, JWKS and the `iss` check stay on the Hawcx
|
|
159
|
+
issuer.
|
|
160
|
+
`customDomain` swaps only the scheme + host + port of the two browser URLs;
|
|
161
|
+
everything server-to-server is unchanged.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const oauth = await HawcxOAuth.fromIssuer({
|
|
165
|
+
issuer: 'https://api.hawcx.com',
|
|
166
|
+
configId: process.env.HAWCX_CONFIG_ID!,
|
|
167
|
+
clientId: process.env.HAWCX_CLIENT_ID!,
|
|
168
|
+
customDomain: 'https://auth.example.com',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// {issuer}/authorize → https://auth.example.com/authorize?response_type=code&client_id=...
|
|
172
|
+
const loginUrl = oauth.buildAuthorizeUrl({ redirectUri, state, codeChallenge, nonce });
|
|
173
|
+
|
|
174
|
+
// Token exchange still POSTs to https://api.hawcx.com/oauth2/token
|
|
175
|
+
const { claims } = await oauth.exchangeCode(code, codeVerifier, redirectUri, nonce);
|
|
176
|
+
|
|
177
|
+
// {issuer}/oauth2/end_session → https://auth.example.com/oauth2/end_session?client_id=...
|
|
178
|
+
const logoutUrl = oauth.buildEndSessionUrl({ postLogoutRedirectUri, idTokenHint: idToken });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The `nonce` passed to `buildAuthorizeUrl` must be passed again to
|
|
182
|
+
`exchangeCode` (4th argument).
|
|
183
|
+
|
|
138
184
|
Throws `DiscoveryError` if the discovery document is unreachable, returns
|
|
139
185
|
non-2xx, isn't valid JSON, or is missing any of `token_endpoint`,
|
|
140
186
|
`jwks_uri`, `id_token_signing_alg_values_supported`.
|
|
@@ -209,6 +255,61 @@ interface JwtClaims {
|
|
|
209
255
|
}
|
|
210
256
|
```
|
|
211
257
|
|
|
258
|
+
#### `buildAuthorizeUrl(options: AuthorizeUrlOptions): string`
|
|
259
|
+
|
|
260
|
+
Build the browser `/authorize` URL (discovery mode only; no network I/O).
|
|
261
|
+
Base is the discovered `authorization_endpoint` (`{issuer}/authorize`), with
|
|
262
|
+
its origin replaced by `customDomain` when set (otherwise the endpoint's scheme
|
|
263
|
+
and authority are kept exactly as discovered). Its path and query are copied
|
|
264
|
+
verbatim; the SDK parameters are appended after them. The URLs are
|
|
265
|
+
semantically, not byte-for-byte, equal to the Python and Java SDKs' (parameter
|
|
266
|
+
encoding of e.g. `~`, `*` and space differs).
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const url = oauth.buildAuthorizeUrl({
|
|
270
|
+
redirectUri: 'https://app.example.com/auth/callback', // Required
|
|
271
|
+
state, // Required
|
|
272
|
+
codeChallenge, // Required (S256)
|
|
273
|
+
nonce, // Optional, recommended; pass again to exchangeCode
|
|
274
|
+
scope: 'openid email', // Optional, default 'openid'
|
|
275
|
+
prompt: 'login', // Optional
|
|
276
|
+
extraParams: { login_hint: 'a@example.com' }, // Optional
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Always sets `response_type=code`, `client_id` and `code_challenge_method=S256`.
|
|
281
|
+
Blank (empty or ASCII-whitespace-only: space, `\t`, `\n`, `\v`, `\f`, `\r`;
|
|
282
|
+
NBSP is not blank) optional values are omitted; a blank `scope`
|
|
283
|
+
becomes `openid`. `extraParams` keys must be non-empty and values strings, and
|
|
284
|
+
cannot be any parameter the SDK sets, `request`, `request_uri`, or a name
|
|
285
|
+
already in the endpoint's query (names are case-sensitive). Throws
|
|
286
|
+
`HawcxOAuthError` (`error: 'invalid_request'`) for a missing or blank required
|
|
287
|
+
option or a bad `extraParams` entry, `HawcxOAuthError`
|
|
288
|
+
(`error: 'issuer_mode_required'`) in legacy mode, and `DiscoveryError`
|
|
289
|
+
(`error: 'invalid_discovery_document'`) if the discovered
|
|
290
|
+
`authorization_endpoint` is missing, not https, has userinfo, a `#`, any
|
|
291
|
+
character outside printable ASCII or any of `` \ " < > ^ ` { | } ``, or its query
|
|
292
|
+
already sets one of the SDK's parameters, `request` or `request_uri`.
|
|
293
|
+
|
|
294
|
+
#### `buildEndSessionUrl(options?: EndSessionUrlOptions): string`
|
|
295
|
+
|
|
296
|
+
Build the browser logout URL from the discovered `end_session_endpoint`
|
|
297
|
+
(`{issuer}/oauth2/end_session`, same `customDomain` origin swap). Always sends
|
|
298
|
+
`client_id`; blank options are omitted. `buildEndSessionUrl(null)` is the same
|
|
299
|
+
as `buildEndSessionUrl({})`.
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
const url = oauth.buildEndSessionUrl({
|
|
303
|
+
postLogoutRedirectUri: 'https://app.example.com/', // Optional
|
|
304
|
+
idTokenHint: idToken, // Optional
|
|
305
|
+
state, // Optional
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Same errors as `buildAuthorizeUrl` for legacy mode and the discovered
|
|
310
|
+
`end_session_endpoint` (whose query may not set `client_id`,
|
|
311
|
+
`post_logout_redirect_uri`, `id_token_hint` or `state`).
|
|
312
|
+
|
|
212
313
|
#### `refreshJwks(): void`
|
|
213
314
|
|
|
214
315
|
Reset the JWKS cache so the next verification refetches keys (useful for testing
|
|
@@ -251,6 +352,122 @@ try {
|
|
|
251
352
|
|
|
252
353
|
---
|
|
253
354
|
|
|
355
|
+
## Profile Module
|
|
356
|
+
|
|
357
|
+
`ProfileClient` calls the Hawcx profile service (`{issuer}/profile/v1/...`).
|
|
358
|
+
Backend only: it is built from a discovery-mode `HawcxOAuth` with a
|
|
359
|
+
`private_key_jwt` signer (see [Confidential clients](#confidential-clients-private_key_jwt)).
|
|
360
|
+
|
|
361
|
+
Client configuration in the Hawcx admin console:
|
|
362
|
+
|
|
363
|
+
- **Machine** (`ProfileClient`): `client_credentials` with
|
|
364
|
+
`allowed_scopes` covering what you call (`users:read`, `users:write`,
|
|
365
|
+
`users:delete`, `consent:read`, `consent:write`, `audit:read`).
|
|
366
|
+
- **On behalf of a user** (`forUser`): `token_exchange_scopes: ["profile:self"]`,
|
|
367
|
+
and the user's ID token must have been issued to this same client.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { HawcxOAuth, ClientAssertionSigner, ProfileApiError } from '@hawcx/oauth-client';
|
|
371
|
+
|
|
372
|
+
const oauth = (
|
|
373
|
+
await HawcxOAuth.fromIssuer({ issuer, configId, clientId })
|
|
374
|
+
).withClientAssertion(ClientAssertionSigner.ed25519FromJwk(privateJwk));
|
|
375
|
+
|
|
376
|
+
const profiles = oauth.profile(); // or new ProfileClient(oauth, options)
|
|
377
|
+
|
|
378
|
+
// Machine: any user in the tenant
|
|
379
|
+
const user = await profiles.getUser('user-123');
|
|
380
|
+
await profiles.updateUser('user-123', { display_name: 'Ada', locale: null }); // null clears, omitted = unchanged
|
|
381
|
+
await profiles.setConsent('user-123', 'marketing', { status: 'granted' });
|
|
382
|
+
for await (const p of profiles.iterUsers({ status: 'active' })) console.log(p.user_id);
|
|
383
|
+
|
|
384
|
+
// On behalf of the user, right after sign-in with the ID token you just received
|
|
385
|
+
const me = profiles.forUser(idToken);
|
|
386
|
+
const mine = await me.getMe();
|
|
387
|
+
await me.updateMe({ given_name: 'Ada' });
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Machine methods: `getUser`, `listUsers`, `iterUsers`, `createUser`,
|
|
391
|
+
`updateUser`, `replaceUser`, `deleteUser`, `getConsents`, `setConsent`,
|
|
392
|
+
`getAudit`, `iterAudit`. User methods: `getMe`, `updateMe`, `getMyConsents`,
|
|
393
|
+
`setMyConsent`, `getMyAudit`, `iterMyAudit`.
|
|
394
|
+
|
|
395
|
+
`forUser` only works with a fresh ID token: the OP caps the delegated token's
|
|
396
|
+
lifetime at the ID token's `exp`, and ID tokens live 60 s by default
|
|
397
|
+
(`id_token_ttl_seconds`). Use it with the ID token you just received at
|
|
398
|
+
sign-in; for later work on a user's profile, use the machine methods with
|
|
399
|
+
their `user_id` (the ID token's `sub` claim).
|
|
400
|
+
|
|
401
|
+
List methods take
|
|
402
|
+
`{ cursor?, limit? }` and return `{ items, nextCursor }` (consents:
|
|
403
|
+
`{ current, history, nextCursor }`).
|
|
404
|
+
|
|
405
|
+
Options (all optional): `baseUrl` (default `{issuer}/profile`; https, http only
|
|
406
|
+
for loopback), `scopes` (machine; default: none sent, so the OP grants all the
|
|
407
|
+
client's allowed scopes; an empty list throws), `audience` (exchange, default
|
|
408
|
+
`hx-profile`), `timeout` (ms, default 10000; bounds token-endpoint calls as
|
|
409
|
+
well as profile requests), `maxRetries` (default 2), `fetch`.
|
|
410
|
+
|
|
411
|
+
Behaviour: access tokens are cached (expiry counted from when the token
|
|
412
|
+
request was sent) and refreshed before expiry in the background, so callers
|
|
413
|
+
keep the still-valid token meanwhile (one in-flight request shared by
|
|
414
|
+
concurrent callers); a failed early refresh keeps that token and is not
|
|
415
|
+
retried for 30 s (or until expiry); `forUser` tokens are re-exchanged only at
|
|
416
|
+
expiry; a 401 re-mints the token and replays the request once (for `forUser`,
|
|
417
|
+
if the re-exchange fails, e.g. `invalid_grant`, the original 401 is thrown);
|
|
418
|
+
`deleteUser` treats any 2xx as success, whatever the body; GET/PUT/DELETE are retried on network errors, 408, 429, 502-504
|
|
419
|
+
from the profile service (honouring its `Retry-After`, capped at 30 s); a
|
|
420
|
+
token-endpoint network error, 408, 429, 502, 503 or 504 is retried for every
|
|
421
|
+
method, POST/PATCH included, since no profile request was sent, using backoff
|
|
422
|
+
only (the token endpoint's `Retry-After` is ignored); all retries share the
|
|
423
|
+
same `maxRetries` budget; POST/PATCH are otherwise never retried; redirects are
|
|
424
|
+
not followed; a DELETE answered 404 counts as success if any earlier attempt of
|
|
425
|
+
the same call may have run (it got no HTTP response, or got 504), and that
|
|
426
|
+
stays true whatever later attempts answer; a 502 (ambiguous, but a false
|
|
427
|
+
"deleted" is worse than a false 404) or a failure before anything was sent
|
|
428
|
+
does not count: any error with `syscall` `connect`/`getaddrinfo`, or,
|
|
429
|
+
with no `syscall`, refused, DNS, connect timeout or TLS handshake codes
|
|
430
|
+
(unreachable-host/network codes need `syscall: 'connect'`, since they can also
|
|
431
|
+
hit an established keep-alive socket); an error from an injected `fetch` with
|
|
432
|
+
no recognised `code`/`errno` counts as may-have-run;
|
|
433
|
+
iterators stop on a null or empty `next_cursor`, or if the server repeats a
|
|
434
|
+
cursor.
|
|
435
|
+
|
|
436
|
+
Errors: profile-service failures throw `ProfileApiError` (a `HawcxError`) with
|
|
437
|
+
`status` (`0` = no response: network error/timeout; a 2xx whose body was cut
|
|
438
|
+
off mid-read keeps that status, title `Response body read failed`, detail the
|
|
439
|
+
error name, since the call ran), `title`, `detail`, `type`, `instance`,
|
|
440
|
+
`requestId`, `retryAfter`, `missing` (403 insufficient scope), `fields` (422)
|
|
441
|
+
the parsed JSON `body`, and `rawBody` (the body text, e.g. a gateway's HTML
|
|
442
|
+
error page, capped at 4096 characters, never in `message`; on a malformed 2xx
|
|
443
|
+
only when the body is a markup page starting with `<`, since any other body
|
|
444
|
+
may be a mangled profile, i.e. PII); a body
|
|
445
|
+
with no non-blank `title`/`detail` but a string
|
|
446
|
+
`message` (Kong) uses `message` as `detail`, and `requestId` falls back to the
|
|
447
|
+
body's `request_id`; a 2xx body that is not JSON (`Invalid JSON response`) or
|
|
448
|
+
is not a JSON object / lacks a required field, or a 204 to anything but
|
|
449
|
+
`deleteUser` (`Unexpected response body`), is a `ProfileApiError` with the 2xx
|
|
450
|
+
status. Token failures throw
|
|
451
|
+
`TokenExchangeError` (`statusCode`, `error`, `network`: `true` when no
|
|
452
|
+
complete HTTP response came back, i.e. no response at all, `statusCode`
|
|
453
|
+
undefined, or a 2xx whose body was cut off mid-read, `statusCode` set; for
|
|
454
|
+
`exchangeCode` the latter means the code is already spent). Invalid arguments (empty `userId`/`purpose`/`idToken`)
|
|
455
|
+
throw `HawcxError` before any request, as do `userId`/`purpose` values, and
|
|
456
|
+
`createUser`'s `user_id`, that are `.`/`..` or contain `/`, `\` or control
|
|
457
|
+
characters.
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
try {
|
|
461
|
+
await profiles.createUser({ user_id: 'user-123', given_name: 'Ada', family_name: 'Lovelace' });
|
|
462
|
+
} catch (err) {
|
|
463
|
+
if (err instanceof ProfileApiError && err.status === 409) {
|
|
464
|
+
/* profile already exists */
|
|
465
|
+
} else throw err;
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
254
471
|
## Delegation Module
|
|
255
472
|
|
|
256
473
|
Server-to-server API for managing users, MFA, and devices. Requires a Hawcx secret key.
|
package/dist/errors.d.ts
CHANGED
|
@@ -39,7 +39,17 @@ export declare class HawcxOAuthError extends HawcxError {
|
|
|
39
39
|
}
|
|
40
40
|
export declare class TokenExchangeError extends HawcxOAuthError {
|
|
41
41
|
readonly statusCode?: number;
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* `true` when no complete HTTP response came back: no response at all (DNS /
|
|
44
|
+
* TLS / connection failure, timeout; `statusCode` undefined) or a 2xx whose
|
|
45
|
+
* body was cut off mid-read (`statusCode` is that 2xx; for `exchangeCode`
|
|
46
|
+
* the code is then already spent). The underlying error is on `cause`.
|
|
47
|
+
* `false` otherwise.
|
|
48
|
+
*/
|
|
49
|
+
readonly network: boolean;
|
|
50
|
+
constructor(message: string, statusCode?: number, fields?: HawcxOAuthErrorFields & {
|
|
51
|
+
network?: boolean;
|
|
52
|
+
});
|
|
43
53
|
}
|
|
44
54
|
export declare class TokenVerificationError extends HawcxOAuthError {
|
|
45
55
|
constructor(message: string, fields?: HawcxOAuthErrorFields);
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;gBAEvB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,qBAAqB;CAM5D;AAED,qBAAa,kBAAmB,SAAQ,eAAe;IACrD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;gBAEvB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,qBAAqB;CAM5D;AAED,qBAAa,kBAAmB,SAAQ,eAAe;IACrD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,qBAAqB,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;CAOzD;AAED,qBAAa,sBAAuB,SAAQ,eAAe;gBAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,qBAAqB;CAI5D;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAe,SAAQ,eAAe;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,qBAAqB;CAOnF"}
|
package/dist/errors.js
CHANGED
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAgBH;;;GAGG;AACH,MAAa,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AALD,gCAKC;AAED;;;;;;;;GAQG;AACH,MAAa,eAAgB,SAAQ,UAAU;IAM7C,YAAY,OAAe,EAAE,MAA8B;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,MAAM,EAAE,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3D,IAAI,MAAM,EAAE,gBAAgB,KAAK,SAAS;YAAE,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;IAC9F,CAAC;CACF;AAZD,0CAYC;AAED,MAAa,kBAAmB,SAAQ,eAAe;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAgBH;;;GAGG;AACH,MAAa,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AALD,gCAKC;AAED;;;;;;;;GAQG;AACH,MAAa,eAAgB,SAAQ,UAAU;IAM7C,YAAY,OAAe,EAAE,MAA8B;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,MAAM,EAAE,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3D,IAAI,MAAM,EAAE,gBAAgB,KAAK,SAAS;YAAE,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;IAC9F,CAAC;CACF;AAZD,0CAYC;AAED,MAAa,kBAAmB,SAAQ,eAAe;IAWrD,YACE,OAAe,EACf,UAAmB,EACnB,MAAsD;QAEtD,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,MAAM,EAAE,OAAO,IAAI,KAAK,CAAC;IAC1C,CAAC;CACF;AArBD,gDAqBC;AAED,MAAa,sBAAuB,SAAQ,eAAe;IACzD,YAAY,OAAe,EAAE,MAA8B;QACzD,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AALD,wDAKC;AAED;;;;;;;;;GASG;AACH,MAAa,cAAe,SAAQ,eAAe;IACjD,YAAY,OAAe,EAAE,OAAqD;QAChF,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAA4B,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtD,CAAC;IACH,CAAC;CACF;AARD,wCAQC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
export { HawcxOAuth } from './oauth';
|
|
16
|
-
export type { HawcxOAuthConfig, HawcxOAuthIssuerConfig, ExchangeResult, JwtClaims, DiscoveryMetadata, VerifyTokenOptions, } from './oauth';
|
|
16
|
+
export type { HawcxOAuthConfig, HawcxOAuthIssuerConfig, ExchangeResult, JwtClaims, DiscoveryMetadata, VerifyTokenOptions, AuthorizeUrlOptions, EndSessionUrlOptions, } from './oauth';
|
|
17
17
|
export { HawcxError, HawcxOAuthError, TokenExchangeError, TokenVerificationError, DiscoveryError, } from './errors';
|
|
18
18
|
export type { HawcxOAuthErrorFields } from './errors';
|
|
19
19
|
export { ClientAssertionSigner, CLIENT_ASSERTION_TYPE } from './client-assertion';
|
|
20
|
+
export { ProfileClient, UserProfileClient } from './profile/client';
|
|
21
|
+
export type { ProfileClientOptions } from './profile/client';
|
|
22
|
+
export { ProfileApiError } from './profile/errors';
|
|
23
|
+
export type { Profile, Consent, AuditEvent, Page, ConsentsPage, MyProfilePatch, ProfilePatch, ReplaceUserInput, CreateUserInput, ConsentInput, PageOptions, ListUsersOptions, } from './profile/types';
|
|
20
24
|
export * from './delegation';
|
|
21
25
|
export * from './stepup';
|
|
22
|
-
export declare const VERSION = "6.
|
|
26
|
+
export declare const VERSION = "6.2.0";
|
|
23
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EACd,SAAS,EACT,iBAAiB,EACjB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,cAAc,EACd,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAGlF,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EACV,OAAO,EACP,OAAO,EACP,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,cAAc,cAAc,CAAC;AAG7B,cAAc,UAAU,CAAC;AAEzB,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
28
28
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
29
29
|
};
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
-
exports.VERSION = exports.CLIENT_ASSERTION_TYPE = exports.ClientAssertionSigner = exports.DiscoveryError = exports.TokenVerificationError = exports.TokenExchangeError = exports.HawcxOAuthError = exports.HawcxError = exports.HawcxOAuth = void 0;
|
|
31
|
+
exports.VERSION = exports.ProfileApiError = exports.UserProfileClient = exports.ProfileClient = exports.CLIENT_ASSERTION_TYPE = exports.ClientAssertionSigner = exports.DiscoveryError = exports.TokenVerificationError = exports.TokenExchangeError = exports.HawcxOAuthError = exports.HawcxError = exports.HawcxOAuth = void 0;
|
|
32
32
|
var oauth_1 = require("./oauth");
|
|
33
33
|
Object.defineProperty(exports, "HawcxOAuth", { enumerable: true, get: function () { return oauth_1.HawcxOAuth; } });
|
|
34
34
|
var errors_1 = require("./errors");
|
|
@@ -40,9 +40,15 @@ Object.defineProperty(exports, "DiscoveryError", { enumerable: true, get: functi
|
|
|
40
40
|
var client_assertion_1 = require("./client-assertion");
|
|
41
41
|
Object.defineProperty(exports, "ClientAssertionSigner", { enumerable: true, get: function () { return client_assertion_1.ClientAssertionSigner; } });
|
|
42
42
|
Object.defineProperty(exports, "CLIENT_ASSERTION_TYPE", { enumerable: true, get: function () { return client_assertion_1.CLIENT_ASSERTION_TYPE; } });
|
|
43
|
+
// Profile service module
|
|
44
|
+
var client_1 = require("./profile/client");
|
|
45
|
+
Object.defineProperty(exports, "ProfileClient", { enumerable: true, get: function () { return client_1.ProfileClient; } });
|
|
46
|
+
Object.defineProperty(exports, "UserProfileClient", { enumerable: true, get: function () { return client_1.UserProfileClient; } });
|
|
47
|
+
var errors_2 = require("./profile/errors");
|
|
48
|
+
Object.defineProperty(exports, "ProfileApiError", { enumerable: true, get: function () { return errors_2.ProfileApiError; } });
|
|
43
49
|
// Delegation module
|
|
44
50
|
__exportStar(require("./delegation"), exports);
|
|
45
51
|
// Step-up module
|
|
46
52
|
__exportStar(require("./stepup"), exports);
|
|
47
|
-
exports.VERSION = '6.
|
|
53
|
+
exports.VERSION = '6.2.0';
|
|
48
54
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;;AAEH,iCAAqC;AAA5B,mGAAA,UAAU,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;;AAEH,iCAAqC;AAA5B,mGAAA,UAAU,OAAA;AAWnB,mCAMkB;AALhB,oGAAA,UAAU,OAAA;AACV,yGAAA,eAAe,OAAA;AACf,4GAAA,kBAAkB,OAAA;AAClB,gHAAA,sBAAsB,OAAA;AACtB,wGAAA,cAAc,OAAA;AAIhB,uDAAkF;AAAzE,yHAAA,qBAAqB,OAAA;AAAE,yHAAA,qBAAqB,OAAA;AAErD,yBAAyB;AACzB,2CAAoE;AAA3D,uGAAA,aAAa,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAEzC,2CAAmD;AAA1C,yGAAA,eAAe,OAAA;AAgBxB,oBAAoB;AACpB,+CAA6B;AAE7B,iBAAiB;AACjB,2CAAyB;AAEZ,QAAA,OAAO,GAAG,OAAO,CAAC"}
|
package/dist/internal/url.d.ts
CHANGED
|
@@ -63,4 +63,11 @@ export declare function parseSecureBaseUrl(label: string, raw: string, makeError
|
|
|
63
63
|
* base's exact origin and stays under its path prefix.
|
|
64
64
|
*/
|
|
65
65
|
export declare function safeJoinUrl(base: SecureBaseUrl, path: string, label: string, makeError: UrlErrorFactory): string;
|
|
66
|
+
/**
|
|
67
|
+
* Validate a browser-facing custom domain as a raw string (no WHATWG parse,
|
|
68
|
+
* which would strip whitespace, decode `%2e`, accept `*` or a trailing dot)
|
|
69
|
+
* and return its canonical origin: `scheme://host[:port]`, lower-cased,
|
|
70
|
+
* default port dropped. Same rules as the Python and Java SDKs.
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseCustomDomain(raw: unknown, makeError: UrlErrorFactory): string;
|
|
66
73
|
//# sourceMappingURL=url.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/internal/url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/internal/url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,KAAK,CAAC;AAiB1E,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,GAAG,GAAG,CAsB3F;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,eAAe,GACzB,aAAa,CAYf;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,eAAe,GACzB,MAAM,CAyCR;AAOD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,eAAe,GAAG,MAAM,CAyClF"}
|
package/dist/internal/url.js
CHANGED
|
@@ -31,6 +31,8 @@ exports.isLoopbackHostname = isLoopbackHostname;
|
|
|
31
31
|
exports.assertSecureUrl = assertSecureUrl;
|
|
32
32
|
exports.parseSecureBaseUrl = parseSecureBaseUrl;
|
|
33
33
|
exports.safeJoinUrl = safeJoinUrl;
|
|
34
|
+
exports.parseCustomDomain = parseCustomDomain;
|
|
35
|
+
const net_1 = require("net");
|
|
34
36
|
/** Exact hostnames (as reported by `URL#hostname`) allowed to use plain http. */
|
|
35
37
|
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
36
38
|
/** C0 controls, space and DEL — stripped or rewritten silently by the URL parser. */
|
|
@@ -145,6 +147,59 @@ function safeJoinUrl(base, path, label, makeError) {
|
|
|
145
147
|
}
|
|
146
148
|
return joined.href;
|
|
147
149
|
}
|
|
150
|
+
/** Raw-string shape of a custom domain (cross-SDK contract §1). */
|
|
151
|
+
const CUSTOM_DOMAIN_SHAPE = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/(\[[0-9A-Fa-f:.]+\]|[^/:?#@[\]]+)(?::([^/]*))?\/?$/;
|
|
152
|
+
const DNS_LABEL = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
153
|
+
const IPV4_OCTET = /^(0|[1-9][0-9]{0,2})$/;
|
|
154
|
+
/**
|
|
155
|
+
* Validate a browser-facing custom domain as a raw string (no WHATWG parse,
|
|
156
|
+
* which would strip whitespace, decode `%2e`, accept `*` or a trailing dot)
|
|
157
|
+
* and return its canonical origin: `scheme://host[:port]`, lower-cased,
|
|
158
|
+
* default port dropped. Same rules as the Python and Java SDKs.
|
|
159
|
+
*/
|
|
160
|
+
function parseCustomDomain(raw, makeError) {
|
|
161
|
+
const bad = (why) => makeError(`customDomain ${why}: ${JSON.stringify(raw)}`);
|
|
162
|
+
if (typeof raw !== 'string' || raw === '')
|
|
163
|
+
throw bad('must be a non-empty string');
|
|
164
|
+
// Printable ASCII only, checked before lower-casing (U+212A Kelvin sign lower-cases to 'k').
|
|
165
|
+
if (/[^\x21-\x7e]|[\\%]/.test(raw)) {
|
|
166
|
+
throw bad('must be printable ASCII (punycode for IDN) without whitespace, backslash or %');
|
|
167
|
+
}
|
|
168
|
+
const m = CUSTOM_DOMAIN_SHAPE.exec(raw);
|
|
169
|
+
if (m === null)
|
|
170
|
+
throw bad('must be scheme://host[:port] only (no userinfo, path, query or fragment)');
|
|
171
|
+
const scheme = m[1].toLowerCase();
|
|
172
|
+
let host = m[2].toLowerCase();
|
|
173
|
+
const port = m[3];
|
|
174
|
+
if (host.startsWith('[')) {
|
|
175
|
+
if (host.includes('.') || !(0, net_1.isIPv6)(host.slice(1, -1))) {
|
|
176
|
+
throw bad('has an invalid IPv6 literal (embedded IPv4 not allowed)');
|
|
177
|
+
}
|
|
178
|
+
// WHATWG serialises IPv6 in RFC 5952 form: `[0:0::1]` -> `[::1]` (contract R7).
|
|
179
|
+
host = new URL(`http://${host}`).hostname;
|
|
180
|
+
}
|
|
181
|
+
else if (/^[0-9.]+$/.test(host) && host.split('.').length === 4) {
|
|
182
|
+
if (!host.split('.').every((o) => IPV4_OCTET.test(o) && Number(o) <= 255)) {
|
|
183
|
+
throw bad('has a non-canonical IPv4 address');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const labels = host.split('.');
|
|
188
|
+
if (host.length > 253 ||
|
|
189
|
+
!labels.every((l) => l.length <= 63 && DNS_LABEL.test(l)) ||
|
|
190
|
+
!/^[a-z]/.test(labels[labels.length - 1])) {
|
|
191
|
+
throw bad('has an invalid hostname (ASCII/punycode labels only, last label starts with a letter)');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (port !== undefined && !(/^[1-9][0-9]{0,4}$/.test(port) && Number(port) <= 65535)) {
|
|
195
|
+
throw bad('has an invalid port');
|
|
196
|
+
}
|
|
197
|
+
if (scheme !== 'https' && !(scheme === 'http' && isLoopbackHostname(host))) {
|
|
198
|
+
throw bad('must use https (http only for loopback)');
|
|
199
|
+
}
|
|
200
|
+
const defaultPort = scheme === 'https' ? '443' : '80';
|
|
201
|
+
return `${scheme}://${host}${port === undefined || port === defaultPort ? '' : ':' + port}`;
|
|
202
|
+
}
|
|
148
203
|
/** Render a URL for an error message with any credentials removed. */
|
|
149
204
|
function redact(u) {
|
|
150
205
|
const copy = new URL(u.href);
|
package/dist/internal/url.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/internal/url.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/internal/url.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;AAuBH,gDAEC;AAYD,0CAsBC;AAgBD,gDAgBC;AAUD,kCA8CC;AAaD,8CAyCC;AAvMD,6BAA6B;AAK7B,iFAAiF;AACjF,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7F,qFAAqF;AACrF,4CAA4C;AAC5C,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,wBAAwB,GAAG,cAAc,CAAC;AAEhD,mEAAmE;AACnE,SAAgB,kBAAkB,CAAC,QAAgB;IACjD,OAAO,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,eAAe,CAAC,KAAa,EAAE,GAAW,EAAE,SAA0B;IACpF,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,SAAS,CAAC,GAAG,KAAK,iCAAiC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAM,CAAC;IACX,IAAI,CAAC;QACH,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,SAAS,CAAC,GAAG,KAAK,wBAAwB,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QAC3C,MAAM,SAAS,CAAC,GAAG,KAAK,6CAA6C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,kBAAkB,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,SAAS,CACb,GAAG,KAAK,yBAAyB,GAAG,yDAAyD,CAC9F,CAAC;AACJ,CAAC;AAYD;;;GAGG;AACH,SAAgB,kBAAkB,CAChC,KAAa,EACb,GAAW,EACX,SAA0B;IAE1B,MAAM,CAAC,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;IACjD,yEAAyE;IACzE,4BAA4B;IAC5B,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/E,MAAM,SAAS,CAAC,GAAG,KAAK,0CAA0C,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,4EAA4E;IAC5E,6EAA6E;IAC7E,oBAAoB;IACpB,MAAM,UAAU,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACjD,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,WAAW,CACzB,IAAmB,EACnB,IAAY,EACZ,KAAa,EACb,SAA0B;IAE1B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtD,MAAM,SAAS,CAAC,GAAG,KAAK,oDAAoD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,SAAS,CAAC,GAAG,KAAK,wCAAwC,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,SAAS,CAAC,GAAG,KAAK,kCAAkC,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,SAAS,CAAC,GAAG,KAAK,oDAAoD,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,SAAS,CAAC,GAAG,KAAK,iCAAiC,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,qEAAqE;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,IAAI,wBAAwB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,MAAM,SAAS,CAAC,GAAG,KAAK,mEAAmE,IAAI,EAAE,CAAC,CAAC;IACrG,CAAC;IAED,0EAA0E;IAC1E,6DAA6D;IAC7D,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,SAAS,CAAC,GAAG,KAAK,0BAA0B,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;IAED,iEAAiE;IACjE,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACtF,MAAM,SAAS,CAAC,GAAG,KAAK,2CAA2C,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,gEAAgE;IAChE,uEAAuE;IACvE,IAAI,IAAI,CAAC,UAAU,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC,EAAE,CAAC;QACjF,MAAM,SAAS,CAAC,GAAG,KAAK,8CAA8C,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,mEAAmE;AACnE,MAAM,mBAAmB,GAAG,mFAAmF,CAAC;AAChH,MAAM,SAAS,GAAG,iCAAiC,CAAC;AACpD,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAE3C;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,GAAY,EAAE,SAA0B;IACxE,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,SAAS,CAAC,gBAAgB,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtF,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE;QAAE,MAAM,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACnF,6FAA6F;IAC7F,IAAI,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,MAAM,GAAG,CAAC,+EAA+E,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,IAAI;QAAE,MAAM,GAAG,CAAC,0EAA0E,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAElB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAA,YAAM,EAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,MAAM,GAAG,CAAC,yDAAyD,CAAC,CAAC;QACvE,CAAC;QACD,gFAAgF;QAChF,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC;IAC5C,CAAC;SAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC1E,MAAM,GAAG,CAAC,kCAAkC,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IACE,IAAI,CAAC,MAAM,GAAG,GAAG;YACjB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACzD,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EACzC,CAAC;YACD,MAAM,GAAG,CAAC,uFAAuF,CAAC,CAAC;QACrG,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACrF,MAAM,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC3E,MAAM,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,OAAO,GAAG,MAAM,MAAM,IAAI,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC;AAC9F,CAAC;AAED,sEAAsE;AACtE,SAAS,MAAM,CAAC,CAAM;IACpB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACnB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACnB,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
|