Skip to content

Commit b92dc8f

Browse files
committed
@W-22137898 Add session TTL management with auto-expiration UI updates
Introduce server-side token introspection on expiration, scheduled timeout for instant UI refresh when TTL expires, and sliding window extension on successful API calls. Fix dark mode contrast for the expired auth status indicator.
1 parent a30a2a1 commit b92dc8f

3 files changed

Lines changed: 298 additions & 9 deletions

File tree

scripts/portal_generator/assets/portal.js

Lines changed: 145 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ async function executeXOriginSource(sourceIdx, buttonEl) {
534534
});
535535

536536
var data = await resp.json();
537+
handleProxyResponse(data, fullUrl);
537538

538539
// Restore button
539540
if (buttonEl) {
@@ -1832,6 +1833,7 @@ function setAuthStatus(authenticated, message, authMethod) {
18321833
sessionStorage.removeItem('anypoint_auth_method');
18331834
sessionStorage.removeItem('anypoint_identity');
18341835
sessionStorage.removeItem('anypoint_token_expires_at');
1836+
stopTtlTimer();
18351837
}
18361838

18371839
updateAuthSummary();
@@ -1875,16 +1877,16 @@ async function loginBearer() {
18751877
sessionStorage.setItem('anypoint_token', body.access_token);
18761878
sessionStorage.setItem('anypoint_identity', username);
18771879

1878-
// Store token expiration time
1879-
if (body.expires_in) {
1880-
var expiresAt = Date.now() + (body.expires_in * 1000);
1881-
sessionStorage.setItem('anypoint_token_expires_at', expiresAt.toString());
1880+
var introspectResult = await introspectToken();
1881+
if (introspectResult && introspectResult.exp) {
1882+
setTokenExpiration(parseInt(introspectResult.exp, 10));
1883+
} else {
1884+
setTokenExpiration(Date.now() + _SESSION_TTL);
18821885
}
18831886

18841887
setAuthStatus(true, null, 'Bearer');
18851888
showAuthMessage('Login successful!', false);
18861889

1887-
// Update playground panels with any environment variables that may have been set
18881890
if (typeof updateAllPlaygroundPanelsFromEnvVars === 'function') {
18891891
updateAllPlaygroundPanelsFromEnvVars();
18901892
}
@@ -1925,16 +1927,20 @@ async function loginOAuth2() {
19251927
sessionStorage.setItem('anypoint_token', body.access_token);
19261928
sessionStorage.setItem('anypoint_identity', clientId);
19271929

1928-
// Store token expiration time
19291930
if (body.expires_in) {
1930-
var expiresAt = Date.now() + (body.expires_in * 1000);
1931-
sessionStorage.setItem('anypoint_token_expires_at', expiresAt.toString());
1931+
setTokenExpiration(Date.now() + (body.expires_in * 1000));
1932+
} else {
1933+
var introspectResult = await introspectToken();
1934+
if (introspectResult && introspectResult.exp) {
1935+
setTokenExpiration(parseInt(introspectResult.exp, 10));
1936+
} else {
1937+
setTokenExpiration(Date.now() + _SESSION_TTL);
1938+
}
19321939
}
19331940

19341941
setAuthStatus(true, null, 'OAuth2');
19351942
showAuthMessage('Token obtained successfully!', false);
19361943

1937-
// Update playground panels with any environment variables that may have been set
19381944
if (typeof updateAllPlaygroundPanelsFromEnvVars === 'function') {
19391945
updateAllPlaygroundPanelsFromEnvVars();
19401946
}
@@ -1952,6 +1958,128 @@ function getAuthHeaders() {
19521958
return {};
19531959
}
19541960

1961+
// ============================================================================
1962+
// Try It Out — Session TTL Management
1963+
// ============================================================================
1964+
1965+
var _ttlTimerId = null;
1966+
var _ttlExpirationTimerId = null;
1967+
var _TTL_CHECK_INTERVAL = 30000;
1968+
var _SESSION_TTL = 3600000;
1969+
1970+
function isAccountsUrl(url) {
1971+
try {
1972+
var path = new URL(url).pathname;
1973+
return path.startsWith('/accounts/') || path === '/accounts';
1974+
} catch (e) {
1975+
return url.indexOf('/accounts/') !== -1 || url.indexOf('/accounts') === url.length - 9;
1976+
}
1977+
}
1978+
1979+
async function introspectToken() {
1980+
var token = sessionStorage.getItem('anypoint_token');
1981+
if (!token) return null;
1982+
var serverBase = getSelectedBaseUrl();
1983+
try {
1984+
var resp = await fetch(PROXY_URL, {
1985+
method: 'POST',
1986+
headers: {'Content-Type': 'application/json'},
1987+
body: JSON.stringify({
1988+
method: 'POST',
1989+
url: serverBase + '/accounts/oauth2/introspect',
1990+
headers: {
1991+
'Content-Type': 'application/json',
1992+
'Authorization': 'Bearer ' + token
1993+
},
1994+
body: JSON.stringify({token: token, token_type_hint: 'access_token'})
1995+
})
1996+
});
1997+
var data = await resp.json();
1998+
if (data.error) return null;
1999+
var body = JSON.parse(data.body);
2000+
return body;
2001+
} catch (e) {
2002+
return null;
2003+
}
2004+
}
2005+
2006+
function setTokenExpiration(expMs) {
2007+
sessionStorage.setItem('anypoint_token_expires_at', String(expMs));
2008+
startTtlTimer();
2009+
scheduleExpirationCheck(expMs);
2010+
}
2011+
2012+
function scheduleExpirationCheck(expMs) {
2013+
if (_ttlExpirationTimerId !== null) {
2014+
clearTimeout(_ttlExpirationTimerId);
2015+
_ttlExpirationTimerId = null;
2016+
}
2017+
var delay = expMs - Date.now();
2018+
if (delay <= 0) {
2019+
checkTtlExpiration();
2020+
return;
2021+
}
2022+
_ttlExpirationTimerId = setTimeout(function() {
2023+
_ttlExpirationTimerId = null;
2024+
checkTtlExpiration();
2025+
}, delay);
2026+
}
2027+
2028+
function extendTokenExpiration() {
2029+
var token = sessionStorage.getItem('anypoint_token');
2030+
if (!token) return;
2031+
setTokenExpiration(Date.now() + _SESSION_TTL);
2032+
}
2033+
2034+
function markTokenExpired() {
2035+
sessionStorage.setItem('anypoint_token_expires_at', '0');
2036+
stopTtlTimer();
2037+
updateAuthSummary();
2038+
}
2039+
2040+
function startTtlTimer() {
2041+
stopTtlTimer();
2042+
_ttlTimerId = setInterval(checkTtlExpiration, _TTL_CHECK_INTERVAL);
2043+
}
2044+
2045+
function stopTtlTimer() {
2046+
if (_ttlTimerId !== null) {
2047+
clearInterval(_ttlTimerId);
2048+
_ttlTimerId = null;
2049+
}
2050+
if (_ttlExpirationTimerId !== null) {
2051+
clearTimeout(_ttlExpirationTimerId);
2052+
_ttlExpirationTimerId = null;
2053+
}
2054+
}
2055+
2056+
async function checkTtlExpiration() {
2057+
var token = sessionStorage.getItem('anypoint_token');
2058+
if (!token) {
2059+
stopTtlTimer();
2060+
return;
2061+
}
2062+
if (!isTokenExpired()) return;
2063+
2064+
var result = await introspectToken();
2065+
if (result && result.active === true && result.exp) {
2066+
setTokenExpiration(parseInt(result.exp, 10));
2067+
updateAuthSummary();
2068+
} else if (result && result.active === false) {
2069+
markTokenExpired();
2070+
}
2071+
}
2072+
2073+
function handleProxyResponse(data, requestUrl) {
2074+
if (data.status === 401) {
2075+
markTokenExpired();
2076+
return;
2077+
}
2078+
if (data.status >= 200 && data.status < 300 && requestUrl && !isAccountsUrl(requestUrl)) {
2079+
extendTokenExpiration();
2080+
}
2081+
}
2082+
19552083
// ============================================================================
19562084
// Try It Out — Environment Variables
19572085
// ============================================================================
@@ -2180,6 +2308,7 @@ async function loadXOriginValues(opId, paramName) {
21802308
});
21812309

21822310
var data = await resp.json();
2311+
handleProxyResponse(data, fullUrl);
21832312

21842313
if (btn) {
21852314
btn.disabled = false;
@@ -2380,6 +2509,7 @@ async function loadXOriginValuesForEnv(paramName) {
23802509
});
23812510

23822511
var data = await resp.json();
2512+
handleProxyResponse(data, fullUrl);
23832513

23842514
if (btn) {
23852515
btn.disabled = false;
@@ -3558,6 +3688,7 @@ async function sendRequest(opId, buttonEl) {
35583688
})
35593689
});
35603690
var data = await resp.json();
3691+
handleProxyResponse(data, fullUrl);
35613692

35623693
// Restore button
35633694
if (buttonEl) {
@@ -5500,6 +5631,7 @@ async function executePlaygroundStep(sid, buttonEl) {
55005631
});
55015632

55025633
var result = await resp.json();
5634+
handleProxyResponse(result, fullUrl);
55035635

55045636
// Restore button
55055637
if (buttonEl) {
@@ -5641,6 +5773,9 @@ function canProceedToNextStep(skillSlug, currentStepIndex) {
56415773
var authMethod = sessionStorage.getItem('anypoint_auth_method') || '';
56425774
if (token && authMethod) {
56435775
setAuthStatus(true, null, authMethod);
5776+
if (sessionStorage.getItem('anypoint_token_expires_at')) {
5777+
startTtlTimer();
5778+
}
56445779
}
56455780

56465781
// Render environment variables
@@ -6405,6 +6540,7 @@ async function runWorkflowStep(skillSlug, stepIndex) {
64056540
})
64066541
});
64076542
var data = await resp.json();
6543+
handleProxyResponse(data, fullUrl);
64086544

64096545
if (spinner) spinner.style.display = 'none';
64106546
if (rightPanel) rightPanel.setAttribute('open', '');

scripts/portal_generator/assets/styles.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,15 @@ code {
15181518
background: rgba(46, 160, 67, 0.25);
15191519
}
15201520

1521+
[data-theme="dark"] .auth-status-container.expired {
1522+
background: rgba(227, 179, 65, 0.15);
1523+
border-color: #e3b341;
1524+
}
1525+
1526+
[data-theme="dark"] .auth-panel-status:hover .auth-status-container.expired {
1527+
background: rgba(227, 179, 65, 0.25);
1528+
}
1529+
15211530
[data-theme="dark"] .badge-security {
15221531
background: rgba(110, 118, 129, 0.1);
15231532
color: #8b949e;

0 commit comments

Comments
 (0)