@masterteam/gateway-auth 0.0.24 → 0.0.26
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { firstValueFrom, EMPTY, of, defer, from, isObservable, map, tap as tap$1, catchError as catchError$1,
|
|
1
|
+
import { firstValueFrom, EMPTY, of, defer, from, isObservable, map, tap as tap$1, throwError, catchError as catchError$1, switchMap as switchMap$1 } from 'rxjs';
|
|
2
2
|
import { HttpClient, HttpContextToken, HttpBackend, HttpResponse } from '@angular/common/http';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { InjectionToken, inject, Injectable, computed, input, ChangeDetectionStrategy, Component, signal, effect } from '@angular/core';
|
|
@@ -956,6 +956,7 @@ let GatewayAuthState = class GatewayAuthState {
|
|
|
956
956
|
...ctx.getState().appSessions,
|
|
957
957
|
[action.session.applicationCode]: action.session,
|
|
958
958
|
},
|
|
959
|
+
activeApplicationCode: action.session.applicationCode,
|
|
959
960
|
});
|
|
960
961
|
}
|
|
961
962
|
updateAppTokens(ctx, action) {
|
|
@@ -1434,25 +1435,64 @@ const normalizePath = (path) => {
|
|
|
1434
1435
|
const withoutQuery = trimmed.split('?')[0] || '';
|
|
1435
1436
|
return withoutQuery.startsWith('api/') ? withoutQuery.slice(4) : withoutQuery;
|
|
1436
1437
|
};
|
|
1437
|
-
|
|
1438
|
+
const getBasePath = (baseUrl) => {
|
|
1439
|
+
if (!baseUrl) {
|
|
1440
|
+
return '';
|
|
1441
|
+
}
|
|
1442
|
+
try {
|
|
1443
|
+
return new URL(baseUrl).pathname;
|
|
1444
|
+
}
|
|
1445
|
+
catch {
|
|
1446
|
+
return baseUrl.startsWith('/') ? baseUrl : '';
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
const stripBasePath = (path, baseUrl) => {
|
|
1450
|
+
const basePath = getBasePath(baseUrl).replace(/^\/+/, '').replace(/\/+$/, '');
|
|
1451
|
+
const normalizedPath = path.replace(/^\/+/, '');
|
|
1452
|
+
if (basePath &&
|
|
1453
|
+
(normalizedPath === basePath || normalizedPath.startsWith(`${basePath}/`))) {
|
|
1454
|
+
return normalizedPath.slice(basePath.length).replace(/^\/+/, '');
|
|
1455
|
+
}
|
|
1456
|
+
return path;
|
|
1457
|
+
};
|
|
1458
|
+
const resolveRequestPath = (url, baseUrl) => {
|
|
1459
|
+
let path = url;
|
|
1438
1460
|
if (isAbsoluteUrl(url)) {
|
|
1439
1461
|
try {
|
|
1440
|
-
|
|
1462
|
+
path = new URL(url).pathname;
|
|
1441
1463
|
}
|
|
1442
1464
|
catch {
|
|
1443
|
-
|
|
1465
|
+
path = url;
|
|
1444
1466
|
}
|
|
1445
1467
|
}
|
|
1446
|
-
if (
|
|
1447
|
-
|
|
1468
|
+
else if (baseUrl && url.startsWith(baseUrl)) {
|
|
1469
|
+
path = url.slice(baseUrl.length);
|
|
1448
1470
|
}
|
|
1449
|
-
return normalizePath(
|
|
1471
|
+
return normalizePath(stripBasePath(path, baseUrl));
|
|
1472
|
+
};
|
|
1473
|
+
function resolveGatewayAuthPath(url, gatewayApiBaseUrl) {
|
|
1474
|
+
return resolveRequestPath(url, gatewayApiBaseUrl);
|
|
1450
1475
|
}
|
|
1451
1476
|
function isGatewayAuthRequestUrl(url, gatewayApiBaseUrl) {
|
|
1452
1477
|
const path = resolveGatewayAuthPath(url, gatewayApiBaseUrl).toLowerCase();
|
|
1453
1478
|
return (GATEWAY_AUTH_ENDPOINT_PATHS.has(path) ||
|
|
1454
1479
|
GATEWAY_AUTH_ENDPOINT_PREFIXES.some((prefix) => path.startsWith(prefix)));
|
|
1455
1480
|
}
|
|
1481
|
+
const GATEWAY_APPLICATION_LAUNCH_PATH_PATTERN = /^applications\/[^/]+\/launch$/i;
|
|
1482
|
+
const isGatewayEndpointRequestUrl = (url, gatewayApiBaseUrl) => {
|
|
1483
|
+
const path = resolveGatewayAuthPath(url, gatewayApiBaseUrl).toLowerCase();
|
|
1484
|
+
return (path.startsWith('auth/') ||
|
|
1485
|
+
GATEWAY_APPLICATION_LAUNCH_PATH_PATTERN.test(path));
|
|
1486
|
+
};
|
|
1487
|
+
const isApplicationContextRequestUrl = (url, applicationApiBaseUrl) => resolveRequestPath(url, applicationApiBaseUrl).toLowerCase() ===
|
|
1488
|
+
GATEWAY_AUTH_ENDPOINTS.applicationContext;
|
|
1489
|
+
class GatewaySessionDeadError extends Error {
|
|
1490
|
+
constructor() {
|
|
1491
|
+
super('GATEWAY_SESSION_DEAD');
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
const isGatewaySessionDeadError = (error) => error instanceof GatewaySessionDeadError;
|
|
1495
|
+
const isUnauthorizedError = (error) => error?.status === 401;
|
|
1456
1496
|
const getBrowserRefreshLock = () => {
|
|
1457
1497
|
if (typeof navigator === 'undefined') {
|
|
1458
1498
|
return null;
|
|
@@ -1601,6 +1641,138 @@ const refreshAccessToken = (context, refreshToken) => {
|
|
|
1601
1641
|
return promise;
|
|
1602
1642
|
};
|
|
1603
1643
|
const refreshTokens$ = (context, refreshToken) => from(refreshAccessToken(context, refreshToken));
|
|
1644
|
+
const inflightRelaunchByCode = new Map();
|
|
1645
|
+
const resolveRelaunchReturnUrl = (options, applicationCode) => {
|
|
1646
|
+
const fromOption = options.getApplicationLaunchReturnUrl?.(applicationCode);
|
|
1647
|
+
const trimmed = fromOption?.trim();
|
|
1648
|
+
if (trimmed) {
|
|
1649
|
+
return trimmed;
|
|
1650
|
+
}
|
|
1651
|
+
if (typeof window !== 'undefined' && window.location?.origin) {
|
|
1652
|
+
return window.location.origin;
|
|
1653
|
+
}
|
|
1654
|
+
return null;
|
|
1655
|
+
};
|
|
1656
|
+
const executeRelaunch = (context, options) => {
|
|
1657
|
+
const code = context.applicationCode;
|
|
1658
|
+
if (!code) {
|
|
1659
|
+
return Promise.reject(new Error('Cannot re-launch without applicationCode'));
|
|
1660
|
+
}
|
|
1661
|
+
const lockKey = `masterteam-gateway-auth-relaunch:${code}`;
|
|
1662
|
+
return runWithRefreshLock(lockKey, async () => {
|
|
1663
|
+
// If another flow/tab already refreshed the app session, use it.
|
|
1664
|
+
const existingSession = context.auth.getAppSession(code);
|
|
1665
|
+
if (existingSession &&
|
|
1666
|
+
existingSession.accessToken &&
|
|
1667
|
+
!isExpired(existingSession.accessTokenExpiresAt, context.refreshSkewMs)) {
|
|
1668
|
+
return {
|
|
1669
|
+
accessToken: existingSession.accessToken,
|
|
1670
|
+
accessTokenExpiresAt: {
|
|
1671
|
+
actualValue: existingSession.accessTokenExpiresAt ?? '',
|
|
1672
|
+
},
|
|
1673
|
+
refreshToken: existingSession.refreshToken,
|
|
1674
|
+
refreshTokenExpiresAt: {
|
|
1675
|
+
actualValue: existingSession.refreshTokenExpiresAt ?? '',
|
|
1676
|
+
},
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
const ensureGatewayAccessToken = async (forceRefresh = false) => {
|
|
1680
|
+
const gatewayAccessToken = context.auth.token();
|
|
1681
|
+
const gatewayAccessTokenExpiresAt = context.auth.accessTokenExpiresAt();
|
|
1682
|
+
const gatewayAccessExpired = !gatewayAccessToken ||
|
|
1683
|
+
isExpired(gatewayAccessTokenExpiresAt, context.refreshSkewMs);
|
|
1684
|
+
if (!forceRefresh && !gatewayAccessExpired && gatewayAccessToken) {
|
|
1685
|
+
return gatewayAccessToken;
|
|
1686
|
+
}
|
|
1687
|
+
const gatewayRefreshToken = context.auth.refreshToken();
|
|
1688
|
+
if (!gatewayRefreshToken ||
|
|
1689
|
+
isExpired(context.auth.refreshTokenExpiresAt())) {
|
|
1690
|
+
throw new GatewaySessionDeadError();
|
|
1691
|
+
}
|
|
1692
|
+
try {
|
|
1693
|
+
await refreshAccessToken({ ...context, scope: 'gateway', applicationCode: undefined }, gatewayRefreshToken);
|
|
1694
|
+
}
|
|
1695
|
+
catch {
|
|
1696
|
+
throw new GatewaySessionDeadError();
|
|
1697
|
+
}
|
|
1698
|
+
const refreshedGatewayAccessToken = context.auth.token();
|
|
1699
|
+
if (!refreshedGatewayAccessToken) {
|
|
1700
|
+
throw new GatewaySessionDeadError();
|
|
1701
|
+
}
|
|
1702
|
+
return refreshedGatewayAccessToken;
|
|
1703
|
+
};
|
|
1704
|
+
const url = buildGatewayUrl(context.gatewayApiBaseUrl, GATEWAY_AUTH_ENDPOINTS.applicationLaunch(code));
|
|
1705
|
+
const params = {};
|
|
1706
|
+
const returnUrl = resolveRelaunchReturnUrl(options, code);
|
|
1707
|
+
if (returnUrl) {
|
|
1708
|
+
params['returnUrl'] = returnUrl;
|
|
1709
|
+
}
|
|
1710
|
+
if (context.deviceToken) {
|
|
1711
|
+
params['deviceToken'] = context.deviceToken;
|
|
1712
|
+
}
|
|
1713
|
+
const requestLaunch = (gatewayAccessToken) => firstValueFrom(context.http
|
|
1714
|
+
.get(url, {
|
|
1715
|
+
params,
|
|
1716
|
+
headers: {
|
|
1717
|
+
Authorization: `Bearer ${gatewayAccessToken}`,
|
|
1718
|
+
},
|
|
1719
|
+
})
|
|
1720
|
+
.pipe(map((response) => response.data), map((data) => {
|
|
1721
|
+
if (!data?.tokens?.accessToken || !data.tokens.refreshToken) {
|
|
1722
|
+
throw new Error('Invalid launch response');
|
|
1723
|
+
}
|
|
1724
|
+
const mapped = mapGatewayTokens(data.tokens);
|
|
1725
|
+
const session = {
|
|
1726
|
+
applicationCode: data.applicationCode,
|
|
1727
|
+
applicationName: data.applicationName,
|
|
1728
|
+
launchUrl: data.launchUrl,
|
|
1729
|
+
accessToken: mapped.accessToken,
|
|
1730
|
+
refreshToken: mapped.refreshToken,
|
|
1731
|
+
accessTokenExpiresAt: mapped.accessTokenExpiresAt,
|
|
1732
|
+
refreshTokenExpiresAt: mapped.refreshTokenExpiresAt,
|
|
1733
|
+
};
|
|
1734
|
+
context.auth.setAppSession(session);
|
|
1735
|
+
return data.tokens;
|
|
1736
|
+
})));
|
|
1737
|
+
const gatewayAccessToken = await ensureGatewayAccessToken();
|
|
1738
|
+
try {
|
|
1739
|
+
return await requestLaunch(gatewayAccessToken);
|
|
1740
|
+
}
|
|
1741
|
+
catch (error) {
|
|
1742
|
+
if (!isUnauthorizedError(error)) {
|
|
1743
|
+
throw error;
|
|
1744
|
+
}
|
|
1745
|
+
const refreshedGatewayAccessToken = await ensureGatewayAccessToken(true);
|
|
1746
|
+
try {
|
|
1747
|
+
return await requestLaunch(refreshedGatewayAccessToken);
|
|
1748
|
+
}
|
|
1749
|
+
catch (retryError) {
|
|
1750
|
+
if (isUnauthorizedError(retryError)) {
|
|
1751
|
+
throw new GatewaySessionDeadError();
|
|
1752
|
+
}
|
|
1753
|
+
throw retryError;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
};
|
|
1758
|
+
const relaunchApplication = (context, options) => {
|
|
1759
|
+
const code = context.applicationCode;
|
|
1760
|
+
if (!code) {
|
|
1761
|
+
return Promise.reject(new Error('Cannot re-launch without applicationCode'));
|
|
1762
|
+
}
|
|
1763
|
+
const existing = inflightRelaunchByCode.get(code);
|
|
1764
|
+
if (existing) {
|
|
1765
|
+
return existing;
|
|
1766
|
+
}
|
|
1767
|
+
const promise = executeRelaunch(context, options);
|
|
1768
|
+
inflightRelaunchByCode.set(code, promise);
|
|
1769
|
+
promise.finally(() => {
|
|
1770
|
+
if (inflightRelaunchByCode.get(code) === promise) {
|
|
1771
|
+
inflightRelaunchByCode.delete(code);
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
return promise;
|
|
1775
|
+
};
|
|
1604
1776
|
const prepareRequest = (req, token, markRetried, baseUrl) => {
|
|
1605
1777
|
let modifiedReq = req;
|
|
1606
1778
|
if (token) {
|
|
@@ -1623,13 +1795,46 @@ const prepareRequest = (req, token, markRetried, baseUrl) => {
|
|
|
1623
1795
|
return modifiedReq;
|
|
1624
1796
|
};
|
|
1625
1797
|
const urlMatchesBase = (url, baseUrl) => {
|
|
1626
|
-
if (!baseUrl
|
|
1798
|
+
if (!baseUrl) {
|
|
1627
1799
|
return false;
|
|
1628
1800
|
}
|
|
1629
|
-
|
|
1801
|
+
const normalizedBase = normalizeBase(baseUrl);
|
|
1802
|
+
if (isAbsoluteUrl(url) && isAbsoluteUrl(normalizedBase)) {
|
|
1803
|
+
return url.startsWith(normalizedBase);
|
|
1804
|
+
}
|
|
1805
|
+
const basePath = getBasePath(baseUrl).replace(/^\/+/, '').replace(/\/+$/, '');
|
|
1806
|
+
if (!basePath) {
|
|
1807
|
+
return !isAbsoluteUrl(url) && url.startsWith(normalizedBase);
|
|
1808
|
+
}
|
|
1809
|
+
let path = url;
|
|
1810
|
+
if (isAbsoluteUrl(url)) {
|
|
1811
|
+
try {
|
|
1812
|
+
path = new URL(url).pathname;
|
|
1813
|
+
}
|
|
1814
|
+
catch {
|
|
1815
|
+
path = url;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
path = path.split('?')[0].replace(/^\/+/, '');
|
|
1819
|
+
return path === basePath || path.startsWith(`${basePath}/`);
|
|
1630
1820
|
};
|
|
1631
|
-
const resolveRequestScope = (req, options, auth, gatewayApiBaseUrl) => {
|
|
1632
|
-
// 1.
|
|
1821
|
+
const resolveRequestScope = (req, options, auth, gatewayApiBaseUrl, applicationApiBaseUrl) => {
|
|
1822
|
+
// 1. Public app context is deliberately unauthenticated app scope.
|
|
1823
|
+
if (isApplicationContextRequestUrl(req.url, applicationApiBaseUrl)) {
|
|
1824
|
+
return { scope: 'application', useGatewayBaseUrl: false };
|
|
1825
|
+
}
|
|
1826
|
+
// 2. Known Gateway endpoints must always use GatewaySession.
|
|
1827
|
+
if (isGatewayEndpointRequestUrl(req.url, gatewayApiBaseUrl)) {
|
|
1828
|
+
return {
|
|
1829
|
+
scope: 'gateway',
|
|
1830
|
+
useGatewayBaseUrl: !urlMatchesBase(req.url, gatewayApiBaseUrl),
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
// 3. Caller hint via request context.
|
|
1834
|
+
if (options.shouldUseGatewayApiBaseUrl?.(req)) {
|
|
1835
|
+
return { scope: 'gateway', useGatewayBaseUrl: true };
|
|
1836
|
+
}
|
|
1837
|
+
// 4. Per-request app override.
|
|
1633
1838
|
const explicitCode = options.resolveApplicationCodeForRequest?.(req);
|
|
1634
1839
|
if (explicitCode) {
|
|
1635
1840
|
const session = auth.getAppSession(explicitCode);
|
|
@@ -1640,30 +1845,34 @@ const resolveRequestScope = (req, options, auth, gatewayApiBaseUrl) => {
|
|
|
1640
1845
|
useGatewayBaseUrl: false,
|
|
1641
1846
|
};
|
|
1642
1847
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1848
|
+
const defaultCode = resolveApplicationCodeOption(options.applicationCode) ??
|
|
1849
|
+
auth.activeApplicationCode();
|
|
1850
|
+
// 5. Requests targeting the configured app API use ApplicationAccess.
|
|
1851
|
+
if (urlMatchesBase(req.url, applicationApiBaseUrl) && defaultCode) {
|
|
1852
|
+
return {
|
|
1853
|
+
scope: 'application',
|
|
1854
|
+
applicationCode: defaultCode,
|
|
1855
|
+
session: auth.getAppSession(defaultCode),
|
|
1856
|
+
useGatewayBaseUrl: false,
|
|
1857
|
+
};
|
|
1646
1858
|
}
|
|
1647
|
-
//
|
|
1648
|
-
// Covers /api/auth/*, /api/auth/me/applications, /api/applications/{code}/launch.
|
|
1859
|
+
// 6. Absolute URL pointing at the Gateway base must use GatewaySession.
|
|
1649
1860
|
if (urlMatchesBase(req.url, gatewayApiBaseUrl)) {
|
|
1650
1861
|
return { scope: 'gateway', useGatewayBaseUrl: false };
|
|
1651
1862
|
}
|
|
1652
|
-
//
|
|
1653
|
-
|
|
1654
|
-
|
|
1863
|
+
// 7. App shells with a configured/active app code should not send the
|
|
1864
|
+
// GatewaySession to direct app APIs. The interceptor will launch the app
|
|
1865
|
+
// if the ApplicationAccess pair is not available yet.
|
|
1655
1866
|
if (defaultCode) {
|
|
1656
1867
|
const session = auth.getAppSession(defaultCode);
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
};
|
|
1664
|
-
}
|
|
1868
|
+
return {
|
|
1869
|
+
scope: 'application',
|
|
1870
|
+
applicationCode: defaultCode,
|
|
1871
|
+
session,
|
|
1872
|
+
useGatewayBaseUrl: false,
|
|
1873
|
+
};
|
|
1665
1874
|
}
|
|
1666
|
-
//
|
|
1875
|
+
// 8. No app context known yet - fall back to GatewaySession.
|
|
1667
1876
|
return { scope: 'gateway', useGatewayBaseUrl: false };
|
|
1668
1877
|
};
|
|
1669
1878
|
const gatewayAuthInterceptor = (req, next) => {
|
|
@@ -1679,21 +1888,18 @@ const gatewayAuthInterceptor = (req, next) => {
|
|
|
1679
1888
|
const refreshSkewMs = resolveAccessTokenRefreshSkewMs(options.accessTokenRefreshSkewMs);
|
|
1680
1889
|
const isAuthRequest = isGatewayAuthRequestUrl(req.url, gatewayApiBaseUrl);
|
|
1681
1890
|
const alreadyRetried = req.context.get(GATEWAY_AUTH_RETRY_CONTEXT);
|
|
1682
|
-
const resolved = resolveRequestScope(req, options, auth, gatewayApiBaseUrl);
|
|
1891
|
+
const resolved = resolveRequestScope(req, options, auth, gatewayApiBaseUrl, appApiBaseUrl);
|
|
1683
1892
|
const baseUrl = resolved.useGatewayBaseUrl
|
|
1684
1893
|
? gatewayApiBaseUrl
|
|
1685
1894
|
: appApiBaseUrl;
|
|
1686
1895
|
const gatewayAccessToken = auth.token();
|
|
1687
1896
|
const session = resolved.session ?? null;
|
|
1688
|
-
const accessToken = resolved.scope === 'application'
|
|
1689
|
-
? session
|
|
1897
|
+
const accessToken = resolved.scope === 'application'
|
|
1898
|
+
? (session?.accessToken ?? null)
|
|
1690
1899
|
: gatewayAccessToken;
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
// retries. This avoids the post-login refresh cascade caused by short
|
|
1695
|
-
// access-token TTLs versus any skew.
|
|
1696
|
-
const tokenToAttach = !isAuthRequest && accessToken ? accessToken : null;
|
|
1900
|
+
const isApplicationContextRequest = isApplicationContextRequestUrl(req.url, appApiBaseUrl);
|
|
1901
|
+
const shouldAttachAuth = !isAuthRequest && !isApplicationContextRequest;
|
|
1902
|
+
const tokenToAttach = shouldAttachAuth && accessToken ? accessToken : null;
|
|
1697
1903
|
const buildRefreshContext = (scopeOverride, appCodeOverride) => ({
|
|
1698
1904
|
http,
|
|
1699
1905
|
gatewayApiBaseUrl,
|
|
@@ -1703,49 +1909,100 @@ const gatewayAuthInterceptor = (req, next) => {
|
|
|
1703
1909
|
scope: scopeOverride ?? resolved.scope,
|
|
1704
1910
|
applicationCode: appCodeOverride ?? resolved.applicationCode,
|
|
1705
1911
|
});
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1708
|
-
|
|
1912
|
+
const handleRelaunchError = (relaunchError) => {
|
|
1913
|
+
if (isGatewaySessionDeadError(relaunchError)) {
|
|
1914
|
+
auth.logout();
|
|
1915
|
+
}
|
|
1916
|
+
else if (resolved.applicationCode) {
|
|
1917
|
+
auth.clearAppSession(resolved.applicationCode);
|
|
1918
|
+
}
|
|
1919
|
+
return throwError(() => relaunchError);
|
|
1920
|
+
};
|
|
1921
|
+
const relaunch$ = (clearAppSessionBeforeLaunch = true) => {
|
|
1922
|
+
if (clearAppSessionBeforeLaunch && resolved.applicationCode) {
|
|
1923
|
+
auth.clearAppSession(resolved.applicationCode);
|
|
1924
|
+
}
|
|
1925
|
+
return from(relaunchApplication(buildRefreshContext('application', resolved.applicationCode), options)).pipe(catchError$1(handleRelaunchError));
|
|
1926
|
+
};
|
|
1927
|
+
const resolveGatewayTokenBeforeRequest$ = () => {
|
|
1928
|
+
const currentAccessToken = auth.token();
|
|
1929
|
+
const currentAccessTokenExpiresAt = auth.accessTokenExpiresAt();
|
|
1930
|
+
if (currentAccessToken &&
|
|
1931
|
+
!isExpired(currentAccessTokenExpiresAt, refreshSkewMs)) {
|
|
1932
|
+
return of(currentAccessToken);
|
|
1933
|
+
}
|
|
1934
|
+
const gatewayRefreshToken = auth.refreshToken();
|
|
1935
|
+
if (!currentAccessToken && !gatewayRefreshToken) {
|
|
1936
|
+
return of(null);
|
|
1937
|
+
}
|
|
1938
|
+
if (!gatewayRefreshToken || isExpired(auth.refreshTokenExpiresAt())) {
|
|
1939
|
+
auth.logout();
|
|
1940
|
+
return throwError(() => new GatewaySessionDeadError());
|
|
1941
|
+
}
|
|
1942
|
+
return refreshTokens$(buildRefreshContext('gateway', undefined), gatewayRefreshToken).pipe(map((tokens) => tokens.accessToken), catchError$1((refreshError) => {
|
|
1943
|
+
auth.logout();
|
|
1944
|
+
return throwError(() => refreshError);
|
|
1945
|
+
}));
|
|
1946
|
+
};
|
|
1947
|
+
const resolveAppTokenBeforeRequest$ = () => {
|
|
1948
|
+
const code = resolved.applicationCode;
|
|
1949
|
+
if (!code) {
|
|
1950
|
+
return of(tokenToAttach);
|
|
1951
|
+
}
|
|
1952
|
+
const latestSession = auth.getAppSession(code);
|
|
1953
|
+
if (latestSession?.accessToken &&
|
|
1954
|
+
!isExpired(latestSession.accessTokenExpiresAt, refreshSkewMs)) {
|
|
1955
|
+
return of(latestSession.accessToken);
|
|
1956
|
+
}
|
|
1957
|
+
if (!latestSession?.refreshToken ||
|
|
1958
|
+
isExpired(latestSession.refreshTokenExpiresAt)) {
|
|
1959
|
+
return relaunch$().pipe(map((tokens) => tokens.accessToken));
|
|
1960
|
+
}
|
|
1961
|
+
return refreshTokens$(buildRefreshContext('application', code), latestSession.refreshToken).pipe(map((tokens) => tokens.accessToken), catchError$1(() => relaunch$().pipe(map((tokens) => tokens.accessToken))));
|
|
1962
|
+
};
|
|
1963
|
+
const initialToken$ = !shouldAttachAuth
|
|
1964
|
+
? of(null)
|
|
1965
|
+
: resolved.scope === 'application'
|
|
1966
|
+
? resolveAppTokenBeforeRequest$()
|
|
1967
|
+
: resolveGatewayTokenBeforeRequest$();
|
|
1968
|
+
return initialToken$.pipe(switchMap$1((requestAccessToken) => next(prepareRequest(req, requestAccessToken, false, baseUrl)).pipe(catchError$1((error) => {
|
|
1969
|
+
if (error?.status !== 401 || !shouldAttachAuth || alreadyRetried) {
|
|
1709
1970
|
return throwError(() => error);
|
|
1710
1971
|
}
|
|
1711
1972
|
// If a concurrent flow already rotated the token while this request was
|
|
1712
|
-
// in flight, just retry with the freshest token
|
|
1973
|
+
// in flight, just retry with the freshest token - do NOT trigger another
|
|
1713
1974
|
// refresh against the backend.
|
|
1714
1975
|
const currentAccessToken = resolved.scope === 'application' && resolved.applicationCode
|
|
1715
|
-
? (auth.getAppSession(resolved.applicationCode)?.accessToken ??
|
|
1976
|
+
? (auth.getAppSession(resolved.applicationCode)?.accessToken ??
|
|
1977
|
+
null)
|
|
1716
1978
|
: auth.token();
|
|
1717
|
-
if (currentAccessToken && currentAccessToken !==
|
|
1979
|
+
if (currentAccessToken && currentAccessToken !== requestAccessToken) {
|
|
1718
1980
|
return next(prepareRequest(req, currentAccessToken, true, baseUrl));
|
|
1719
1981
|
}
|
|
1720
|
-
const
|
|
1982
|
+
const isApp = resolved.scope === 'application' && !!resolved.applicationCode;
|
|
1983
|
+
const latestAppSession = isApp
|
|
1721
1984
|
? auth.getAppSession(resolved.applicationCode)
|
|
1722
1985
|
: null;
|
|
1723
|
-
const latestRefreshToken =
|
|
1986
|
+
const latestRefreshToken = isApp
|
|
1724
1987
|
? (latestAppSession?.refreshToken ?? null)
|
|
1725
1988
|
: auth.refreshToken();
|
|
1726
|
-
const latestRefreshTokenExpiresAt =
|
|
1989
|
+
const latestRefreshTokenExpiresAt = isApp
|
|
1727
1990
|
? (latestAppSession?.refreshTokenExpiresAt ?? null)
|
|
1728
1991
|
: auth.refreshTokenExpiresAt();
|
|
1729
1992
|
const canRefreshNow = !!latestRefreshToken && !isExpired(latestRefreshTokenExpiresAt);
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}
|
|
1739
|
-
return refreshTokens$(buildRefreshContext(), latestRefreshToken).pipe(catchError$1((refreshError) => {
|
|
1740
|
-
if (resolved.scope === 'application' && resolved.applicationCode) {
|
|
1741
|
-
auth.clearAppSession(resolved.applicationCode);
|
|
1742
|
-
}
|
|
1743
|
-
else {
|
|
1993
|
+
const tokens$ = !canRefreshNow || !latestRefreshToken
|
|
1994
|
+
? isApp
|
|
1995
|
+
? relaunch$()
|
|
1996
|
+
: (auth.logout(), throwError(() => error))
|
|
1997
|
+
: refreshTokens$(buildRefreshContext(), latestRefreshToken).pipe(catchError$1((refreshError) => {
|
|
1998
|
+
if (isApp) {
|
|
1999
|
+
return relaunch$();
|
|
2000
|
+
}
|
|
1744
2001
|
auth.logout();
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
}));
|
|
2002
|
+
return throwError(() => refreshError);
|
|
2003
|
+
}));
|
|
2004
|
+
return tokens$.pipe(switchMap$1((tokens) => next(prepareRequest(req, tokens.accessToken, true, baseUrl))));
|
|
2005
|
+
}))));
|
|
1749
2006
|
};
|
|
1750
2007
|
|
|
1751
2008
|
const CORRELATION_ID_HEADER = 'X-Correlation-ID';
|