@hawcx/oauth-client 5.2.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 +355 -0
- package/README.md +247 -0
- package/dist/client-assertion.d.ts.map +1 -1
- package/dist/client-assertion.js +6 -2
- package/dist/client-assertion.js.map +1 -1
- package/dist/delegation/exceptions.d.ts +34 -0
- package/dist/delegation/exceptions.d.ts.map +1 -1
- package/dist/delegation/exceptions.js +39 -1
- package/dist/delegation/exceptions.js.map +1 -1
- package/dist/delegation/hawcx.d.ts +6 -2
- package/dist/delegation/hawcx.d.ts.map +1 -1
- package/dist/delegation/hawcx.js.map +1 -1
- package/dist/delegation/index.d.ts +2 -1
- package/dist/delegation/index.d.ts.map +1 -1
- package/dist/delegation/index.js +3 -1
- package/dist/delegation/index.js.map +1 -1
- package/dist/delegation/transport.d.ts +6 -2
- package/dist/delegation/transport.d.ts.map +1 -1
- package/dist/delegation/transport.js +9 -2
- package/dist/delegation/transport.js.map +1 -1
- 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 +73 -0
- package/dist/internal/url.d.ts.map +1 -0
- package/dist/internal/url.js +210 -0
- package/dist/internal/url.js.map +1 -0
- package/dist/management/jwt_transport.d.ts +44 -3
- package/dist/management/jwt_transport.d.ts.map +1 -1
- package/dist/management/jwt_transport.js +60 -4
- package/dist/management/jwt_transport.js.map +1 -1
- package/dist/management/response_signature.d.ts +65 -0
- package/dist/management/response_signature.d.ts.map +1 -0
- package/dist/management/response_signature.js +170 -0
- package/dist/management/response_signature.js.map +1 -0
- package/dist/oauth.d.ts +89 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +265 -34
- 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/dist/stepup/client.d.ts +11 -0
- package/dist/stepup/client.d.ts.map +1 -1
- package/dist/stepup/client.js +6 -1
- package/dist/stepup/client.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Structured URL validation and joining for security-relevant URLs
|
|
4
|
+
* (TOB-HSDK-13).
|
|
5
|
+
*
|
|
6
|
+
* Every URL the SDK sends a credential, a signed request, or an
|
|
7
|
+
* authorization code to — or fetches verification keys from — is built and
|
|
8
|
+
* checked here with the WHATWG `URL` parser instead of string concatenation
|
|
9
|
+
* and prefix/substring checks. String concatenation lets a malformed base or
|
|
10
|
+
* path change the request host, e.g.
|
|
11
|
+
*
|
|
12
|
+
* 'https://api.hawcx.com' + '.attacker.example/v1/x'
|
|
13
|
+
* → https://api.hawcx.com.attacker.example/v1/x
|
|
14
|
+
* 'https://api.hawcx.com' + '@attacker.example/v1/x'
|
|
15
|
+
* → https://attacker.example/v1/x (api.hawcx.com becomes userinfo)
|
|
16
|
+
*
|
|
17
|
+
* Invariants enforced by this module:
|
|
18
|
+
* - A base URL is absolute, `https:` (or `http:` only for the exact loopback
|
|
19
|
+
* hostnames `localhost`, `127.0.0.1`, `[::1]`), carries no userinfo, and no
|
|
20
|
+
* query or fragment.
|
|
21
|
+
* - A joined URL is built from a *relative path* only (leading `/`, not `//`,
|
|
22
|
+
* no backslash, no whitespace/control characters, no fragment, and no
|
|
23
|
+
* percent-encoded `/`, `.` or `\` in the path), and the
|
|
24
|
+
* result is re-parsed and asserted to have exactly the base's origin and to
|
|
25
|
+
* stay under the base's path prefix. Any failure throws (fail closed).
|
|
26
|
+
*
|
|
27
|
+
* Not part of the public API (not re-exported from the package index).
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.isLoopbackHostname = isLoopbackHostname;
|
|
31
|
+
exports.assertSecureUrl = assertSecureUrl;
|
|
32
|
+
exports.parseSecureBaseUrl = parseSecureBaseUrl;
|
|
33
|
+
exports.safeJoinUrl = safeJoinUrl;
|
|
34
|
+
exports.parseCustomDomain = parseCustomDomain;
|
|
35
|
+
const net_1 = require("net");
|
|
36
|
+
/** Exact hostnames (as reported by `URL#hostname`) allowed to use plain http. */
|
|
37
|
+
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
38
|
+
/** C0 controls, space and DEL — stripped or rewritten silently by the URL parser. */
|
|
39
|
+
// eslint-disable-next-line no-control-regex
|
|
40
|
+
const CONTROL_OR_SPACE = /[\u0000- \u007f]/;
|
|
41
|
+
/**
|
|
42
|
+
* Percent-encoded path separators and dots (`%2f`, `%2e`, `%5c`, any case).
|
|
43
|
+
* WHATWG resolves `%2e%2e` as a dot segment but leaves `%2f` encoded, so e.g.
|
|
44
|
+
* `/..%2fevil` keeps the origin yet lands outside the intended prefix once a
|
|
45
|
+
* server or proxy decodes it. No legitimate endpoint needs these.
|
|
46
|
+
*/
|
|
47
|
+
const ENCODED_SEPARATOR_OR_DOT = /%(2f|2e|5c)/i;
|
|
48
|
+
/** True when `hostname` is one of the exact loopback hostnames. */
|
|
49
|
+
function isLoopbackHostname(hostname) {
|
|
50
|
+
return LOOPBACK_HOSTNAMES.has(hostname);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse `raw` and require it to be a secure absolute URL: `https:`, or
|
|
54
|
+
* `http:` only for an exact loopback hostname; no username/password.
|
|
55
|
+
*
|
|
56
|
+
* Query and fragment are permitted here (e.g. a discovered `jwks_uri` may
|
|
57
|
+
* legitimately carry a query); use {@link parseSecureBaseUrl} for a base that
|
|
58
|
+
* paths are appended to.
|
|
59
|
+
*
|
|
60
|
+
* @returns the parsed URL
|
|
61
|
+
*/
|
|
62
|
+
function assertSecureUrl(label, raw, makeError) {
|
|
63
|
+
if (typeof raw !== 'string' || raw.length === 0) {
|
|
64
|
+
throw makeError(`${label} must be a non-empty URL string`);
|
|
65
|
+
}
|
|
66
|
+
let u;
|
|
67
|
+
try {
|
|
68
|
+
u = new URL(raw);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
throw makeError(`${label} is not a valid URL: ${raw}`, err);
|
|
72
|
+
}
|
|
73
|
+
if (u.username !== '' || u.password !== '') {
|
|
74
|
+
throw makeError(`${label} must not contain credentials (userinfo): ${redact(u)}`);
|
|
75
|
+
}
|
|
76
|
+
if (u.protocol === 'https:') {
|
|
77
|
+
return u;
|
|
78
|
+
}
|
|
79
|
+
if (u.protocol === 'http:' && isLoopbackHostname(u.hostname)) {
|
|
80
|
+
return u;
|
|
81
|
+
}
|
|
82
|
+
throw makeError(`${label} must use https (got: ${raw}). Plaintext http is permitted only for loopback hosts.`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Validate a base URL (see {@link assertSecureUrl}) and additionally reject a
|
|
86
|
+
* query or fragment, which would otherwise swallow any appended path.
|
|
87
|
+
*/
|
|
88
|
+
function parseSecureBaseUrl(label, raw, makeError) {
|
|
89
|
+
const u = assertSecureUrl(label, raw, makeError);
|
|
90
|
+
// `u.search`/`u.hash` are '' for both "no ?" and a bare trailing "?", so
|
|
91
|
+
// check the raw string too.
|
|
92
|
+
if (u.search !== '' || u.hash !== '' || raw.includes('?') || raw.includes('#')) {
|
|
93
|
+
throw makeError(`${label} must not contain a query or fragment: ${raw}`);
|
|
94
|
+
}
|
|
95
|
+
// Strip a single trailing slash — the same normalisation the SDK has always
|
|
96
|
+
// applied with `.replace(/\/$/, '')` — so valid bases join to the exact same
|
|
97
|
+
// string as before.
|
|
98
|
+
const pathPrefix = u.pathname.replace(/\/$/, '');
|
|
99
|
+
return { origin: u.origin, pathPrefix };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Join a relative `path` onto a validated base and return the absolute URL
|
|
103
|
+
* string. For a canonical base (lower-case host, no default port) this is
|
|
104
|
+
* byte-identical to the historical `base.replace(/\/$/, '') + path`.
|
|
105
|
+
*
|
|
106
|
+
* Throws unless `path` is a plain relative path and the result keeps the
|
|
107
|
+
* base's exact origin and stays under its path prefix.
|
|
108
|
+
*/
|
|
109
|
+
function safeJoinUrl(base, path, label, makeError) {
|
|
110
|
+
if (typeof path !== 'string' || !path.startsWith('/')) {
|
|
111
|
+
throw makeError(`${label} must be a relative path starting with '/' (got: ${String(path)})`);
|
|
112
|
+
}
|
|
113
|
+
if (path.startsWith('//')) {
|
|
114
|
+
throw makeError(`${label} must not be scheme-relative ('//'): ${path}`);
|
|
115
|
+
}
|
|
116
|
+
if (path.includes('\\')) {
|
|
117
|
+
throw makeError(`${label} must not contain a backslash: ${path}`);
|
|
118
|
+
}
|
|
119
|
+
if (CONTROL_OR_SPACE.test(path)) {
|
|
120
|
+
throw makeError(`${label} must not contain whitespace or control characters`);
|
|
121
|
+
}
|
|
122
|
+
if (path.includes('#')) {
|
|
123
|
+
throw makeError(`${label} must not contain a fragment: ${path}`);
|
|
124
|
+
}
|
|
125
|
+
// Path part only: a query string cannot move the origin or the path.
|
|
126
|
+
const pathPart = path.split('?', 1)[0];
|
|
127
|
+
if (ENCODED_SEPARATOR_OR_DOT.test(pathPart)) {
|
|
128
|
+
throw makeError(`${label} must not contain percent-encoded '/', '.' or '\\' in its path: ${path}`);
|
|
129
|
+
}
|
|
130
|
+
// Build from the parsed origin + prefix, never from the caller's raw base
|
|
131
|
+
// string: the path always begins after a complete authority.
|
|
132
|
+
let joined;
|
|
133
|
+
try {
|
|
134
|
+
joined = new URL(base.origin + base.pathPrefix + path);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
throw makeError(`${label} produced an invalid URL`, err);
|
|
138
|
+
}
|
|
139
|
+
// Fail closed: the request must not leave the configured origin…
|
|
140
|
+
if (joined.origin !== base.origin || joined.username !== '' || joined.password !== '') {
|
|
141
|
+
throw makeError(`${label} resolves outside the configured origin ${base.origin}`);
|
|
142
|
+
}
|
|
143
|
+
// …nor climb out of the configured path prefix via dot segments
|
|
144
|
+
// (including percent-encoded `%2e%2e`, which the URL parser resolves).
|
|
145
|
+
if (base.pathPrefix !== '' && !joined.pathname.startsWith(base.pathPrefix + '/')) {
|
|
146
|
+
throw makeError(`${label} resolves outside the configured base path ${base.pathPrefix}`);
|
|
147
|
+
}
|
|
148
|
+
return joined.href;
|
|
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
|
+
}
|
|
203
|
+
/** Render a URL for an error message with any credentials removed. */
|
|
204
|
+
function redact(u) {
|
|
205
|
+
const copy = new URL(u.href);
|
|
206
|
+
copy.username = '';
|
|
207
|
+
copy.password = '';
|
|
208
|
+
return copy.href;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/internal/url.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,7 +28,13 @@ import type { JsonObject } from '../delegation/transport';
|
|
|
28
28
|
*/
|
|
29
29
|
export type HttpFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
30
30
|
export interface JwtBearerTransportOptions {
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Base URL of the management API, e.g. `https://api.hawcx.com`. Must be
|
|
33
|
+
* `https:` (plain `http:` only for `localhost` / `127.0.0.1` / `[::1]`),
|
|
34
|
+
* without credentials, query or fragment. May carry a path prefix
|
|
35
|
+
* (e.g. `https://gateway.example.com/hawcx`). Validated at construction;
|
|
36
|
+
* an invalid value throws `DelegationRequestError`.
|
|
37
|
+
*/
|
|
32
38
|
baseUrl: string;
|
|
33
39
|
/** Tenant config identifier; forwarded as `X-Config-Id`. */
|
|
34
40
|
configId: string;
|
|
@@ -60,6 +66,36 @@ export interface JwtBearerTransportOptions {
|
|
|
60
66
|
* (Node >= 18). Pass a capturing fake in tests.
|
|
61
67
|
*/
|
|
62
68
|
http?: HttpFetch;
|
|
69
|
+
/**
|
|
70
|
+
* OIDC issuer whose `${issuer}/.well-known/jwks.json` publishes the key that
|
|
71
|
+
* signs management responses (the same JWKS that verifies ID tokens).
|
|
72
|
+
* Defaults to `baseUrl` — in every Hawcx environment the management API and
|
|
73
|
+
* the OP share one public host (e.g. `https://api.hawcx.com`). Set this
|
|
74
|
+
* only when they differ (e.g. local dev with separate ports). Same URL
|
|
75
|
+
* rules as `baseUrl`.
|
|
76
|
+
*/
|
|
77
|
+
issuer?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Verify the `X-Response-Signature` JWS on every successful response
|
|
80
|
+
* (default: `true`). A response with a missing or invalid signature is
|
|
81
|
+
* rejected with `DelegationResponseError`.
|
|
82
|
+
*
|
|
83
|
+
* Failures throw `ResponseSignatureInvalidError` (bad/missing signature) or
|
|
84
|
+
* `ResponseSignatureUnavailableError` (signing keys could not be fetched —
|
|
85
|
+
* the request may already have been applied server-side); both extend
|
|
86
|
+
* `DelegationResponseError` and expose a stable `code`.
|
|
87
|
+
*
|
|
88
|
+
* **Emergency escape hatch only.** Setting `false` makes the SDK trust any
|
|
89
|
+
* response body it receives, unauthenticated; a one-time process warning is
|
|
90
|
+
* emitted. Do not disable in production.
|
|
91
|
+
*/
|
|
92
|
+
verifyResponseSignature?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Allowed difference in seconds between local time and the response
|
|
95
|
+
* signature's `iat` (default: 300) — same default as the ECIES transport's
|
|
96
|
+
* response timestamp skew.
|
|
97
|
+
*/
|
|
98
|
+
clockSkewSeconds?: number;
|
|
63
99
|
}
|
|
64
100
|
/**
|
|
65
101
|
* Drop-in transport for the management API that authenticates via a per-request
|
|
@@ -77,6 +113,8 @@ export declare class JwtBearerTransport {
|
|
|
77
113
|
private readonly jwtTtlSeconds;
|
|
78
114
|
private readonly timeoutMs;
|
|
79
115
|
private readonly http;
|
|
116
|
+
/** `null` only when verification was explicitly disabled. */
|
|
117
|
+
private readonly responseVerifier;
|
|
80
118
|
/** Lazily-imported (and memoised) signing key. */
|
|
81
119
|
private keyPromise?;
|
|
82
120
|
private readonly importKeyFn;
|
|
@@ -85,7 +123,7 @@ export declare class JwtBearerTransport {
|
|
|
85
123
|
/**
|
|
86
124
|
* Mint a per-request EdDSA JWT.
|
|
87
125
|
*
|
|
88
|
-
* @param audience - full request URL (
|
|
126
|
+
* @param audience - full request URL (base joined with endpoint), pinned per-call
|
|
89
127
|
* @param bodySha256 - base64url (no-padding) of SHA-256 over the exact body bytes
|
|
90
128
|
*/
|
|
91
129
|
private mintJwt;
|
|
@@ -95,7 +133,10 @@ export declare class JwtBearerTransport {
|
|
|
95
133
|
* Matches the signature of `DelegationTransport.send` so the two transports
|
|
96
134
|
* are drop-in substitutes behind `StepUpClient`.
|
|
97
135
|
*
|
|
98
|
-
* @param options.endpoint - API path
|
|
136
|
+
* @param options.endpoint - API path relative to `baseUrl`, e.g.
|
|
137
|
+
* `/v1/management/stepup/start-token`. Must start with a single `/`;
|
|
138
|
+
* anything that would resolve off the configured origin or out of its path
|
|
139
|
+
* prefix throws `DelegationRequestError` before the request is signed.
|
|
99
140
|
* @param options.payload - request body; serialised once and hashed; the
|
|
100
141
|
* EXACT same bytes are sent and hashed — no double-serialisation.
|
|
101
142
|
* @param options.headers - optional extra headers merged last (win over built-ins)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt_transport.d.ts","sourceRoot":"","sources":["../../src/management/jwt_transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,EAAmC,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"jwt_transport.d.ts","sourceRoot":"","sources":["../../src/management/jwt_transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,EAAmC,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAc1D;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE9E,MAAM,WAAW,yBAAyB;IACxC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC;IACvC,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;OAaG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmC;IAEpE,kDAAkD;IAClD,OAAO,CAAC,UAAU,CAAC,CAAuB;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;gBAE7C,OAAO,EAAE,yBAAyB;IAgE9C,OAAO,CAAC,SAAS;IAajB;;;;;OAKG;YACW,OAAO;IAiBrB;;;;;;;;;;;;;OAaG;IACG,IAAI,CAAC,IAAI,SAAS,UAAU,EAAE,IAAI,SAAS,UAAU,EAAE,OAAO,EAAE;QACpE,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,IAAI,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,GAAG,OAAO,CAAC,IAAI,CAAC;CAgGlB"}
|
|
@@ -25,6 +25,12 @@ const node_crypto_1 = require("node:crypto");
|
|
|
25
25
|
const node_crypto_2 = require("node:crypto");
|
|
26
26
|
const jose_1 = require("jose");
|
|
27
27
|
const exceptions_1 = require("../delegation/exceptions");
|
|
28
|
+
const url_1 = require("../internal/url");
|
|
29
|
+
const response_signature_1 = require("./response_signature");
|
|
30
|
+
/** Config/URL errors surface as `DelegationRequestError`, like other request failures. */
|
|
31
|
+
const urlError = (message, cause) => new exceptions_1.DelegationRequestError(message, cause === undefined ? {} : { cause });
|
|
32
|
+
/** Emit the "verification disabled" warning at most once per process. */
|
|
33
|
+
let responseSignatureDisabledWarned = false;
|
|
28
34
|
/**
|
|
29
35
|
* Drop-in transport for the management API that authenticates via a per-request
|
|
30
36
|
* EdDSA JWT (`private_key_jwt`, Path A).
|
|
@@ -35,13 +41,49 @@ const exceptions_1 = require("../delegation/exceptions");
|
|
|
35
41
|
*/
|
|
36
42
|
class JwtBearerTransport {
|
|
37
43
|
constructor(options) {
|
|
38
|
-
|
|
44
|
+
// Parsed and validated once (TOB-HSDK-13): every signed request is built
|
|
45
|
+
// from this structured origin + path prefix, never by string concatenation.
|
|
46
|
+
this.baseUrl = (0, url_1.parseSecureBaseUrl)('baseUrl', options.baseUrl, urlError);
|
|
39
47
|
this.configId = options.configId;
|
|
40
48
|
this.kid = options.kid;
|
|
41
49
|
this.clientId = options.clientId;
|
|
42
50
|
this.jwtTtlSeconds = options.jwtTtlSeconds ?? 60;
|
|
43
51
|
this.timeoutMs = (options.timeoutSeconds ?? 15) * 1000;
|
|
44
52
|
this.http = options.http ?? ((...args) => fetch(...args));
|
|
53
|
+
// Response authentication (TOB-HSDK-11). The JWKS URL is derived from the
|
|
54
|
+
// validated issuer with the same structured join as request URLs.
|
|
55
|
+
//
|
|
56
|
+
// DEFAULT ISSUER = baseUrl, i.e. JWKS = ${baseUrl}/.well-known/jwks.json,
|
|
57
|
+
// fetched without X-Config-Id. This holds today because the OP's single
|
|
58
|
+
// global ISSUER is the management API host and its global JWKS carries the
|
|
59
|
+
// response-signing kid. REVISIT under hawcx_core_oauth#67 (per-tenant
|
|
60
|
+
// issuer / discovery_mode cutover): if a tenant's signing kid moves to a
|
|
61
|
+
// per-tenant JWKS, this default stops containing it and every management
|
|
62
|
+
// call fails closed (ResponseSignatureUnavailable/Invalid). Callers can
|
|
63
|
+
// already override via the `issuer` option; whoever lands #67 must update
|
|
64
|
+
// this default (e.g. derive a per-tenant issuer) and the README.
|
|
65
|
+
const issuer = options.issuer === undefined
|
|
66
|
+
? this.baseUrl
|
|
67
|
+
: (0, url_1.parseSecureBaseUrl)('issuer', options.issuer, urlError);
|
|
68
|
+
if (options.verifyResponseSignature === false) {
|
|
69
|
+
this.responseVerifier = null;
|
|
70
|
+
if (!responseSignatureDisabledWarned) {
|
|
71
|
+
responseSignatureDisabledWarned = true;
|
|
72
|
+
process.emitWarning('[@hawcx/oauth-client] JwtBearerTransport: verifyResponseSignature is disabled — ' +
|
|
73
|
+
'management API responses will NOT be authenticated. Use only as a temporary escape hatch.', { type: 'Warning', code: 'HAWCX_RESPONSE_SIGNATURE_DISABLED' });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const clockSkewSeconds = options.clockSkewSeconds ?? 300;
|
|
78
|
+
if (!Number.isFinite(clockSkewSeconds) || clockSkewSeconds < 0) {
|
|
79
|
+
throw new exceptions_1.DelegationRequestError('clockSkewSeconds must be a non-negative number');
|
|
80
|
+
}
|
|
81
|
+
this.responseVerifier = new response_signature_1.ResponseSignatureVerifier({
|
|
82
|
+
jwksUri: (0, url_1.safeJoinUrl)(issuer, '/.well-known/jwks.json', 'JWKS URL', urlError),
|
|
83
|
+
clockSkewSeconds,
|
|
84
|
+
timeoutMs: this.timeoutMs,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
45
87
|
const key = options.oidcSigningKey;
|
|
46
88
|
if (typeof key === 'string') {
|
|
47
89
|
// PKCS#8 PEM
|
|
@@ -72,7 +114,7 @@ class JwtBearerTransport {
|
|
|
72
114
|
/**
|
|
73
115
|
* Mint a per-request EdDSA JWT.
|
|
74
116
|
*
|
|
75
|
-
* @param audience - full request URL (
|
|
117
|
+
* @param audience - full request URL (base joined with endpoint), pinned per-call
|
|
76
118
|
* @param bodySha256 - base64url (no-padding) of SHA-256 over the exact body bytes
|
|
77
119
|
*/
|
|
78
120
|
async mintJwt(audience, bodySha256) {
|
|
@@ -95,7 +137,10 @@ class JwtBearerTransport {
|
|
|
95
137
|
* Matches the signature of `DelegationTransport.send` so the two transports
|
|
96
138
|
* are drop-in substitutes behind `StepUpClient`.
|
|
97
139
|
*
|
|
98
|
-
* @param options.endpoint - API path
|
|
140
|
+
* @param options.endpoint - API path relative to `baseUrl`, e.g.
|
|
141
|
+
* `/v1/management/stepup/start-token`. Must start with a single `/`;
|
|
142
|
+
* anything that would resolve off the configured origin or out of its path
|
|
143
|
+
* prefix throws `DelegationRequestError` before the request is signed.
|
|
99
144
|
* @param options.payload - request body; serialised once and hashed; the
|
|
100
145
|
* EXACT same bytes are sent and hashed — no double-serialisation.
|
|
101
146
|
* @param options.headers - optional extra headers merged last (win over built-ins)
|
|
@@ -107,7 +152,9 @@ class JwtBearerTransport {
|
|
|
107
152
|
// SHA-256 → base64url without padding (RFC 7515 §2).
|
|
108
153
|
const hash = (0, node_crypto_2.createHash)('sha256').update(bodyBytes).digest();
|
|
109
154
|
const bodySha256 = hash.toString('base64url').replace(/=+$/, '');
|
|
110
|
-
|
|
155
|
+
// Join structurally and assert the result stays on the configured origin
|
|
156
|
+
// BEFORE signing: the JWT `aud` and the request target are this one value.
|
|
157
|
+
const url = (0, url_1.safeJoinUrl)(this.baseUrl, endpoint, 'endpoint', urlError);
|
|
111
158
|
let jwt;
|
|
112
159
|
try {
|
|
113
160
|
jwt = await this.mintJwt(url, bodySha256);
|
|
@@ -168,6 +215,15 @@ class JwtBearerTransport {
|
|
|
168
215
|
responseBody: errorBody,
|
|
169
216
|
});
|
|
170
217
|
}
|
|
218
|
+
// Authenticate every response we are about to return as success — any
|
|
219
|
+
// status below 400, i.e. every 2xx (and any stray 1xx/3xx, which the
|
|
220
|
+
// server never sends signed and so fail closed). Error responses (>= 400)
|
|
221
|
+
// are unsigned by design and were rejected above. The hash covers the raw
|
|
222
|
+
// bytes from `arrayBuffer()`, not `text`: UTF-8 decoding replaces invalid
|
|
223
|
+
// sequences and would let distinct byte strings hash the same.
|
|
224
|
+
if (this.responseVerifier !== null) {
|
|
225
|
+
await this.responseVerifier.verify(response.headers.get(response_signature_1.RESPONSE_SIGNATURE_HEADER), responseData);
|
|
226
|
+
}
|
|
171
227
|
let parsed;
|
|
172
228
|
try {
|
|
173
229
|
parsed = JSON.parse(text);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt_transport.js","sourceRoot":"","sources":["../../src/management/jwt_transport.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAEH,6CAAyC;AACzC,6CAAyC;AACzC,+BAAiE;AAEjE,yDAA2F;
|
|
1
|
+
{"version":3,"file":"jwt_transport.js","sourceRoot":"","sources":["../../src/management/jwt_transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification of the management API's `X-Response-Signature` (TOB-HSDK-11).
|
|
3
|
+
*
|
|
4
|
+
* Every successful jwt-mode (Path A) management response from hx_auth carries
|
|
5
|
+
* `X-Response-Signature`: a compact JWS signed with the shared OAuth signing
|
|
6
|
+
* key that the OP publishes at `${issuer}/.well-known/jwks.json` — the same
|
|
7
|
+
* JWKS that verifies ID tokens. Its payload is exactly
|
|
8
|
+
*
|
|
9
|
+
* { "body_sha256": base64url(sha256(<exact response body bytes>)), "iat": <unix s> }
|
|
10
|
+
*
|
|
11
|
+
* Verifying it restores the response authentication the legacy ECIES
|
|
12
|
+
* transport always had (signature + timestamp skew on every response).
|
|
13
|
+
*
|
|
14
|
+
* Checks, all fail-closed:
|
|
15
|
+
* - the header is present and is a compact JWS whose protected header names
|
|
16
|
+
* a `kid`;
|
|
17
|
+
* - `alg` is one of ES256 / ES384 / ES512 (the server's KMS key is an EC key
|
|
18
|
+
* and the alg follows its curve). `none`, HS*, RS*, EdDSA are rejected —
|
|
19
|
+
* this forecloses alg-substitution / key-confusion;
|
|
20
|
+
* - the signature verifies against the issuer JWKS (jose `RemoteJWKSet`:
|
|
21
|
+
* cached, and refetched once when the `kid` is unknown — key rotation);
|
|
22
|
+
* - `iat` is present and within ±clockSkewSeconds of local time;
|
|
23
|
+
* - the payload is a response signature, not some other token minted with
|
|
24
|
+
* the same key (TOB-HSDK-9 type confusion): `body_sha256` is required and
|
|
25
|
+
* `aud`, `sub`, `nonce` must be absent — an ID token always carries
|
|
26
|
+
* `aud`/`sub`;
|
|
27
|
+
* - `body_sha256` equals base64url(sha256(raw body bytes)), compared in
|
|
28
|
+
* constant time. The hash is taken over the raw bytes as received, before
|
|
29
|
+
* any decoding or JSON parsing.
|
|
30
|
+
*
|
|
31
|
+
* Failures are split so callers can react correctly:
|
|
32
|
+
* - {@link ResponseSignatureInvalidError} (`code: 'response_signature_invalid'`)
|
|
33
|
+
* — missing/empty header or a signature that does not verify;
|
|
34
|
+
* - {@link ResponseSignatureUnavailableError}
|
|
35
|
+
* (`code: 'response_signature_unavailable'`) — the signing keys could not
|
|
36
|
+
* be obtained (JWKS fetch failed / timed out / non-200 / malformed, or the
|
|
37
|
+
* kid is unknown while jose's refetch cooldown suppresses a refetch). The
|
|
38
|
+
* request may already have been applied server-side.
|
|
39
|
+
* Both subclass `DelegationResponseError` and carry fixed, non-leaky messages.
|
|
40
|
+
*
|
|
41
|
+
* Not part of the public API (not re-exported from the package index).
|
|
42
|
+
*/
|
|
43
|
+
/** Response-signature header name (HTTP headers are case-insensitive). */
|
|
44
|
+
export declare const RESPONSE_SIGNATURE_HEADER = "X-Response-Signature";
|
|
45
|
+
export interface ResponseSignatureVerifierOptions {
|
|
46
|
+
/** Absolute, already-validated JWKS URL (`${issuer}/.well-known/jwks.json`). */
|
|
47
|
+
jwksUri: string;
|
|
48
|
+
/** Allowed |now − iat| in seconds. */
|
|
49
|
+
clockSkewSeconds: number;
|
|
50
|
+
/** Bound on each JWKS fetch, in ms. */
|
|
51
|
+
timeoutMs: number;
|
|
52
|
+
}
|
|
53
|
+
export declare class ResponseSignatureVerifier {
|
|
54
|
+
private readonly jwks;
|
|
55
|
+
private readonly clockSkewSeconds;
|
|
56
|
+
constructor(options: ResponseSignatureVerifierOptions);
|
|
57
|
+
/**
|
|
58
|
+
* Verify `signature` (the header value, or null when absent) over the EXACT
|
|
59
|
+
* response body bytes. Resolves on success; throws
|
|
60
|
+
* {@link ResponseSignatureInvalidError} or
|
|
61
|
+
* {@link ResponseSignatureUnavailableError} otherwise.
|
|
62
|
+
*/
|
|
63
|
+
verify(signature: string | null, body: Uint8Array): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=response_signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response_signature.d.ts","sourceRoot":"","sources":["../../src/management/response_signature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AASH,0EAA0E;AAC1E,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAoBhE,MAAM,WAAW,gCAAgC;IAC/C,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAE9B,OAAO,EAAE,gCAAgC;IAUrD;;;;;OAKG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAgGxE"}
|