oidc-spa 7.2.1 → 7.2.3

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.
Files changed (257) hide show
  1. package/backend.js.map +1 -1
  2. package/core/AuthResponse.js.map +1 -1
  3. package/core/Oidc.js.map +1 -1
  4. package/core/OidcInitializationError.js.map +1 -1
  5. package/core/OidcMetadata.js.map +1 -1
  6. package/core/StateData.js.map +1 -1
  7. package/core/configId.js.map +1 -1
  8. package/core/createOidc.js +1 -1
  9. package/core/createOidc.js.map +1 -1
  10. package/core/diagnostic.js.map +1 -1
  11. package/core/evtIsUserActive.js.map +1 -1
  12. package/core/handleOidcCallback.js.map +1 -1
  13. package/core/iframeMessageProtection.js.map +1 -1
  14. package/core/index.js.map +1 -1
  15. package/core/initialLocationHref.js.map +1 -1
  16. package/core/isNewBrowserSession.js.map +1 -1
  17. package/core/loginOrGoToAuthServer.js.map +1 -1
  18. package/core/loginPropagationToOtherTabs.js.map +1 -1
  19. package/core/loginSilent.js.map +1 -1
  20. package/core/logoutPropagationToOtherTabs.js.map +1 -1
  21. package/core/oidcClientTsUserToTokens.js.map +1 -1
  22. package/core/ongoingLoginOrRefreshProcesses.js.map +1 -1
  23. package/core/persistedAuthState.js.map +1 -1
  24. package/entrypoint.js.map +1 -1
  25. package/esm/core/AuthResponse.js +2 -2
  26. package/esm/core/AuthResponse.js.map +1 -1
  27. package/esm/core/Oidc.d.ts +1 -1
  28. package/esm/core/Oidc.js.map +1 -1
  29. package/esm/core/OidcInitializationError.js.map +1 -1
  30. package/esm/core/OidcMetadata.js +2 -2
  31. package/esm/core/OidcMetadata.js.map +1 -1
  32. package/esm/core/StateData.js +3 -3
  33. package/esm/core/StateData.js.map +1 -1
  34. package/esm/core/configId.js.map +1 -1
  35. package/esm/core/createOidc.d.ts +2 -2
  36. package/esm/core/createOidc.js +33 -33
  37. package/esm/core/createOidc.js.map +1 -1
  38. package/esm/core/diagnostic.d.ts +1 -1
  39. package/esm/core/diagnostic.js +4 -4
  40. package/esm/core/diagnostic.js.map +1 -1
  41. package/esm/core/evtIsUserActive.d.ts +1 -1
  42. package/esm/core/evtIsUserActive.js +5 -5
  43. package/esm/core/evtIsUserActive.js.map +1 -1
  44. package/esm/core/handleOidcCallback.d.ts +2 -2
  45. package/esm/core/handleOidcCallback.js +5 -5
  46. package/esm/core/handleOidcCallback.js.map +1 -1
  47. package/esm/core/iframeMessageProtection.d.ts +1 -1
  48. package/esm/core/iframeMessageProtection.js +3 -3
  49. package/esm/core/iframeMessageProtection.js.map +1 -1
  50. package/esm/core/index.d.ts +4 -4
  51. package/esm/core/index.js +4 -4
  52. package/esm/core/index.js.map +1 -1
  53. package/esm/core/initialLocationHref.js.map +1 -1
  54. package/esm/core/isNewBrowserSession.d.ts +1 -1
  55. package/esm/core/isNewBrowserSession.js.map +1 -1
  56. package/esm/core/loginOrGoToAuthServer.d.ts +2 -2
  57. package/esm/core/loginOrGoToAuthServer.js +6 -6
  58. package/esm/core/loginOrGoToAuthServer.js.map +1 -1
  59. package/esm/core/loginPropagationToOtherTabs.js +3 -3
  60. package/esm/core/loginPropagationToOtherTabs.js.map +1 -1
  61. package/esm/core/loginSilent.d.ts +2 -2
  62. package/esm/core/loginSilent.js +8 -8
  63. package/esm/core/loginSilent.js.map +1 -1
  64. package/esm/core/logoutPropagationToOtherTabs.js +3 -3
  65. package/esm/core/logoutPropagationToOtherTabs.js.map +1 -1
  66. package/esm/core/oidcClientTsUserToTokens.d.ts +2 -2
  67. package/esm/core/oidcClientTsUserToTokens.js +4 -4
  68. package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
  69. package/esm/core/ongoingLoginOrRefreshProcesses.js +3 -3
  70. package/esm/core/ongoingLoginOrRefreshProcesses.js.map +1 -1
  71. package/esm/core/persistedAuthState.js +2 -2
  72. package/esm/core/persistedAuthState.js.map +1 -1
  73. package/esm/entrypoint.js +3 -3
  74. package/esm/entrypoint.js.map +1 -1
  75. package/esm/index.d.ts +1 -1
  76. package/esm/index.js +2 -2
  77. package/esm/index.js.map +1 -1
  78. package/esm/keycloak/index.d.ts +3 -3
  79. package/esm/keycloak/index.js +3 -3
  80. package/esm/keycloak/index.js.map +1 -1
  81. package/esm/keycloak/isKeycloak.js.map +1 -1
  82. package/esm/keycloak/keycloak-js/Keycloak.d.ts +1 -1
  83. package/esm/keycloak/keycloak-js/Keycloak.js +9 -9
  84. package/esm/keycloak/keycloak-js/Keycloak.js.map +1 -1
  85. package/esm/keycloak/keycloak-js/index.d.ts +2 -2
  86. package/esm/keycloak/keycloak-js/index.js +2 -2
  87. package/esm/keycloak/keycloak-js/index.js.map +1 -1
  88. package/esm/keycloak/keycloak-js/types.js.map +1 -1
  89. package/esm/keycloak/keycloakIssuerUriParsed.js +3 -3
  90. package/esm/keycloak/keycloakIssuerUriParsed.js.map +1 -1
  91. package/esm/keycloak/keycloakUtils.d.ts +1 -1
  92. package/esm/keycloak/keycloakUtils.js +3 -3
  93. package/esm/keycloak/keycloakUtils.js.map +1 -1
  94. package/esm/keycloak-js.d.ts +1 -1
  95. package/esm/keycloak-js.js +2 -2
  96. package/esm/keycloak-js.js.map +1 -1
  97. package/esm/mock/index.d.ts +1 -1
  98. package/esm/mock/index.js +2 -2
  99. package/esm/mock/index.js.map +1 -1
  100. package/esm/mock/oidc.d.ts +1 -1
  101. package/esm/mock/oidc.js +6 -6
  102. package/esm/mock/oidc.js.map +1 -1
  103. package/esm/mock/react.d.ts +8 -8
  104. package/esm/mock/react.js +3 -3
  105. package/esm/mock/react.js.map +1 -1
  106. package/esm/react/index.d.ts +1 -1
  107. package/esm/react/index.js +2 -2
  108. package/esm/react/index.js.map +1 -1
  109. package/esm/react/react.d.ts +2 -2
  110. package/esm/react/react.js +6 -6
  111. package/esm/react/react.js.map +1 -1
  112. package/esm/tools/Deferred.js.map +1 -1
  113. package/esm/tools/EphemeralSessionStorage.js +2 -2
  114. package/esm/tools/EphemeralSessionStorage.js.map +1 -1
  115. package/esm/tools/Evt.js +3 -3
  116. package/esm/tools/Evt.js.map +1 -1
  117. package/esm/tools/StatefulEvt.js.map +1 -1
  118. package/esm/tools/ValueOrAsyncGetter.js.map +1 -1
  119. package/esm/tools/asymmetricEncryption.js.map +1 -1
  120. package/esm/tools/base64.js.map +1 -1
  121. package/esm/tools/createObjectThatThrowsIfAccessed.js.map +1 -1
  122. package/esm/tools/decodeJwt.js.map +1 -1
  123. package/esm/tools/generateUrlSafeRandom.js.map +1 -1
  124. package/esm/tools/getDownlinkAndRtt.js +2 -2
  125. package/esm/tools/getDownlinkAndRtt.js.map +1 -1
  126. package/esm/tools/getIsOnline.js +2 -2
  127. package/esm/tools/getIsOnline.js.map +1 -1
  128. package/esm/tools/getIsValidRemoteJson.js.map +1 -1
  129. package/esm/tools/getPrUserInteraction.js +2 -2
  130. package/esm/tools/getPrUserInteraction.js.map +1 -1
  131. package/esm/tools/getUserEnvironmentInfo.js.map +1 -1
  132. package/esm/tools/haveSharedParentDomain.js.map +1 -1
  133. package/esm/tools/isDev.js.map +1 -1
  134. package/esm/tools/parseKeycloakIssuerUri.js +2 -2
  135. package/esm/tools/parseKeycloakIssuerUri.js.map +1 -1
  136. package/esm/tools/readExpirationTimeInJwt.js +3 -3
  137. package/esm/tools/readExpirationTimeInJwt.js.map +1 -1
  138. package/esm/tools/startCountdown.js +2 -2
  139. package/esm/tools/startCountdown.js.map +1 -1
  140. package/esm/tools/subscribeToUserInteraction.js +2 -2
  141. package/esm/tools/subscribeToUserInteraction.js.map +1 -1
  142. package/esm/tools/toFullyQualifiedUrl.js.map +1 -1
  143. package/esm/tools/toHumanReadableDuration.js.map +1 -1
  144. package/esm/tools/urlSearchParams.js.map +1 -1
  145. package/esm/tools/workerTimers.js +2 -2
  146. package/esm/tools/workerTimers.js.map +1 -1
  147. package/index.js.map +1 -1
  148. package/keycloak/index.js.map +1 -1
  149. package/keycloak/isKeycloak.js.map +1 -1
  150. package/keycloak/keycloak-js/Keycloak.js.map +1 -1
  151. package/keycloak/keycloak-js/index.js.map +1 -1
  152. package/keycloak/keycloak-js/types.js.map +1 -1
  153. package/keycloak/keycloakIssuerUriParsed.js.map +1 -1
  154. package/keycloak/keycloakUtils.js.map +1 -1
  155. package/keycloak-js.js.map +1 -1
  156. package/mock/index.js.map +1 -1
  157. package/mock/oidc.js.map +1 -1
  158. package/mock/react.js.map +1 -1
  159. package/package.json +11 -33
  160. package/react/index.js.map +1 -1
  161. package/react/react.js.map +1 -1
  162. package/src/backend.ts +391 -0
  163. package/src/core/AuthResponse.ts +26 -0
  164. package/src/core/Oidc.ts +140 -0
  165. package/src/core/OidcInitializationError.ts +19 -0
  166. package/src/core/OidcMetadata.ts +271 -0
  167. package/src/core/StateData.ts +118 -0
  168. package/src/core/configId.ts +3 -0
  169. package/src/core/createOidc.ts +1576 -0
  170. package/src/core/diagnostic.ts +267 -0
  171. package/src/core/evtIsUserActive.ts +108 -0
  172. package/src/core/handleOidcCallback.ts +321 -0
  173. package/src/core/iframeMessageProtection.ts +100 -0
  174. package/src/core/index.ts +4 -0
  175. package/src/core/initialLocationHref.ts +5 -0
  176. package/src/core/isNewBrowserSession.ts +37 -0
  177. package/src/core/loginOrGoToAuthServer.ts +324 -0
  178. package/src/core/loginPropagationToOtherTabs.ts +51 -0
  179. package/src/core/loginSilent.ts +242 -0
  180. package/src/core/logoutPropagationToOtherTabs.ts +53 -0
  181. package/src/core/oidcClientTsUserToTokens.ts +229 -0
  182. package/src/core/ongoingLoginOrRefreshProcesses.ts +47 -0
  183. package/src/core/persistedAuthState.ts +122 -0
  184. package/src/entrypoint.ts +69 -0
  185. package/src/index.ts +1 -0
  186. package/src/keycloak/index.ts +8 -0
  187. package/src/keycloak/isKeycloak.ts +23 -0
  188. package/src/keycloak/keycloak-js/Keycloak.ts +1097 -0
  189. package/src/keycloak/keycloak-js/index.ts +2 -0
  190. package/src/keycloak/keycloak-js/types.ts +442 -0
  191. package/src/keycloak/keycloakIssuerUriParsed.ts +29 -0
  192. package/src/keycloak/keycloakUtils.ts +90 -0
  193. package/src/keycloak-js.ts +1 -0
  194. package/src/mock/index.ts +1 -0
  195. package/src/mock/oidc.ts +211 -0
  196. package/src/mock/react.tsx +11 -0
  197. package/src/react/index.ts +1 -0
  198. package/src/react/react.tsx +476 -0
  199. package/src/tools/Deferred.ts +33 -0
  200. package/src/tools/EphemeralSessionStorage.ts +223 -0
  201. package/src/tools/Evt.ts +56 -0
  202. package/src/tools/StatefulEvt.ts +38 -0
  203. package/src/tools/ValueOrAsyncGetter.ts +1 -0
  204. package/src/tools/asymmetricEncryption.ts +184 -0
  205. package/src/tools/base64.ts +7 -0
  206. package/src/tools/createObjectThatThrowsIfAccessed.ts +40 -0
  207. package/src/tools/decodeJwt.ts +95 -0
  208. package/src/tools/generateUrlSafeRandom.ts +26 -0
  209. package/src/tools/getDownlinkAndRtt.ts +22 -0
  210. package/src/tools/getIsOnline.ts +20 -0
  211. package/src/tools/getIsValidRemoteJson.ts +18 -0
  212. package/src/tools/getPrUserInteraction.ts +27 -0
  213. package/src/tools/getUserEnvironmentInfo.ts +42 -0
  214. package/src/tools/haveSharedParentDomain.ts +13 -0
  215. package/src/tools/isDev.ts +30 -0
  216. package/src/tools/parseKeycloakIssuerUri.ts +49 -0
  217. package/src/tools/readExpirationTimeInJwt.ts +16 -0
  218. package/src/tools/startCountdown.ts +36 -0
  219. package/src/tools/subscribeToUserInteraction.ts +33 -0
  220. package/src/tools/toFullyQualifiedUrl.ts +58 -0
  221. package/src/tools/toHumanReadableDuration.ts +21 -0
  222. package/src/tools/urlSearchParams.ts +130 -0
  223. package/src/tools/workerTimers.ts +57 -0
  224. package/src/vendor/backend/evt.ts +2 -0
  225. package/src/vendor/backend/jsonwebtoken.ts +1 -0
  226. package/src/vendor/backend/node-fetch.ts +2 -0
  227. package/src/vendor/backend/node-jose.ts +1 -0
  228. package/src/vendor/backend/tsafe.ts +5 -0
  229. package/src/vendor/backend/zod.ts +1 -0
  230. package/src/vendor/frontend/oidc-client-ts.ts +1 -0
  231. package/src/vendor/frontend/tsafe.ts +6 -0
  232. package/src/vendor/frontend/worker-timers.ts +2 -0
  233. package/tools/Deferred.js.map +1 -1
  234. package/tools/EphemeralSessionStorage.js.map +1 -1
  235. package/tools/Evt.js.map +1 -1
  236. package/tools/StatefulEvt.js.map +1 -1
  237. package/tools/ValueOrAsyncGetter.js.map +1 -1
  238. package/tools/asymmetricEncryption.js.map +1 -1
  239. package/tools/base64.js.map +1 -1
  240. package/tools/createObjectThatThrowsIfAccessed.js.map +1 -1
  241. package/tools/decodeJwt.js.map +1 -1
  242. package/tools/generateUrlSafeRandom.js.map +1 -1
  243. package/tools/getDownlinkAndRtt.js.map +1 -1
  244. package/tools/getIsOnline.js.map +1 -1
  245. package/tools/getIsValidRemoteJson.js.map +1 -1
  246. package/tools/getPrUserInteraction.js.map +1 -1
  247. package/tools/getUserEnvironmentInfo.js.map +1 -1
  248. package/tools/haveSharedParentDomain.js.map +1 -1
  249. package/tools/isDev.js.map +1 -1
  250. package/tools/parseKeycloakIssuerUri.js.map +1 -1
  251. package/tools/readExpirationTimeInJwt.js.map +1 -1
  252. package/tools/startCountdown.js.map +1 -1
  253. package/tools/subscribeToUserInteraction.js.map +1 -1
  254. package/tools/toFullyQualifiedUrl.js.map +1 -1
  255. package/tools/toHumanReadableDuration.js.map +1 -1
  256. package/tools/urlSearchParams.js.map +1 -1
  257. package/tools/workerTimers.js.map +1 -1
@@ -0,0 +1,100 @@
1
+ import { assert } from "../vendor/frontend/tsafe";
2
+ import { asymmetricEncrypt, asymmetricDecrypt, generateKeys } from "../tools/asymmetricEncryption";
3
+ import { type AuthResponse } from "./AuthResponse";
4
+
5
+ const sessionStorage_original = window.sessionStorage;
6
+ const setItem_real = Storage.prototype.setItem;
7
+
8
+ const SESSION_STORAGE_PREFIX = "oidc-spa_iframe_authResponse_publicKey_";
9
+
10
+ export function preventSessionStorageSetItemOfPublicKeyByThirdParty() {
11
+ const setItem_protected = function setItem(this: any, key: string, value: string): void {
12
+ if (this !== sessionStorage_original) {
13
+ return setItem_real.call(this, key, value);
14
+ }
15
+
16
+ if (key.startsWith(SESSION_STORAGE_PREFIX)) {
17
+ throw new Error(
18
+ "Attack prevented by oidc-spa. You have malicious code running in your system"
19
+ );
20
+ }
21
+
22
+ return setItem_real.call(sessionStorage_original, key, value);
23
+ };
24
+
25
+ {
26
+ const pd = Object.getOwnPropertyDescriptor(Storage.prototype, "setItem");
27
+
28
+ assert(pd !== undefined);
29
+
30
+ Object.defineProperty(Storage.prototype, "setItem", {
31
+ enumerable: pd.enumerable,
32
+ writable: pd.writable,
33
+ value: setItem_protected
34
+ });
35
+ }
36
+ }
37
+
38
+ const ENCRYPTED_AUTH_RESPONSES_PREFIX = "oidc-spa_encrypted_authResponse_";
39
+
40
+ function getSessionStorageKey(params: { stateUrlParamValue: string }) {
41
+ const { stateUrlParamValue } = params;
42
+
43
+ return `${SESSION_STORAGE_PREFIX}${stateUrlParamValue}`;
44
+ }
45
+
46
+ export async function initIframeMessageProtection(params: { stateUrlParamValue: string }) {
47
+ const { stateUrlParamValue } = params;
48
+
49
+ const { publicKey, privateKey } = await generateKeys();
50
+
51
+ const sessionStorageKey = getSessionStorageKey({ stateUrlParamValue });
52
+
53
+ setItem_real.call(sessionStorage, sessionStorageKey, publicKey);
54
+
55
+ function getIsEncryptedAuthResponse(params: { message: unknown }): boolean {
56
+ const { message } = params;
57
+
58
+ return typeof message === "string" && message.startsWith(ENCRYPTED_AUTH_RESPONSES_PREFIX);
59
+ }
60
+
61
+ async function decodeEncryptedAuth(params: {
62
+ encryptedAuthResponse: string;
63
+ }): Promise<{ authResponse: AuthResponse }> {
64
+ const { encryptedAuthResponse } = params;
65
+
66
+ const { message: authResponse_str } = await asymmetricDecrypt({
67
+ encryptedMessage: encryptedAuthResponse.slice(ENCRYPTED_AUTH_RESPONSES_PREFIX.length),
68
+ privateKey
69
+ });
70
+
71
+ const authResponse: AuthResponse = JSON.parse(authResponse_str);
72
+
73
+ return { authResponse };
74
+ }
75
+
76
+ function clearSessionStoragePublicKey() {
77
+ sessionStorage.removeItem(sessionStorageKey);
78
+ }
79
+
80
+ return { getIsEncryptedAuthResponse, decodeEncryptedAuth, clearSessionStoragePublicKey };
81
+ }
82
+
83
+ export async function encryptAuthResponse(params: { authResponse: AuthResponse }) {
84
+ const { authResponse } = params;
85
+
86
+ const publicKey = sessionStorage.getItem(
87
+ getSessionStorageKey({ stateUrlParamValue: authResponse.state })
88
+ );
89
+
90
+ assert(publicKey !== null, "2293302");
91
+
92
+ const { encryptedMessage: encryptedMessage_withoutPrefix } = await asymmetricEncrypt({
93
+ publicKey,
94
+ message: JSON.stringify(authResponse)
95
+ });
96
+
97
+ const encryptedMessage = `${ENCRYPTED_AUTH_RESPONSES_PREFIX}${encryptedMessage_withoutPrefix}`;
98
+
99
+ return { encryptedMessage };
100
+ }
@@ -0,0 +1,4 @@
1
+ export type { Oidc } from "./Oidc";
2
+ export { createOidc, type ParamsOfCreateOidc } from "./createOidc";
3
+ export { OidcInitializationError } from "./OidcInitializationError";
4
+ export { handleOidcCallback } from "./handleOidcCallback";
@@ -0,0 +1,5 @@
1
+ const globalContext = {
2
+ initialLocationHref: window.location.href
3
+ };
4
+
5
+ export const { initialLocationHref } = globalContext;
@@ -0,0 +1,37 @@
1
+ import type { NonPostableEvt } from "../tools/Evt";
2
+
3
+ export function createGetIsNewBrowserSession(params: {
4
+ configId: string;
5
+ evtUserNotLoggedIn: NonPostableEvt<void>;
6
+ }) {
7
+ const { configId, evtUserNotLoggedIn } = params;
8
+
9
+ const SESSION_STORAGE_KEY = `oidc-spa.subject-id:${configId}`;
10
+
11
+ {
12
+ const { unsubscribe } = evtUserNotLoggedIn.subscribe(() => {
13
+ unsubscribe();
14
+ sessionStorage.removeItem(SESSION_STORAGE_KEY);
15
+ });
16
+ }
17
+
18
+ function getIsNewBrowserSession(params: { subjectId: string }): boolean {
19
+ const { subjectId } = params;
20
+
21
+ const subjectId_sessionStorage = sessionStorage.getItem(SESSION_STORAGE_KEY);
22
+
23
+ if (subjectId_sessionStorage === null) {
24
+ sessionStorage.setItem(SESSION_STORAGE_KEY, subjectId);
25
+ return true;
26
+ }
27
+
28
+ if (subjectId !== subjectId_sessionStorage) {
29
+ sessionStorage.setItem(SESSION_STORAGE_KEY, subjectId);
30
+ return true;
31
+ }
32
+
33
+ return false;
34
+ }
35
+
36
+ return { getIsNewBrowserSession };
37
+ }
@@ -0,0 +1,324 @@
1
+ import type { UserManager as OidcClientTsUserManager } from "../vendor/frontend/oidc-client-ts";
2
+ import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
3
+ import { assert, type Equals, noUndefined } from "../vendor/frontend/tsafe";
4
+ import type { StateData } from "./StateData";
5
+ import type { NonPostableEvt } from "../tools/Evt";
6
+ import { createStatefulEvt } from "../tools/StatefulEvt";
7
+ import { Deferred } from "../tools/Deferred";
8
+ import { addOrUpdateSearchParam, getAllSearchParams } from "../tools/urlSearchParams";
9
+
10
+ const globalContext = {
11
+ evtHasLoginBeenCalled: createStatefulEvt(() => false)
12
+ };
13
+
14
+ type Params = Params.Login | Params.GoToAuthServer;
15
+
16
+ namespace Params {
17
+ type Common = {
18
+ redirectUrl: string;
19
+ extraQueryParams_local: Record<string, string | undefined> | undefined;
20
+ transformUrlBeforeRedirect_local: ((url: string) => string) | undefined;
21
+ };
22
+
23
+ export type Login = Common & {
24
+ action: "login";
25
+ doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: boolean;
26
+ doForceReloadOnBfCache: boolean;
27
+ interaction:
28
+ | "ensure no interaction"
29
+ | "ensure interaction"
30
+ | "directly redirect if active session show login otherwise";
31
+ };
32
+
33
+ export type GoToAuthServer = Common & {
34
+ action: "go to auth server";
35
+ };
36
+ }
37
+
38
+ export function getPrSafelyRestoredFromBfCacheAfterLoginBackNavigation() {
39
+ const dOut = new Deferred<void>();
40
+
41
+ const { unsubscribe } = globalContext.evtHasLoginBeenCalled.subscribe(hasLoginBeenCalled => {
42
+ if (!hasLoginBeenCalled) {
43
+ unsubscribe();
44
+ dOut.resolve();
45
+ }
46
+ });
47
+
48
+ return dOut.pr;
49
+ }
50
+
51
+ export function createLoginOrGoToAuthServer(params: {
52
+ configId: string;
53
+ oidcClientTsUserManager: OidcClientTsUserManager;
54
+ transformUrlBeforeRedirect:
55
+ | ((params: { authorizationUrl: string; isSilent: boolean }) => string)
56
+ | undefined;
57
+
58
+ getExtraQueryParams:
59
+ | ((params: { isSilent: boolean; url: string }) => Record<string, string | undefined>)
60
+ | undefined;
61
+
62
+ getExtraTokenParams: (() => Record<string, string | undefined>) | undefined;
63
+
64
+ homeUrl: string;
65
+ evtIsUserLoggedIn: NonPostableEvt<boolean>;
66
+ log: typeof console.log | undefined;
67
+ }) {
68
+ const {
69
+ configId,
70
+ oidcClientTsUserManager,
71
+
72
+ transformUrlBeforeRedirect,
73
+ getExtraQueryParams,
74
+
75
+ getExtraTokenParams,
76
+
77
+ homeUrl,
78
+ evtIsUserLoggedIn,
79
+ log
80
+ } = params;
81
+
82
+ const LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN = `oidc-spa.login-redirect-initiated:${configId}`;
83
+
84
+ let lastPublicUrl: string | undefined = undefined;
85
+
86
+ async function loginOrGoToAuthServer(params: Params): Promise<never> {
87
+ const {
88
+ redirectUrl: redirectUrl_params,
89
+ extraQueryParams_local,
90
+ transformUrlBeforeRedirect_local,
91
+ ...rest
92
+ } = params;
93
+
94
+ log?.(`Calling loginOrGoToAuthServer ${JSON.stringify(params, null, 2)}`);
95
+
96
+ login_specific_handling: {
97
+ if (rest.action !== "login") {
98
+ break login_specific_handling;
99
+ }
100
+
101
+ if (globalContext.evtHasLoginBeenCalled.current) {
102
+ log?.("login() has already been called, ignoring the call");
103
+ return new Promise<never>(() => {});
104
+ }
105
+
106
+ globalContext.evtHasLoginBeenCalled.current = true;
107
+
108
+ if (document.visibilityState !== "visible") {
109
+ rest.interaction === "ensure no interaction";
110
+
111
+ const dVisible = new Deferred<void>();
112
+
113
+ const onVisible = () => {
114
+ if (document.visibilityState !== "visible") {
115
+ return;
116
+ }
117
+ document.removeEventListener("visibilitychange", onVisible);
118
+ dVisible.resolve();
119
+ };
120
+ document.addEventListener("visibilitychange", onVisible);
121
+
122
+ await dVisible.pr;
123
+ }
124
+
125
+ bf_cache_handling: {
126
+ if (rest.doForceReloadOnBfCache) {
127
+ window.removeEventListener("pageshow", () => {
128
+ location.reload();
129
+ });
130
+ break bf_cache_handling;
131
+ }
132
+
133
+ localStorage.setItem(LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN, "true");
134
+
135
+ const callback = () => {
136
+ window.removeEventListener("pageshow", callback);
137
+
138
+ log?.(
139
+ "We came back from the login pages and the state of the app has been restored"
140
+ );
141
+
142
+ if (rest.doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack) {
143
+ if (lastPublicUrl !== undefined) {
144
+ log?.(`Loading last public route: ${lastPublicUrl}`);
145
+ window.location.href = lastPublicUrl;
146
+ } else {
147
+ log?.("We don't know the last public route, navigating back in history");
148
+ window.history.back();
149
+ }
150
+ } else {
151
+ log?.("The current page doesn't require auth...");
152
+
153
+ if (
154
+ localStorage.getItem(LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN) === null
155
+ ) {
156
+ log?.("but the user is now authenticated, reloading the page");
157
+ location.reload();
158
+ } else {
159
+ log?.("and the user doesn't seem to be authenticated, avoiding a reload");
160
+ globalContext.evtHasLoginBeenCalled.current = false;
161
+ }
162
+ }
163
+ };
164
+
165
+ window.addEventListener("pageshow", callback);
166
+ }
167
+ }
168
+
169
+ const redirectUrl = toFullyQualifiedUrl({
170
+ urlish: redirectUrl_params,
171
+ doAssertNoQueryParams: false
172
+ });
173
+
174
+ log?.(`redirectUrl: ${redirectUrl}`);
175
+
176
+ const stateData: StateData = {
177
+ context: "redirect",
178
+ redirectUrl,
179
+ extraQueryParams: {},
180
+ hasBeenProcessedByCallback: false,
181
+ configId,
182
+ action: "login",
183
+ redirectUrl_consentRequiredCase: (() => {
184
+ switch (rest.action) {
185
+ case "login":
186
+ return lastPublicUrl ?? homeUrl;
187
+ case "go to auth server":
188
+ return redirectUrl;
189
+ }
190
+ })()
191
+ };
192
+
193
+ const isSilent = rest.action === "login" && rest.interaction === "ensure no interaction";
194
+
195
+ const transformUrl_oidcClientTs = (url: string) => {
196
+ (
197
+ [
198
+ [
199
+ getExtraQueryParams,
200
+ transformUrlBeforeRedirect === undefined
201
+ ? undefined
202
+ : (url: string) =>
203
+ transformUrlBeforeRedirect({
204
+ isSilent,
205
+ authorizationUrl: url
206
+ })
207
+ ],
208
+ [extraQueryParams_local, transformUrlBeforeRedirect_local]
209
+ ] as const
210
+ ).forEach(([extraQueryParamsMaybeGetter, transformUrlBeforeRedirect], i, arr) => {
211
+ const url_before = i !== arr.length - 1 ? undefined : url;
212
+
213
+ add_extra_query_params: {
214
+ if (extraQueryParamsMaybeGetter === undefined) {
215
+ break add_extra_query_params;
216
+ }
217
+
218
+ const extraQueryParams =
219
+ typeof extraQueryParamsMaybeGetter === "function"
220
+ ? extraQueryParamsMaybeGetter({ isSilent, url })
221
+ : extraQueryParamsMaybeGetter;
222
+
223
+ for (const [name, value] of Object.entries(extraQueryParams)) {
224
+ if (value === undefined) {
225
+ continue;
226
+ }
227
+ url = addOrUpdateSearchParam({
228
+ url,
229
+ name,
230
+ value,
231
+ encodeMethod: "www-form"
232
+ });
233
+ }
234
+ }
235
+
236
+ apply_transform_url: {
237
+ if (transformUrlBeforeRedirect === undefined) {
238
+ break apply_transform_url;
239
+ }
240
+ url = transformUrlBeforeRedirect(url);
241
+ }
242
+
243
+ update_state: {
244
+ if (url_before === undefined) {
245
+ break update_state;
246
+ }
247
+
248
+ const paramValueByName_current = getAllSearchParams(url);
249
+ const paramValueByName_before = getAllSearchParams(url_before);
250
+
251
+ for (const [name, value_current] of Object.entries(paramValueByName_current)) {
252
+ const value_before: string | undefined = paramValueByName_before[name];
253
+
254
+ if (value_before === value_current) {
255
+ continue;
256
+ }
257
+
258
+ stateData.extraQueryParams[name] = value_current;
259
+ }
260
+ }
261
+ });
262
+
263
+ return url;
264
+ };
265
+
266
+ const redirectMethod = (() => {
267
+ switch (rest.action) {
268
+ case "login":
269
+ return rest.doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack
270
+ ? "replace"
271
+ : "assign";
272
+ case "go to auth server":
273
+ return "assign";
274
+ }
275
+ })();
276
+
277
+ log?.(`redirectMethod: ${redirectMethod}`);
278
+
279
+ return oidcClientTsUserManager
280
+ .signinRedirect({
281
+ state: stateData,
282
+ redirectMethod,
283
+ prompt: (() => {
284
+ switch (rest.action) {
285
+ case "go to auth server":
286
+ return undefined;
287
+ case "login":
288
+ switch (rest.interaction) {
289
+ case "ensure no interaction":
290
+ return "none";
291
+ case "ensure interaction":
292
+ return "login";
293
+ case "directly redirect if active session show login otherwise":
294
+ return undefined;
295
+ }
296
+ assert<Equals<typeof rest.interaction, never>>;
297
+ }
298
+ assert<Equals<typeof rest, never>>;
299
+ })(),
300
+ transformUrl: transformUrl_oidcClientTs,
301
+ extraTokenParams:
302
+ getExtraTokenParams === undefined ? undefined : noUndefined(getExtraTokenParams())
303
+ })
304
+ .then(() => new Promise<never>(() => {}));
305
+ }
306
+
307
+ const { unsubscribe } = evtIsUserLoggedIn.subscribe(isLoggedIn => {
308
+ unsubscribe();
309
+
310
+ if (isLoggedIn) {
311
+ localStorage.removeItem(LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN);
312
+ } else {
313
+ const realPushState = history.pushState.bind(history);
314
+ history.pushState = function pushState(...args) {
315
+ lastPublicUrl = window.location.href;
316
+ return realPushState(...args);
317
+ };
318
+ }
319
+ });
320
+
321
+ return {
322
+ loginOrGoToAuthServer
323
+ };
324
+ }
@@ -0,0 +1,51 @@
1
+ import { assert, is } from "../vendor/frontend/tsafe";
2
+ import { Deferred } from "../tools/Deferred";
3
+
4
+ const globalContext = {
5
+ appInstanceId: Math.random().toString(36).slice(2)
6
+ };
7
+
8
+ type Message = {
9
+ appInstanceId: string;
10
+ configId: string;
11
+ };
12
+
13
+ function getChannelName(params: { configId: string }) {
14
+ const { configId } = params;
15
+ return `oidc-spa:login-propagation:${configId}`;
16
+ }
17
+
18
+ export function notifyOtherTabsOfLogin(params: { configId: string }) {
19
+ const { configId } = params;
20
+
21
+ const message: Message = {
22
+ configId,
23
+ appInstanceId: globalContext.appInstanceId
24
+ };
25
+
26
+ new BroadcastChannel(getChannelName({ configId })).postMessage(message);
27
+ }
28
+
29
+ export function getPrOtherTabLogin(params: { configId: string }) {
30
+ const { configId } = params;
31
+
32
+ const dOtherTabLogin = new Deferred<void>();
33
+
34
+ const channel = new BroadcastChannel(getChannelName({ configId }));
35
+
36
+ channel.onmessage = ({ data: message }) => {
37
+ assert(is<Message>(message));
38
+
39
+ if (message.appInstanceId === globalContext.appInstanceId) {
40
+ return;
41
+ }
42
+
43
+ channel.close();
44
+
45
+ dOtherTabLogin.resolve();
46
+ };
47
+
48
+ const prOtherTabLogin = dOtherTabLogin.pr;
49
+
50
+ return { prOtherTabLogin };
51
+ }