@tidecloak/js 0.13.35-staging → 0.13.36-staging
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/dist/cjs/lib/secureFetchPolicy.js +45 -0
- package/dist/cjs/lib/secureFetchPolicy.js.map +1 -0
- package/dist/cjs/lib/tidecloak.js +67 -5
- package/dist/cjs/lib/tidecloak.js.map +1 -1
- package/dist/cjs/src/AdminAPI.js +25 -3
- package/dist/cjs/src/AdminAPI.js.map +1 -1
- package/dist/cjs/src/IAMService.js +101 -46
- package/dist/cjs/src/IAMService.js.map +1 -1
- package/dist/cjs/src/utils/index.js +3 -1
- package/dist/cjs/src/utils/index.js.map +1 -1
- package/dist/cjs/src/utils/initOptions.js +78 -0
- package/dist/cjs/src/utils/initOptions.js.map +1 -0
- package/dist/esm/lib/secureFetchPolicy.js +42 -0
- package/dist/esm/lib/secureFetchPolicy.js.map +1 -0
- package/dist/esm/lib/tidecloak.js +65 -4
- package/dist/esm/lib/tidecloak.js.map +1 -1
- package/dist/esm/src/AdminAPI.js +25 -3
- package/dist/esm/src/AdminAPI.js.map +1 -1
- package/dist/esm/src/IAMService.js +102 -47
- package/dist/esm/src/IAMService.js.map +1 -1
- package/dist/esm/src/utils/index.js +1 -0
- package/dist/esm/src/utils/index.js.map +1 -1
- package/dist/esm/src/utils/initOptions.js +75 -0
- package/dist/esm/src/utils/initOptions.js.map +1 -0
- package/dist/types/AdminAPI.d.ts +16 -0
- package/dist/types/IAMService.d.ts +25 -0
- package/dist/types/lib/secureFetchPolicy.d.ts +37 -0
- package/dist/types/lib/tidecloak.d.ts +1 -0
- package/dist/types/src/AdminAPI.d.ts +16 -0
- package/dist/types/src/IAMService.d.ts +25 -0
- package/dist/types/src/utils/index.d.ts +1 -0
- package/dist/types/src/utils/initOptions.d.ts +69 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/initOptions.d.ts +69 -0
- package/package.json +5 -5
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the options object handed to `TideCloak.init()` from a caller's
|
|
3
|
+
* resolved TideCloak config.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is an ALLOWLIST, deliberately
|
|
6
|
+
*
|
|
7
|
+
* `IAMService.initIAM` has always built these options as an allowlist: it reads
|
|
8
|
+
* a handful of keys off the config and drops everything else on the floor. The
|
|
9
|
+
* config object is NOT a `KeycloakInitOptions` bag - it is an adapter/config
|
|
10
|
+
* snapshot (`tidecloak.json` + Tide fields + app-level OIDC hints), and callers
|
|
11
|
+
* legitimately put keys in it that are meaningful to `IAMService` (or to their
|
|
12
|
+
* own code) but that must NOT be handed to `init()`.
|
|
13
|
+
*
|
|
14
|
+
* PR #91 widened this allowlist to also forward `redirectUri`,
|
|
15
|
+
* `silentCheckSsoFallback`, `silentCheckSsoTimeout`, `scope` and a
|
|
16
|
+
* caller-supplied `pkceMethod`, on the reasoning that "each is inert unless the
|
|
17
|
+
* caller sets it". That reasoning is wrong in one important direction: consumers
|
|
18
|
+
* had been shipping configs with these keys set for as long as the keys were
|
|
19
|
+
* being SWALLOWED, so their behaviour was pinned to the SDK's defaults, not to
|
|
20
|
+
* the value in their config. Forwarding them is therefore a silent behaviour
|
|
21
|
+
* change for every existing consumer - the exact opposite of inert.
|
|
22
|
+
*
|
|
23
|
+
* `scope` is the sharpest edge: forwarding it rewrites the authorization
|
|
24
|
+
* request's scope, which changes the audience/roles on the issued access token
|
|
25
|
+
* and can strip a consumer's admin-API access.
|
|
26
|
+
*
|
|
27
|
+
* So: forward ONLY what the SDK genuinely needs, and keep the previously
|
|
28
|
+
* defaulted behaviour byte-for-byte. Any option that a consumer should be able
|
|
29
|
+
* to set must be added here CONSCIOUSLY, with its prior default preserved.
|
|
30
|
+
*
|
|
31
|
+
* ### Forwarded
|
|
32
|
+
* - `setupRequestEnclave` - passed through from the `initIAM` argument.
|
|
33
|
+
* - `onLoad` - always `"check-sso"`. NOT taken from config: callers (e.g. the
|
|
34
|
+
* admin console) set `onLoad: "login-required"`, and honouring it would send
|
|
35
|
+
* `#processInit` down the full-redirect branch and never run silent check-sso
|
|
36
|
+
* at all. That is a separate product decision.
|
|
37
|
+
* - `silentCheckSsoRedirectUri` - the resolved URI (see ./silentCheckSso.js).
|
|
38
|
+
* This is the ONE option PR #91 needed to make caller-supplied, and it stays.
|
|
39
|
+
* `undefined` => TideCloak skips silent check-sso and falls back to an
|
|
40
|
+
* interactive login rather than emitting a URI that would 400.
|
|
41
|
+
* - `pkceMethod` - always `"S256"`, as it was before PR #91.
|
|
42
|
+
* - `useDPoP` / `checkLoginIframe` - the two options `initIAM` has always
|
|
43
|
+
* forwarded. `useDPoP` is forwarded ONLY when the caller actually set it, and
|
|
44
|
+
* VERBATIM. DPoP is opt-in: nothing upstream may default it on, because
|
|
45
|
+
* enabling it appends `dpop_jkt` to the authorization request and the realm
|
|
46
|
+
* then issues a `cnf.jkt`-BOUND token, which a plain-Bearer consumer cannot
|
|
47
|
+
* use (RFC 9449) - a bare `401` with no explanation. Pinned in
|
|
48
|
+
* test/bootstrapDpop.test.js.
|
|
49
|
+
*
|
|
50
|
+
* ### Deliberately NOT forwarded (regression-tested in test/initOptions.test.js)
|
|
51
|
+
* `scope`, `redirectUri`, `silentCheckSsoFallback`, `silentCheckSsoTimeout`.
|
|
52
|
+
* Consumers depend on the SDK's defaults for these. `redirectUri` in particular
|
|
53
|
+
* is still READ from config by `IAMService.doLogin` / `doLogout` and is used as
|
|
54
|
+
* a derivation source for `silentCheckSsoRedirectUri`, so a sub-path-hosted app
|
|
55
|
+
* still gets a correct login redirect - it just does not have `kc.redirectUri`
|
|
56
|
+
* overridden at init time.
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} args
|
|
59
|
+
* @param {Object|null|undefined} args.config Resolved TideCloak config.
|
|
60
|
+
* @param {boolean} args.setupRequestEnclave
|
|
61
|
+
* @param {string|undefined} args.silentCheckSsoRedirectUri Resolved URI, or
|
|
62
|
+
* `undefined` when no correct URI could be derived.
|
|
63
|
+
* @returns {Object} Options for `TideCloak.init()`.
|
|
64
|
+
*/
|
|
65
|
+
export function buildInitOptions({ config, setupRequestEnclave, silentCheckSsoRedirectUri, }) {
|
|
66
|
+
return {
|
|
67
|
+
setupRequestEnclave,
|
|
68
|
+
onLoad: "check-sso",
|
|
69
|
+
silentCheckSsoRedirectUri,
|
|
70
|
+
pkceMethod: "S256",
|
|
71
|
+
...((config === null || config === void 0 ? void 0 : config.useDPoP) && { useDPoP: config.useDPoP }),
|
|
72
|
+
...((config === null || config === void 0 ? void 0 : config.checkLoginIframe) === false && { checkLoginIframe: false }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=initOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initOptions.js","sourceRoot":"","sources":["../../../../src/utils/initOptions.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,MAAM,EACN,mBAAmB,EACnB,yBAAyB,GAC1B;IACC,OAAO;QACL,mBAAmB;QACnB,MAAM,EAAE,WAAW;QACnB,yBAAyB;QACzB,UAAU,EAAE,MAAM;QAClB,GAAG,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,OAAO,KAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;QACnD,GAAG,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,gBAAgB,MAAK,KAAK,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC;KACvE,CAAC;AACJ,CAAC"}
|
package/dist/types/AdminAPI.d.ts
CHANGED
|
@@ -11,6 +11,22 @@ export class AdminAPI {
|
|
|
11
11
|
* Get the current realm
|
|
12
12
|
*/
|
|
13
13
|
getRealm(): any;
|
|
14
|
+
/**
|
|
15
|
+
* SAFETY INVARIANT: never send this SDK's own access token as a plain `Bearer`.
|
|
16
|
+
*
|
|
17
|
+
* When DPoP is enabled the issued token is sender-constrained (`typ: DPoP`,
|
|
18
|
+
* `cnf.jkt`), and RFC 9449 says a bound token presented as a plain `Bearer` is
|
|
19
|
+
* invalid - the server answers a bare `401` explaining nothing. So every admin
|
|
20
|
+
* call goes through `IAMService.secureFetch`, which:
|
|
21
|
+
*
|
|
22
|
+
* - with a DPoP provider: upgrades `Authorization: Bearer <our token>` to
|
|
23
|
+
* `Authorization: DPoP <our token>` + a `DPoP:` proof (and handles nonces);
|
|
24
|
+
* - with no DPoP provider: falls through to a plain `fetch` - byte-for-byte
|
|
25
|
+
* what this method did before.
|
|
26
|
+
*
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
private _authFetch;
|
|
14
30
|
/**
|
|
15
31
|
* Make an authenticated fetch request
|
|
16
32
|
*/
|
|
@@ -137,6 +137,7 @@ declare class IAMService {
|
|
|
137
137
|
_nativeEncryptionCallbackUnsubscribe: any;
|
|
138
138
|
_pendingEncryptionRequests: Map<any, any>;
|
|
139
139
|
_dpopProvider: import("../lib/tidecloak-dpop.js").DPoPSignatureProvider;
|
|
140
|
+
_frontchannelInitPromise: Promise<boolean>;
|
|
140
141
|
/**
|
|
141
142
|
* Register an event listener.
|
|
142
143
|
* @param {'ready'|'initError'|'authSuccess'|'authError'|'authRefreshSuccess'|'authRefreshError'|'logout'|'tokenExpired'} event
|
|
@@ -177,6 +178,15 @@ declare class IAMService {
|
|
|
177
178
|
* @returns {Promise<boolean>} true if authenticated, else false.
|
|
178
179
|
*/
|
|
179
180
|
initIAM(config: any, onReady?: Function): Promise<boolean>;
|
|
181
|
+
/**
|
|
182
|
+
* Drive the one-and-only `TideCloak.init()` for front-channel mode and emit the
|
|
183
|
+
* resulting `ready` state. Split out of {@link initIAM} so the in-flight promise
|
|
184
|
+
* can be shared with concurrent callers.
|
|
185
|
+
* @private
|
|
186
|
+
* @param {Object} config
|
|
187
|
+
* @returns {Promise<boolean>}
|
|
188
|
+
*/
|
|
189
|
+
private _initFrontchannel;
|
|
180
190
|
/** @private */
|
|
181
191
|
private getTideCloakClient;
|
|
182
192
|
/**
|
|
@@ -200,6 +210,21 @@ declare class IAMService {
|
|
|
200
210
|
private _sanitizeReturnUrl;
|
|
201
211
|
/** @returns {Object} Loaded config */
|
|
202
212
|
getConfig(): any;
|
|
213
|
+
/**
|
|
214
|
+
* Whether initialization has already run to completion for the current mode.
|
|
215
|
+
*
|
|
216
|
+
* "Settled", not "started": while an init is still in flight this returns
|
|
217
|
+
* `false`, because the auth state it will produce does not exist yet and reading
|
|
218
|
+
* it would report a spurious logged-out state.
|
|
219
|
+
*
|
|
220
|
+
* Consumers that subscribe to events LATE (a React provider re-running its
|
|
221
|
+
* effect and re-subscribing, say) must use this to re-read the current auth
|
|
222
|
+
* state synchronously rather than waiting on a `ready` event that has already
|
|
223
|
+
* been emitted and is gone.
|
|
224
|
+
*
|
|
225
|
+
* @returns {boolean}
|
|
226
|
+
*/
|
|
227
|
+
isInitialized(): boolean;
|
|
203
228
|
/** @returns {boolean} Whether there's a valid token (or session in hybrid/native mode) */
|
|
204
229
|
isLoggedIn(): boolean;
|
|
205
230
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decision seam of `TideCloak.secureFetch`: given the caller's `Authorization`
|
|
3
|
+
* header (if any) and the access token this client currently holds, should we sign
|
|
4
|
+
* the request as our own - `Authorization: DPoP <token>` plus a `DPoP:` proof?
|
|
5
|
+
*
|
|
6
|
+
* Kept in its own dependency-free module so it can be unit-tested directly.
|
|
7
|
+
* `lib/tidecloak.js` pulls in `heimdall-tide`, which cannot be loaded under plain
|
|
8
|
+
* node ESM, so the predicate is unreachable from a test if it lives there - and
|
|
9
|
+
* this is precisely the seam where a mistake becomes an unexplainable `401`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @param {string|null|undefined} existingAuth - the caller's `Authorization` header.
|
|
13
|
+
* @param {string} ourToken - the access token this client currently holds.
|
|
14
|
+
* @returns {boolean} true -> attach `Authorization: DPoP <ourToken>` + a DPoP proof.
|
|
15
|
+
*
|
|
16
|
+
* Three cases:
|
|
17
|
+
*
|
|
18
|
+
* - NO `Authorization` header: YES, attach. "I set no Authorization, use your
|
|
19
|
+
* token" is the entire point of calling `secureFetch` rather than `fetch`, and
|
|
20
|
+
* it is exactly what `secureFetch`'s own stale-token warning tells callers to do
|
|
21
|
+
* ("omit the Authorization header entirely and let secureFetch attach it").
|
|
22
|
+
*
|
|
23
|
+
* This case used to be false, which sent the request as a plain, UNAUTHENTICATED
|
|
24
|
+
* fetch: no token, no proof, no credentials of any kind. A consumer that reads
|
|
25
|
+
* its token from an async state container and calls before that state has
|
|
26
|
+
* settled (React state that has not yet committed, say) would silently ship a
|
|
27
|
+
* credential-less admin request and get back a bare `401` naming none of this.
|
|
28
|
+
* That is the admin-API 401 this closes.
|
|
29
|
+
*
|
|
30
|
+
* - OUR bearer token: YES, attach - upgrade `Bearer` to `DPoP` + proof. Unchanged.
|
|
31
|
+
*
|
|
32
|
+
* - SOMEONE ELSE'S bearer token: NO. A third-party API call is a legitimate
|
|
33
|
+
* pass-through: it goes out verbatim and must never be handed one of our proofs.
|
|
34
|
+
* Unchanged. (A stale copy of one of ours lands here too, and is warned about
|
|
35
|
+
* once by the caller.)
|
|
36
|
+
*/
|
|
37
|
+
export function shouldAttachDpopProof(existingAuth: string | null | undefined, ourToken: string): boolean;
|
|
@@ -11,6 +11,22 @@ export class AdminAPI {
|
|
|
11
11
|
* Get the current realm
|
|
12
12
|
*/
|
|
13
13
|
getRealm(): any;
|
|
14
|
+
/**
|
|
15
|
+
* SAFETY INVARIANT: never send this SDK's own access token as a plain `Bearer`.
|
|
16
|
+
*
|
|
17
|
+
* When DPoP is enabled the issued token is sender-constrained (`typ: DPoP`,
|
|
18
|
+
* `cnf.jkt`), and RFC 9449 says a bound token presented as a plain `Bearer` is
|
|
19
|
+
* invalid - the server answers a bare `401` explaining nothing. So every admin
|
|
20
|
+
* call goes through `IAMService.secureFetch`, which:
|
|
21
|
+
*
|
|
22
|
+
* - with a DPoP provider: upgrades `Authorization: Bearer <our token>` to
|
|
23
|
+
* `Authorization: DPoP <our token>` + a `DPoP:` proof (and handles nonces);
|
|
24
|
+
* - with no DPoP provider: falls through to a plain `fetch` - byte-for-byte
|
|
25
|
+
* what this method did before.
|
|
26
|
+
*
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
private _authFetch;
|
|
14
30
|
/**
|
|
15
31
|
* Make an authenticated fetch request
|
|
16
32
|
*/
|
|
@@ -145,6 +145,7 @@ declare class IAMService {
|
|
|
145
145
|
_nativeEncryptionCallbackUnsubscribe: any;
|
|
146
146
|
_pendingEncryptionRequests: Map<any, any>;
|
|
147
147
|
_dpopProvider: import("../lib/tidecloak-dpop.js").DPoPSignatureProvider | null;
|
|
148
|
+
_frontchannelInitPromise: Promise<boolean> | null;
|
|
148
149
|
/**
|
|
149
150
|
* Register an event listener.
|
|
150
151
|
* @param {'ready'|'initError'|'authSuccess'|'authError'|'authRefreshSuccess'|'authRefreshError'|'logout'|'tokenExpired'} event
|
|
@@ -185,6 +186,15 @@ declare class IAMService {
|
|
|
185
186
|
* @returns {Promise<boolean>} true if authenticated, else false.
|
|
186
187
|
*/
|
|
187
188
|
initIAM(config: Object, onReady?: Function): Promise<boolean>;
|
|
189
|
+
/**
|
|
190
|
+
* Drive the one-and-only `TideCloak.init()` for front-channel mode and emit the
|
|
191
|
+
* resulting `ready` state. Split out of {@link initIAM} so the in-flight promise
|
|
192
|
+
* can be shared with concurrent callers.
|
|
193
|
+
* @private
|
|
194
|
+
* @param {Object} config
|
|
195
|
+
* @returns {Promise<boolean>}
|
|
196
|
+
*/
|
|
197
|
+
private _initFrontchannel;
|
|
188
198
|
/** @private */
|
|
189
199
|
private getTideCloakClient;
|
|
190
200
|
/**
|
|
@@ -208,6 +218,21 @@ declare class IAMService {
|
|
|
208
218
|
private _sanitizeReturnUrl;
|
|
209
219
|
/** @returns {Object} Loaded config */
|
|
210
220
|
getConfig(): Object;
|
|
221
|
+
/**
|
|
222
|
+
* Whether initialization has already run to completion for the current mode.
|
|
223
|
+
*
|
|
224
|
+
* "Settled", not "started": while an init is still in flight this returns
|
|
225
|
+
* `false`, because the auth state it will produce does not exist yet and reading
|
|
226
|
+
* it would report a spurious logged-out state.
|
|
227
|
+
*
|
|
228
|
+
* Consumers that subscribe to events LATE (a React provider re-running its
|
|
229
|
+
* effect and re-subscribing, say) must use this to re-read the current auth
|
|
230
|
+
* state synchronously rather than waiting on a `ready` event that has already
|
|
231
|
+
* been emitted and is gone.
|
|
232
|
+
*
|
|
233
|
+
* @returns {boolean}
|
|
234
|
+
*/
|
|
235
|
+
isInitialized(): boolean;
|
|
211
236
|
/** @returns {boolean} Whether there's a valid token (or session in hybrid/native mode) */
|
|
212
237
|
isLoggedIn(): boolean;
|
|
213
238
|
/**
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the options object handed to `TideCloak.init()` from a caller's
|
|
3
|
+
* resolved TideCloak config.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is an ALLOWLIST, deliberately
|
|
6
|
+
*
|
|
7
|
+
* `IAMService.initIAM` has always built these options as an allowlist: it reads
|
|
8
|
+
* a handful of keys off the config and drops everything else on the floor. The
|
|
9
|
+
* config object is NOT a `KeycloakInitOptions` bag - it is an adapter/config
|
|
10
|
+
* snapshot (`tidecloak.json` + Tide fields + app-level OIDC hints), and callers
|
|
11
|
+
* legitimately put keys in it that are meaningful to `IAMService` (or to their
|
|
12
|
+
* own code) but that must NOT be handed to `init()`.
|
|
13
|
+
*
|
|
14
|
+
* PR #91 widened this allowlist to also forward `redirectUri`,
|
|
15
|
+
* `silentCheckSsoFallback`, `silentCheckSsoTimeout`, `scope` and a
|
|
16
|
+
* caller-supplied `pkceMethod`, on the reasoning that "each is inert unless the
|
|
17
|
+
* caller sets it". That reasoning is wrong in one important direction: consumers
|
|
18
|
+
* had been shipping configs with these keys set for as long as the keys were
|
|
19
|
+
* being SWALLOWED, so their behaviour was pinned to the SDK's defaults, not to
|
|
20
|
+
* the value in their config. Forwarding them is therefore a silent behaviour
|
|
21
|
+
* change for every existing consumer - the exact opposite of inert.
|
|
22
|
+
*
|
|
23
|
+
* `scope` is the sharpest edge: forwarding it rewrites the authorization
|
|
24
|
+
* request's scope, which changes the audience/roles on the issued access token
|
|
25
|
+
* and can strip a consumer's admin-API access.
|
|
26
|
+
*
|
|
27
|
+
* So: forward ONLY what the SDK genuinely needs, and keep the previously
|
|
28
|
+
* defaulted behaviour byte-for-byte. Any option that a consumer should be able
|
|
29
|
+
* to set must be added here CONSCIOUSLY, with its prior default preserved.
|
|
30
|
+
*
|
|
31
|
+
* ### Forwarded
|
|
32
|
+
* - `setupRequestEnclave` - passed through from the `initIAM` argument.
|
|
33
|
+
* - `onLoad` - always `"check-sso"`. NOT taken from config: callers (e.g. the
|
|
34
|
+
* admin console) set `onLoad: "login-required"`, and honouring it would send
|
|
35
|
+
* `#processInit` down the full-redirect branch and never run silent check-sso
|
|
36
|
+
* at all. That is a separate product decision.
|
|
37
|
+
* - `silentCheckSsoRedirectUri` - the resolved URI (see ./silentCheckSso.js).
|
|
38
|
+
* This is the ONE option PR #91 needed to make caller-supplied, and it stays.
|
|
39
|
+
* `undefined` => TideCloak skips silent check-sso and falls back to an
|
|
40
|
+
* interactive login rather than emitting a URI that would 400.
|
|
41
|
+
* - `pkceMethod` - always `"S256"`, as it was before PR #91.
|
|
42
|
+
* - `useDPoP` / `checkLoginIframe` - the two options `initIAM` has always
|
|
43
|
+
* forwarded. `useDPoP` is forwarded ONLY when the caller actually set it, and
|
|
44
|
+
* VERBATIM. DPoP is opt-in: nothing upstream may default it on, because
|
|
45
|
+
* enabling it appends `dpop_jkt` to the authorization request and the realm
|
|
46
|
+
* then issues a `cnf.jkt`-BOUND token, which a plain-Bearer consumer cannot
|
|
47
|
+
* use (RFC 9449) - a bare `401` with no explanation. Pinned in
|
|
48
|
+
* test/bootstrapDpop.test.js.
|
|
49
|
+
*
|
|
50
|
+
* ### Deliberately NOT forwarded (regression-tested in test/initOptions.test.js)
|
|
51
|
+
* `scope`, `redirectUri`, `silentCheckSsoFallback`, `silentCheckSsoTimeout`.
|
|
52
|
+
* Consumers depend on the SDK's defaults for these. `redirectUri` in particular
|
|
53
|
+
* is still READ from config by `IAMService.doLogin` / `doLogout` and is used as
|
|
54
|
+
* a derivation source for `silentCheckSsoRedirectUri`, so a sub-path-hosted app
|
|
55
|
+
* still gets a correct login redirect - it just does not have `kc.redirectUri`
|
|
56
|
+
* overridden at init time.
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} args
|
|
59
|
+
* @param {Object|null|undefined} args.config Resolved TideCloak config.
|
|
60
|
+
* @param {boolean} args.setupRequestEnclave
|
|
61
|
+
* @param {string|undefined} args.silentCheckSsoRedirectUri Resolved URI, or
|
|
62
|
+
* `undefined` when no correct URI could be derived.
|
|
63
|
+
* @returns {Object} Options for `TideCloak.init()`.
|
|
64
|
+
*/
|
|
65
|
+
export function buildInitOptions({ config, setupRequestEnclave, silentCheckSsoRedirectUri, }: {
|
|
66
|
+
config: Object | null | undefined;
|
|
67
|
+
setupRequestEnclave: boolean;
|
|
68
|
+
silentCheckSsoRedirectUri: string | undefined;
|
|
69
|
+
}): Object;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the options object handed to `TideCloak.init()` from a caller's
|
|
3
|
+
* resolved TideCloak config.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is an ALLOWLIST, deliberately
|
|
6
|
+
*
|
|
7
|
+
* `IAMService.initIAM` has always built these options as an allowlist: it reads
|
|
8
|
+
* a handful of keys off the config and drops everything else on the floor. The
|
|
9
|
+
* config object is NOT a `KeycloakInitOptions` bag - it is an adapter/config
|
|
10
|
+
* snapshot (`tidecloak.json` + Tide fields + app-level OIDC hints), and callers
|
|
11
|
+
* legitimately put keys in it that are meaningful to `IAMService` (or to their
|
|
12
|
+
* own code) but that must NOT be handed to `init()`.
|
|
13
|
+
*
|
|
14
|
+
* PR #91 widened this allowlist to also forward `redirectUri`,
|
|
15
|
+
* `silentCheckSsoFallback`, `silentCheckSsoTimeout`, `scope` and a
|
|
16
|
+
* caller-supplied `pkceMethod`, on the reasoning that "each is inert unless the
|
|
17
|
+
* caller sets it". That reasoning is wrong in one important direction: consumers
|
|
18
|
+
* had been shipping configs with these keys set for as long as the keys were
|
|
19
|
+
* being SWALLOWED, so their behaviour was pinned to the SDK's defaults, not to
|
|
20
|
+
* the value in their config. Forwarding them is therefore a silent behaviour
|
|
21
|
+
* change for every existing consumer - the exact opposite of inert.
|
|
22
|
+
*
|
|
23
|
+
* `scope` is the sharpest edge: forwarding it rewrites the authorization
|
|
24
|
+
* request's scope, which changes the audience/roles on the issued access token
|
|
25
|
+
* and can strip a consumer's admin-API access.
|
|
26
|
+
*
|
|
27
|
+
* So: forward ONLY what the SDK genuinely needs, and keep the previously
|
|
28
|
+
* defaulted behaviour byte-for-byte. Any option that a consumer should be able
|
|
29
|
+
* to set must be added here CONSCIOUSLY, with its prior default preserved.
|
|
30
|
+
*
|
|
31
|
+
* ### Forwarded
|
|
32
|
+
* - `setupRequestEnclave` - passed through from the `initIAM` argument.
|
|
33
|
+
* - `onLoad` - always `"check-sso"`. NOT taken from config: callers (e.g. the
|
|
34
|
+
* admin console) set `onLoad: "login-required"`, and honouring it would send
|
|
35
|
+
* `#processInit` down the full-redirect branch and never run silent check-sso
|
|
36
|
+
* at all. That is a separate product decision.
|
|
37
|
+
* - `silentCheckSsoRedirectUri` - the resolved URI (see ./silentCheckSso.js).
|
|
38
|
+
* This is the ONE option PR #91 needed to make caller-supplied, and it stays.
|
|
39
|
+
* `undefined` => TideCloak skips silent check-sso and falls back to an
|
|
40
|
+
* interactive login rather than emitting a URI that would 400.
|
|
41
|
+
* - `pkceMethod` - always `"S256"`, as it was before PR #91.
|
|
42
|
+
* - `useDPoP` / `checkLoginIframe` - the two options `initIAM` has always
|
|
43
|
+
* forwarded. `useDPoP` is forwarded ONLY when the caller actually set it, and
|
|
44
|
+
* VERBATIM. DPoP is opt-in: nothing upstream may default it on, because
|
|
45
|
+
* enabling it appends `dpop_jkt` to the authorization request and the realm
|
|
46
|
+
* then issues a `cnf.jkt`-BOUND token, which a plain-Bearer consumer cannot
|
|
47
|
+
* use (RFC 9449) - a bare `401` with no explanation. Pinned in
|
|
48
|
+
* test/bootstrapDpop.test.js.
|
|
49
|
+
*
|
|
50
|
+
* ### Deliberately NOT forwarded (regression-tested in test/initOptions.test.js)
|
|
51
|
+
* `scope`, `redirectUri`, `silentCheckSsoFallback`, `silentCheckSsoTimeout`.
|
|
52
|
+
* Consumers depend on the SDK's defaults for these. `redirectUri` in particular
|
|
53
|
+
* is still READ from config by `IAMService.doLogin` / `doLogout` and is used as
|
|
54
|
+
* a derivation source for `silentCheckSsoRedirectUri`, so a sub-path-hosted app
|
|
55
|
+
* still gets a correct login redirect - it just does not have `kc.redirectUri`
|
|
56
|
+
* overridden at init time.
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} args
|
|
59
|
+
* @param {Object|null|undefined} args.config Resolved TideCloak config.
|
|
60
|
+
* @param {boolean} args.setupRequestEnclave
|
|
61
|
+
* @param {string|undefined} args.silentCheckSsoRedirectUri Resolved URI, or
|
|
62
|
+
* `undefined` when no correct URI could be derived.
|
|
63
|
+
* @returns {Object} Options for `TideCloak.init()`.
|
|
64
|
+
*/
|
|
65
|
+
export function buildInitOptions({ config, setupRequestEnclave, silentCheckSsoRedirectUri, }: {
|
|
66
|
+
config: any | null | undefined;
|
|
67
|
+
setupRequestEnclave: boolean;
|
|
68
|
+
silentCheckSsoRedirectUri: string | undefined;
|
|
69
|
+
}): any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tidecloak/js",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.36-staging",
|
|
4
4
|
"description": "Vanilla JavaScript/browser SDK for TideCloak with an IAMService singleton and low-level client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cjs/src/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"./policy.css": "./dist/esm/src/policy.css"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test": "node --test test/*.test.js",
|
|
27
|
+
"test": "node --experimental-test-module-mocks --test test/*.test.js",
|
|
28
28
|
"postinstall": "node ./scripts/postinstall.cjs",
|
|
29
29
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
30
30
|
"build:esm": "tsc -p tsconfig.esm.json",
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"authorization"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"heimdall-tide": "0.13.
|
|
61
|
-
"@tideorg/js": "0.13.
|
|
60
|
+
"heimdall-tide": "0.13.36-staging",
|
|
61
|
+
"@tideorg/js": "0.13.36-staging"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@tidecloak/policy": "0.13.
|
|
64
|
+
"@tidecloak/policy": "0.13.36-staging"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@tidecloak/policy": {
|