@tidecloak/js 0.13.33 → 0.13.35-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/tidecloak.js +226 -44
- package/dist/cjs/lib/tidecloak.js.map +1 -1
- package/dist/cjs/src/IAMService.js +61 -5
- package/dist/cjs/src/IAMService.js.map +1 -1
- package/dist/cjs/src/utils/index.js +4 -1
- package/dist/cjs/src/utils/index.js.map +1 -1
- package/dist/cjs/src/utils/silentCheckSso.js +138 -0
- package/dist/cjs/src/utils/silentCheckSso.js.map +1 -0
- package/dist/esm/lib/tidecloak.js +226 -44
- package/dist/esm/lib/tidecloak.js.map +1 -1
- package/dist/esm/src/IAMService.js +62 -6
- 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/silentCheckSso.js +134 -0
- package/dist/esm/src/utils/silentCheckSso.js.map +1 -0
- package/dist/types/lib/tidecloak.d.ts +9 -0
- package/dist/types/src/utils/index.d.ts +1 -0
- package/dist/types/src/utils/silentCheckSso.d.ts +82 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/silentCheckSso.d.ts +82 -0
- package/package.json +5 -4
|
@@ -36,10 +36,18 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
36
36
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
37
37
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
38
38
|
};
|
|
39
|
-
var _TideCloak_instances, _TideCloak_refreshQueue, _TideCloak_adapter, _TideCloak_useNonce, _TideCloak_callbackStorage, _TideCloak_logInfo, _TideCloak_logWarn, _TideCloak_loginIframe, _TideCloak_dpopProvider, _TideCloak_config, _TideCloak_loadAdapter, _TideCloak_loadDefaultAdapter, _TideCloak_loadCordovaAdapter, _TideCloak_loadCordovaNativeAdapter, _TideCloak_loadConfig, _TideCloak_setupEndpoints, _TideCloak_loadOidcConfig, _TideCloak_setupOidcEndpoints, _TideCloak_getIssuerUrl, _TideCloak_check3pCookiesSupported, _TideCloak_processInit, _TideCloak_setupCheckLoginIframe, _TideCloak_checkLoginIframe, _TideCloak_checkSsoSilently, _TideCloak_parseCallback, _TideCloak_parseCallbackUrl, _TideCloak_parseCallbackParams, _TideCloak_processCallback, _TideCloak_scheduleCheckIframe, _TideCloak_fetchAccessToken, _TideCloak_fetchRefreshToken, _TideCloak_ensureRequestEnclaveOpen, _TideCloak_getVoucherUrl, _TideCloak_setToken, _TideCloak_getRealmUrl, _TideCloak_createLogger, _LocalStorage_instances, _LocalStorage_clearInvalidValues, _LocalStorage_clearAllValues, _LocalStorage_getStoredEntries, _LocalStorage_parseExpiry, _CookieStorage_instances, _CookieStorage_getCookie, _CookieStorage_setCookie, _CookieStorage_cookieExpiration;
|
|
39
|
+
var _TideCloak_instances, _TideCloak_refreshQueue, _TideCloak_adapter, _TideCloak_useNonce, _TideCloak_callbackStorage, _TideCloak_logInfo, _TideCloak_logWarn, _TideCloak_loginIframe, _TideCloak_dpopProvider, _TideCloak_config, _TideCloak_loadAdapter, _TideCloak_loadDefaultAdapter, _TideCloak_loadCordovaAdapter, _TideCloak_loadCordovaNativeAdapter, _TideCloak_loadConfig, _TideCloak_setupEndpoints, _TideCloak_loadOidcConfig, _TideCloak_setupOidcEndpoints, _TideCloak_getIssuerUrl, _TideCloak_check3pCookiesSupported, _TideCloak_processInit, _TideCloak_setupCheckLoginIframe, _TideCloak_checkLoginIframe, _TideCloak_checkSsoSilently, _TideCloak_parseCallback, _TideCloak_parseCallbackUrl, _TideCloak_parseCallbackParams, _TideCloak_processCallback, _TideCloak_scheduleCheckIframe, _TideCloak_consumePostLogoutMarker, _TideCloak_fetchAccessToken, _TideCloak_fetchRefreshToken, _TideCloak_ensureRequestEnclaveOpen, _TideCloak_reloginInteractively, _TideCloak_provideDokenOrRelogin, _TideCloak_getVoucherUrl, _TideCloak_setToken, _TideCloak_teardownEnclaves, _TideCloak_getRealmUrl, _TideCloak_createLogger, _LocalStorage_instances, _LocalStorage_clearInvalidValues, _LocalStorage_clearAllValues, _LocalStorage_getStoredEntries, _LocalStorage_parseExpiry, _CookieStorage_instances, _CookieStorage_getCookie, _CookieStorage_setCookie, _CookieStorage_cookieExpiration;
|
|
40
40
|
// MODIFIED: Added dependency to external Tide helper libraries.
|
|
41
41
|
import { RequestEnclave, ApprovalEnclave, ApprovalEnclaveNew } from "heimdall-tide";
|
|
42
42
|
const CONTENT_TYPE_JSON = 'application/json';
|
|
43
|
+
// Post-logout marker. After an explicit logout there is no session to silently
|
|
44
|
+
// resume, so the immediately-following init() must skip the racy silent
|
|
45
|
+
// check-sso (whose hidden iframe can net::ERR_ABORTED and wedge the app) and go
|
|
46
|
+
// straight to interactive login. `logout()` stamps this durable localStorage
|
|
47
|
+
// marker (survives the logout redirect round-trip); init() consumes it exactly
|
|
48
|
+
// once and forces an interactive login when present + fresh.
|
|
49
|
+
const POST_LOGOUT_MARKER_KEY = 'tide-post-logout';
|
|
50
|
+
const POST_LOGOUT_MARKER_TTL_MS = 60000;
|
|
43
51
|
/**
|
|
44
52
|
* @typedef {Object} Endpoints
|
|
45
53
|
* @property {() => string} authorize
|
|
@@ -107,6 +115,15 @@ class TideCloak {
|
|
|
107
115
|
this.timeSkew = null;
|
|
108
116
|
/** @type {boolean} */
|
|
109
117
|
this.silentCheckSsoFallback = true;
|
|
118
|
+
/**
|
|
119
|
+
* Max time (ms) to wait for the hidden silent-check-sso iframe to post back
|
|
120
|
+
* before treating the attempt as "not authenticated". Guards against the
|
|
121
|
+
* post-logout wedge where the check-sso (or the Tide enclave it spins up)
|
|
122
|
+
* never responds and init() would otherwise hang forever. Overridable via
|
|
123
|
+
* initOptions.silentCheckSsoTimeout.
|
|
124
|
+
* @type {number}
|
|
125
|
+
*/
|
|
126
|
+
this.silentCheckSsoTimeout = 10000;
|
|
110
127
|
/** @type {TideCloakPkceMethod} */
|
|
111
128
|
this.pkceMethod = 'S256';
|
|
112
129
|
this.enableLogging = false;
|
|
@@ -185,6 +202,9 @@ class TideCloak {
|
|
|
185
202
|
if (typeof initOptions.silentCheckSsoFallback === 'boolean') {
|
|
186
203
|
this.silentCheckSsoFallback = initOptions.silentCheckSsoFallback;
|
|
187
204
|
}
|
|
205
|
+
if (typeof initOptions.silentCheckSsoTimeout === 'number' && initOptions.silentCheckSsoTimeout > 0) {
|
|
206
|
+
this.silentCheckSsoTimeout = initOptions.silentCheckSsoTimeout;
|
|
207
|
+
}
|
|
188
208
|
if (typeof initOptions.pkceMethod !== 'undefined') {
|
|
189
209
|
if (initOptions.pkceMethod !== 'S256' && initOptions.pkceMethod !== false) {
|
|
190
210
|
throw new TypeError(`Invalid value for pkceMethod', expected 'S256' or false but got ${initOptions.pkceMethod}.`);
|
|
@@ -231,9 +251,29 @@ class TideCloak {
|
|
|
231
251
|
await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_check3pCookiesSupported).call(this);
|
|
232
252
|
await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processInit).call(this, initOptions);
|
|
233
253
|
(_c = this.onReady) === null || _c === void 0 ? void 0 : _c.call(this, this.authenticated);
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
254
|
+
// Do NOT eagerly open the Tide RequestEnclave during init().
|
|
255
|
+
//
|
|
256
|
+
// On a post-logout silent re-auth, the doken returned by the silent
|
|
257
|
+
// check-sso can be STALE: its session key no longer matches the session the
|
|
258
|
+
// hidden enclave cached from the previous login. Opening the hidden enclave
|
|
259
|
+
// here makes it sit "waiting for doken refresh" forever - a refresh that
|
|
260
|
+
// never comes because no fresh doken exists - which is exactly the
|
|
261
|
+
// "[ENCLAVE] Received init but waiting for doken refresh" ... hang that
|
|
262
|
+
// wedges the SPA on "Signing you in..." on the second login. (The enclave
|
|
263
|
+
// silently WAITS rather than emitting an error, so heimdall's own
|
|
264
|
+
// requireReloginCallback recovery never fires.)
|
|
265
|
+
//
|
|
266
|
+
// Instead we DEFER enclave setup: it is created lazily on the first real
|
|
267
|
+
// Tide operation (encrypt / decrypt / approve / signDpopApproval - each
|
|
268
|
+
// calls initRequestEnclave()), by which point a fresh interactive login has
|
|
269
|
+
// minted a current doken and the enclave can complete its handshake. We
|
|
270
|
+
// still register the user-gesture listener so the popup fallback can open
|
|
271
|
+
// once an enclave has actually been created; #ensureRequestEnclaveOpen
|
|
272
|
+
// no-ops while there is no enclave or no doken.
|
|
273
|
+
if (initOptions.setupRequestEnclave) {
|
|
274
|
+
if (this.doken) {
|
|
275
|
+
__classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDECLOAK] Deferring Tide RequestEnclave setup until first use (not opening during init to avoid a stale-doken handshake stall on silent re-auth).');
|
|
276
|
+
}
|
|
237
277
|
// to get around popups requiring user gestures
|
|
238
278
|
document.addEventListener('click', () => {
|
|
239
279
|
__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_ensureRequestEnclaveOpen).call(this);
|
|
@@ -337,6 +377,16 @@ class TideCloak {
|
|
|
337
377
|
this.logout = async (options) => {
|
|
338
378
|
var _a;
|
|
339
379
|
await ((_a = __classPrivateFieldGet(this, _TideCloak_dpopProvider, "f")) === null || _a === void 0 ? void 0 : _a.flush());
|
|
380
|
+
// Stamp the post-logout marker so the next init() forces interactive login
|
|
381
|
+
// instead of running the racy silent check-sso. See POST_LOGOUT_MARKER_KEY.
|
|
382
|
+
try {
|
|
383
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
384
|
+
window.localStorage.setItem(POST_LOGOUT_MARKER_KEY, Date.now().toString());
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
__classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] could not set marker: ' + (e instanceof Error ? e.message : String(e)));
|
|
389
|
+
}
|
|
340
390
|
return __classPrivateFieldGet(this, _TideCloak_adapter, "f").logout(options);
|
|
341
391
|
};
|
|
342
392
|
/**
|
|
@@ -625,19 +675,8 @@ class TideCloak {
|
|
|
625
675
|
isRunningLocal: new URL(__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_getVoucherUrl).call(this)).hostname === "localhost"
|
|
626
676
|
}).init({
|
|
627
677
|
doken: this.doken,
|
|
628
|
-
dokenRefreshCallback: async () =>
|
|
629
|
-
|
|
630
|
-
if (!this.doken)
|
|
631
|
-
throw new Error('[TIDECLOAK] No doken found');
|
|
632
|
-
return this.doken;
|
|
633
|
-
},
|
|
634
|
-
requireReloginCallback: async () => {
|
|
635
|
-
await this.login({
|
|
636
|
-
idpHint: 'tide',
|
|
637
|
-
prompt: 'login',
|
|
638
|
-
redirectUri: window.location.href
|
|
639
|
-
});
|
|
640
|
-
}
|
|
678
|
+
dokenRefreshCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_provideDokenOrRelogin).call(this),
|
|
679
|
+
requireReloginCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this)
|
|
641
680
|
});
|
|
642
681
|
}
|
|
643
682
|
}
|
|
@@ -659,19 +698,8 @@ class TideCloak {
|
|
|
659
698
|
backgroundUrl: __classPrivateFieldGet(this, _TideCloak_config, "f")['backgroundUrl'],
|
|
660
699
|
logoUrl: __classPrivateFieldGet(this, _TideCloak_config, "f")['logoUrl'],
|
|
661
700
|
doken: this.doken,
|
|
662
|
-
dokenRefreshCallback: async () =>
|
|
663
|
-
|
|
664
|
-
if (!this.doken)
|
|
665
|
-
throw new Error('[TIDECLOAK] No doken found');
|
|
666
|
-
return this.doken;
|
|
667
|
-
},
|
|
668
|
-
requireReloginCallback: async () => {
|
|
669
|
-
await this.login({
|
|
670
|
-
idpHint: 'tide',
|
|
671
|
-
prompt: 'login',
|
|
672
|
-
redirectUri: window.location.href
|
|
673
|
-
});
|
|
674
|
-
}
|
|
701
|
+
dokenRefreshCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_provideDokenOrRelogin).call(this),
|
|
702
|
+
requireReloginCallback: async () => __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this)
|
|
675
703
|
});
|
|
676
704
|
}
|
|
677
705
|
}
|
|
@@ -1402,6 +1430,15 @@ async function _TideCloak_processInit(initOptions) {
|
|
|
1402
1430
|
await this.login(options);
|
|
1403
1431
|
};
|
|
1404
1432
|
const onLoad = async () => {
|
|
1433
|
+
// Post-logout deterministic path: if the previous logout stamped a fresh
|
|
1434
|
+
// marker, skip silent check-sso entirely and go straight to interactive
|
|
1435
|
+
// login. There is no session to silently resume after an explicit logout,
|
|
1436
|
+
// and the silent path is exactly where the ERR_ABORTED wedge lives.
|
|
1437
|
+
if (__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_consumePostLogoutMarker).call(this)) {
|
|
1438
|
+
__classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDE-POSTLOGOUT] marker honored in init(); forcing interactive login, bypassing silent check-sso');
|
|
1439
|
+
await doLogin(true);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1405
1442
|
switch (initOptions.onLoad) {
|
|
1406
1443
|
case 'check-sso':
|
|
1407
1444
|
if (__classPrivateFieldGet(this, _TideCloak_loginIframe, "f").enable) {
|
|
@@ -1533,13 +1570,38 @@ async function _TideCloak_checkLoginIframe() {
|
|
|
1533
1570
|
*/
|
|
1534
1571
|
async function _TideCloak_checkSsoSilently() {
|
|
1535
1572
|
const iframe = document.createElement('iframe');
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1573
|
+
return await new Promise((resolve) => {
|
|
1574
|
+
let settled = false;
|
|
1575
|
+
/** @type {ReturnType<typeof setTimeout>=} */
|
|
1576
|
+
let timer;
|
|
1577
|
+
/** @type {ReturnType<typeof setTimeout>=} */
|
|
1578
|
+
let loadGraceTimer;
|
|
1579
|
+
const cleanup = () => {
|
|
1580
|
+
if (timer)
|
|
1581
|
+
clearTimeout(timer);
|
|
1582
|
+
if (loadGraceTimer)
|
|
1583
|
+
clearTimeout(loadGraceTimer);
|
|
1584
|
+
window.removeEventListener('message', messageCallback);
|
|
1585
|
+
iframe.removeEventListener('error', onIframeError);
|
|
1586
|
+
iframe.removeEventListener('load', onIframeLoad);
|
|
1587
|
+
if (iframe.parentNode)
|
|
1588
|
+
document.body.removeChild(iframe);
|
|
1589
|
+
};
|
|
1590
|
+
// Every NON-authenticating terminal outcome funnels through here exactly
|
|
1591
|
+
// once: onerror / onabort / load-without-message / timeout /
|
|
1592
|
+
// createLoginUrl-failure. Resolving (never rejecting) as NOT-authenticated
|
|
1593
|
+
// lets init() complete cleanly so the app falls through to interactive
|
|
1594
|
+
// login. This is what makes the silent path structurally un-hangable: no
|
|
1595
|
+
// matter which DOM event (or none) the aborted iframe produces, one of
|
|
1596
|
+
// these fires. `via` is logged so QA can prove which path was taken.
|
|
1597
|
+
const settleNotAuthenticated = (via) => {
|
|
1598
|
+
if (settled)
|
|
1599
|
+
return;
|
|
1600
|
+
settled = true;
|
|
1601
|
+
__classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-SILENTSSO] terminal=' + via + ' -> not-authenticated (authenticated=' + this.authenticated + ')');
|
|
1602
|
+
cleanup();
|
|
1603
|
+
resolve();
|
|
1604
|
+
};
|
|
1543
1605
|
/**
|
|
1544
1606
|
* @param {MessageEvent} event
|
|
1545
1607
|
*/
|
|
@@ -1547,18 +1609,63 @@ async function _TideCloak_checkSsoSilently() {
|
|
|
1547
1609
|
if (event.origin !== window.location.origin || iframe.contentWindow !== event.source) {
|
|
1548
1610
|
return;
|
|
1549
1611
|
}
|
|
1550
|
-
|
|
1612
|
+
if (settled)
|
|
1613
|
+
return;
|
|
1614
|
+
settled = true;
|
|
1615
|
+
cleanup();
|
|
1551
1616
|
try {
|
|
1617
|
+
const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallback).call(this, event.data);
|
|
1552
1618
|
await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_processCallback).call(this, oauth);
|
|
1619
|
+
__classPrivateFieldGet(this, _TideCloak_logInfo, "f").call(this, '[TIDE-SILENTSSO] terminal=message -> processed (authenticated=' + this.authenticated + ')');
|
|
1553
1620
|
resolve();
|
|
1554
1621
|
}
|
|
1555
1622
|
catch (error) {
|
|
1556
|
-
reject
|
|
1623
|
+
// A failed silent callback must NOT reject: rejecting would surface as
|
|
1624
|
+
// an init() error / AuthWall wall instead of a clean fall-through to
|
|
1625
|
+
// interactive login. Resolve as not-authenticated.
|
|
1626
|
+
__classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-SILENTSSO] terminal=message-error -> not-authenticated: ' + (error instanceof Error ? error.message : String(error)));
|
|
1627
|
+
resolve();
|
|
1557
1628
|
}
|
|
1558
|
-
document.body.removeChild(iframe);
|
|
1559
|
-
window.removeEventListener('message', messageCallback);
|
|
1560
1629
|
};
|
|
1630
|
+
const onIframeError = () => settleNotAuthenticated('onerror');
|
|
1631
|
+
const onIframeLoad = () => {
|
|
1632
|
+
// The iframe finished navigating. On the happy path it landed on the
|
|
1633
|
+
// real silent-check-sso.html and `messageCallback` settles first. If it
|
|
1634
|
+
// loaded an error page, or a cross-origin KC page that never posts, or
|
|
1635
|
+
// an aborted navigation that still fired `load`, no message arrives - so
|
|
1636
|
+
// arm a short grace timer that settles NOT-authenticated well before the
|
|
1637
|
+
// outer ceiling.
|
|
1638
|
+
if (settled)
|
|
1639
|
+
return;
|
|
1640
|
+
const grace = Math.max(250, Math.min(2000, Math.floor(this.silentCheckSsoTimeout / 4)));
|
|
1641
|
+
if (loadGraceTimer)
|
|
1642
|
+
clearTimeout(loadGraceTimer);
|
|
1643
|
+
loadGraceTimer = setTimeout(() => settleNotAuthenticated('load-no-message'), grace);
|
|
1644
|
+
};
|
|
1645
|
+
// Wire ALL listeners before the navigation starts so no immediate event is
|
|
1646
|
+
// missed. `error` covers the browsers that surface a failed iframe
|
|
1647
|
+
// navigation as an error/abort event; `load` + the grace timer cover the
|
|
1648
|
+
// ones that fire load on an error document; the outer `timer` covers
|
|
1649
|
+
// net::ERR_ABORTED that produces NO DOM event at all.
|
|
1561
1650
|
window.addEventListener('message', messageCallback);
|
|
1651
|
+
iframe.addEventListener('error', onIframeError);
|
|
1652
|
+
iframe.addEventListener('load', onIframeLoad);
|
|
1653
|
+
// Hard ceiling. Created synchronously (NOT after an awaited createLoginUrl)
|
|
1654
|
+
// so it is always armed regardless of what the iframe navigation does.
|
|
1655
|
+
timer = setTimeout(() => settleNotAuthenticated('timeout'), this.silentCheckSsoTimeout);
|
|
1656
|
+
// Build the URL and start the navigation. Failure here must still settle
|
|
1657
|
+
// (not reject) so init() stays deterministic.
|
|
1658
|
+
this.createLoginUrl({ prompt: 'none', redirectUri: this.silentCheckSsoRedirectUri })
|
|
1659
|
+
.then((src) => {
|
|
1660
|
+
if (settled)
|
|
1661
|
+
return;
|
|
1662
|
+
iframe.setAttribute('src', src);
|
|
1663
|
+
iframe.setAttribute('sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin');
|
|
1664
|
+
iframe.setAttribute('title', 'keycloak-silent-check-sso');
|
|
1665
|
+
iframe.style.display = 'none';
|
|
1666
|
+
document.body.appendChild(iframe);
|
|
1667
|
+
})
|
|
1668
|
+
.catch(() => settleNotAuthenticated('createLoginUrl-error'));
|
|
1562
1669
|
});
|
|
1563
1670
|
}, _TideCloak_parseCallback = function _TideCloak_parseCallback(url) {
|
|
1564
1671
|
const oauth = __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_parseCallbackUrl).call(this, url);
|
|
@@ -1693,6 +1800,25 @@ async function _TideCloak_checkSsoSilently() {
|
|
|
1693
1800
|
await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_scheduleCheckIframe).call(this);
|
|
1694
1801
|
}
|
|
1695
1802
|
}
|
|
1803
|
+
}, _TideCloak_consumePostLogoutMarker = function _TideCloak_consumePostLogoutMarker() {
|
|
1804
|
+
try {
|
|
1805
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
1806
|
+
return false;
|
|
1807
|
+
const raw = window.localStorage.getItem(POST_LOGOUT_MARKER_KEY);
|
|
1808
|
+
if (!raw)
|
|
1809
|
+
return false;
|
|
1810
|
+
window.localStorage.removeItem(POST_LOGOUT_MARKER_KEY);
|
|
1811
|
+
const ts = Number(raw);
|
|
1812
|
+
const fresh = Number.isFinite(ts) && (Date.now() - ts) < POST_LOGOUT_MARKER_TTL_MS;
|
|
1813
|
+
if (!fresh) {
|
|
1814
|
+
__classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] marker present but stale (age > ' + POST_LOGOUT_MARKER_TTL_MS + 'ms); ignoring');
|
|
1815
|
+
}
|
|
1816
|
+
return fresh;
|
|
1817
|
+
}
|
|
1818
|
+
catch (e) {
|
|
1819
|
+
__classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDE-POSTLOGOUT] could not read marker: ' + (e instanceof Error ? e.message : String(e)));
|
|
1820
|
+
return false;
|
|
1821
|
+
}
|
|
1696
1822
|
}, _TideCloak_fetchAccessToken =
|
|
1697
1823
|
/**
|
|
1698
1824
|
* @typedef {Object} AccessTokenResponse The successful token response from the authorization server, based on the {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 OAuth 2.0 Authorization Framework specification}.
|
|
@@ -1800,9 +1926,42 @@ async function _TideCloak_fetchRefreshToken(url, refreshToken, clientId) {
|
|
|
1800
1926
|
throw error;
|
|
1801
1927
|
}
|
|
1802
1928
|
}, _TideCloak_ensureRequestEnclaveOpen = function _TideCloak_ensureRequestEnclaveOpen() {
|
|
1803
|
-
|
|
1929
|
+
// Never (re)open the enclave without a doken. Post-logout the doken is
|
|
1930
|
+
// flushed; reopening a dokenless hidden enclave wedges it with
|
|
1931
|
+
// "Expecting doken in request".
|
|
1932
|
+
if (!this.requestEnclave || !this.doken)
|
|
1804
1933
|
return;
|
|
1805
1934
|
this.requestEnclave.checkEnclaveOpen();
|
|
1935
|
+
}, _TideCloak_reloginInteractively =
|
|
1936
|
+
/**
|
|
1937
|
+
* Trigger a full interactive login redirect. Used as the recovery leg when a
|
|
1938
|
+
* silent flow cannot proceed (e.g. the enclave asks for a doken refresh but
|
|
1939
|
+
* the doken was flushed at logout). Navigates the whole page to Keycloak.
|
|
1940
|
+
* @returns {Promise<void>}
|
|
1941
|
+
*/
|
|
1942
|
+
async function _TideCloak_reloginInteractively() {
|
|
1943
|
+
await this.login({
|
|
1944
|
+
idpHint: 'tide',
|
|
1945
|
+
prompt: 'login',
|
|
1946
|
+
redirectUri: window.location.href
|
|
1947
|
+
});
|
|
1948
|
+
}, _TideCloak_provideDokenOrRelogin =
|
|
1949
|
+
/**
|
|
1950
|
+
* Provide the current doken to the enclave, or fall through to interactive
|
|
1951
|
+
* login when there is none (post-logout / flushed state). Throwing a bare
|
|
1952
|
+
* error here would leave the enclave (and whatever awaits its doken-refresh
|
|
1953
|
+
* completion) hanging; instead we redirect to a full interactive login, which
|
|
1954
|
+
* recovers cleanly.
|
|
1955
|
+
* @returns {Promise<string>}
|
|
1956
|
+
*/
|
|
1957
|
+
async function _TideCloak_provideDokenOrRelogin() {
|
|
1958
|
+
await this.ensureTokenReady();
|
|
1959
|
+
if (!this.doken) {
|
|
1960
|
+
__classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDECLOAK] Enclave requested a doken refresh but no doken is present (post-logout). Falling through to interactive login.');
|
|
1961
|
+
await __classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_reloginInteractively).call(this);
|
|
1962
|
+
throw new Error('[TIDECLOAK] No doken found - redirecting to interactive login');
|
|
1963
|
+
}
|
|
1964
|
+
return this.doken;
|
|
1806
1965
|
}, _TideCloak_getVoucherUrl = function _TideCloak_getVoucherUrl() {
|
|
1807
1966
|
if (!this.tokenParsed)
|
|
1808
1967
|
throw new Error('User authentication required to access voucher service');
|
|
@@ -1876,9 +2035,32 @@ async function _TideCloak_fetchRefreshToken(url, refreshToken, clientId) {
|
|
|
1876
2035
|
else {
|
|
1877
2036
|
delete this.doken;
|
|
1878
2037
|
delete this.dokenParsed;
|
|
1879
|
-
|
|
1880
|
-
|
|
2038
|
+
// No doken present (e.g. post-logout / flushed state). Do NOT push a
|
|
2039
|
+
// "doken refresh" into the enclave: refreshing with an undefined doken
|
|
2040
|
+
// wedges the hidden enclave ("Expecting doken in request",
|
|
2041
|
+
// TIDE-SWE-UNHANDLED) and blocks the silent re-auth that runs after
|
|
2042
|
+
// logout. Instead tear the stale enclave(s) down so (a) no dokenless
|
|
2043
|
+
// refresh is ever sent, (b) the persistent user-gesture listener can no
|
|
2044
|
+
// longer reopen a dokenless hidden enclave (#ensureRequestEnclaveOpen
|
|
2045
|
+
// no-ops once requestEnclave is cleared), and (c) the next authenticated
|
|
2046
|
+
// flow re-creates a fresh enclave from a valid doken via
|
|
2047
|
+
// initRequestEnclave(). The app is then free to fall through to an
|
|
2048
|
+
// interactive login, which recovers cleanly.
|
|
2049
|
+
__classPrivateFieldGet(this, _TideCloak_instances, "m", _TideCloak_teardownEnclaves).call(this);
|
|
2050
|
+
}
|
|
2051
|
+
}, _TideCloak_teardownEnclaves = function _TideCloak_teardownEnclaves() {
|
|
2052
|
+
for (const key of ['requestEnclave', 'approvalEnclave']) {
|
|
2053
|
+
const enclave = this[key];
|
|
2054
|
+
if (!enclave)
|
|
2055
|
+
continue;
|
|
2056
|
+
try {
|
|
2057
|
+
if (typeof enclave.close === 'function')
|
|
2058
|
+
enclave.close();
|
|
2059
|
+
}
|
|
2060
|
+
catch (error) {
|
|
2061
|
+
__classPrivateFieldGet(this, _TideCloak_logWarn, "f").call(this, '[TIDECLOAK] Failed to close ' + key + ': ' + (error instanceof Error ? error.message : error));
|
|
1881
2062
|
}
|
|
2063
|
+
this[key] = undefined;
|
|
1882
2064
|
}
|
|
1883
2065
|
}, _TideCloak_getRealmUrl = function _TideCloak_getRealmUrl() {
|
|
1884
2066
|
if (typeof this.authServerUrl === 'undefined') {
|