oidc-spa 7.2.0 → 7.2.2

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 +1 -1
  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,33 @@
1
+ export class Deferred<T> {
2
+ public readonly pr: Promise<T>;
3
+
4
+ /** NOTE: Does not need to be called bound to instance*/
5
+ public readonly resolve: (value: T) => void;
6
+ public readonly reject: (error: any) => void;
7
+
8
+ constructor() {
9
+ let resolve!: (value: T) => void;
10
+ let reject!: (error: any) => void;
11
+
12
+ this.pr = new Promise<T>((resolve_, reject_) => {
13
+ resolve = value => {
14
+ resolve_(value);
15
+ };
16
+
17
+ reject = error => {
18
+ reject_(error);
19
+ };
20
+ });
21
+
22
+ this.resolve = resolve;
23
+ this.reject = reject;
24
+ }
25
+ }
26
+
27
+ export namespace Deferred {
28
+ export type Unpack<T extends Deferred<any>> = T extends Deferred<infer U> ? U : never;
29
+ }
30
+
31
+ export class VoidDeferred extends Deferred<undefined> {
32
+ public declare readonly resolve: () => void;
33
+ }
@@ -0,0 +1,223 @@
1
+ import { assert, typeGuard, id } from "../vendor/frontend/tsafe";
2
+
3
+ type SessionStorageItem_Parsed = {
4
+ __brand: "SessionStorageItem_Parsed-v1";
5
+ value: string;
6
+ expiresAtTime: number;
7
+ };
8
+
9
+ function parseSessionStorageItem(
10
+ sessionStorageItemValue: string
11
+ ): SessionStorageItem_Parsed | undefined {
12
+ let json: unknown;
13
+
14
+ try {
15
+ json = JSON.parse(sessionStorageItemValue);
16
+ } catch {
17
+ return undefined;
18
+ }
19
+
20
+ if (
21
+ !typeGuard<SessionStorageItem_Parsed>(
22
+ json,
23
+ json instanceof Object &&
24
+ "__brand" in json &&
25
+ json.__brand === id<SessionStorageItem_Parsed["__brand"]>("SessionStorageItem_Parsed-v1")
26
+ )
27
+ ) {
28
+ return undefined;
29
+ }
30
+
31
+ return json;
32
+ }
33
+
34
+ type InMemoryItem = {
35
+ key: string;
36
+ value: string;
37
+ removeFromSessionStorage: (() => void) | undefined;
38
+ };
39
+
40
+ const SESSION_STORAGE_PREFIX = "ephemeral:";
41
+
42
+ function createStoreInSessionStorageAndScheduleRemovalInMemoryItem(params: {
43
+ key: string;
44
+ value: string;
45
+ remainingTtlMs: number;
46
+ }): InMemoryItem {
47
+ const { key, value, remainingTtlMs } = params;
48
+
49
+ const sessionStorageKey = `${SESSION_STORAGE_PREFIX}${key}`;
50
+
51
+ const removeFromSessionStorage = () => {
52
+ inMemoryItem.removeFromSessionStorage = undefined;
53
+ clearTimeout(timer);
54
+ sessionStorage.removeItem(sessionStorageKey);
55
+ };
56
+
57
+ const timer = setTimeout(() => removeFromSessionStorage(), remainingTtlMs);
58
+
59
+ const inMemoryItem: InMemoryItem = {
60
+ key,
61
+ value,
62
+ removeFromSessionStorage
63
+ };
64
+
65
+ sessionStorage.removeItem(sessionStorageKey);
66
+
67
+ sessionStorage.setItem(
68
+ sessionStorageKey,
69
+ JSON.stringify(
70
+ id<SessionStorageItem_Parsed>({
71
+ __brand: "SessionStorageItem_Parsed-v1",
72
+ value,
73
+ expiresAtTime: Date.now() + remainingTtlMs
74
+ })
75
+ )
76
+ );
77
+
78
+ return inMemoryItem;
79
+ }
80
+
81
+ export type EphemeralSessionStorage = {
82
+ // `Storage` methods, we don't use the type directly because it has [name: string]: any;
83
+ readonly length: number;
84
+ clear(): void;
85
+ getItem(key: string): string | null;
86
+ key(index: number): string | null;
87
+ removeItem(key: string): void;
88
+ setItem(key: string, value: string): void;
89
+
90
+ // Custom method
91
+ persistCurrentStateAndSubsequentChanges: () => void;
92
+ };
93
+
94
+ export function createEphemeralSessionStorage(params: {
95
+ sessionStorageTtlMs: number;
96
+ }): EphemeralSessionStorage {
97
+ const { sessionStorageTtlMs } = params;
98
+
99
+ const inMemoryItems: InMemoryItem[] = [];
100
+
101
+ for (let i = 0; i < sessionStorage.length; i++) {
102
+ const sessionStorageKey = sessionStorage.key(i);
103
+ assert(sessionStorageKey !== null, "470498");
104
+
105
+ if (!sessionStorageKey.startsWith(SESSION_STORAGE_PREFIX)) {
106
+ continue;
107
+ }
108
+
109
+ const sessionStorageItem = sessionStorage.getItem(sessionStorageKey);
110
+
111
+ assert(sessionStorageItem !== null, "846771");
112
+
113
+ const sessionStorageItem_parsed = parseSessionStorageItem(sessionStorageItem);
114
+
115
+ if (sessionStorageItem_parsed === undefined) {
116
+ continue;
117
+ }
118
+
119
+ const remainingTtlMs = sessionStorageItem_parsed.expiresAtTime - Date.now();
120
+
121
+ sessionStorage.removeItem(sessionStorageKey);
122
+
123
+ if (remainingTtlMs <= 0) {
124
+ continue;
125
+ }
126
+
127
+ inMemoryItems.push({
128
+ key: sessionStorageKey.slice(SESSION_STORAGE_PREFIX.length),
129
+ value: sessionStorageItem_parsed.value,
130
+ removeFromSessionStorage: undefined
131
+ });
132
+ }
133
+
134
+ let isPersistenceEnabled = false;
135
+
136
+ const storage: EphemeralSessionStorage = {
137
+ persistCurrentStateAndSubsequentChanges: () => {
138
+ isPersistenceEnabled = true;
139
+
140
+ for (let i = 0; i < storage.length; i++) {
141
+ const key = storage.key(i);
142
+ assert(key !== null, "803385");
143
+
144
+ const value = storage.getItem(key);
145
+
146
+ assert(value !== null, "777098");
147
+
148
+ storage.setItem(key, value);
149
+ }
150
+ },
151
+ get length() {
152
+ return inMemoryItems.length;
153
+ },
154
+ key: index => {
155
+ const inMemoryItem = inMemoryItems[index];
156
+
157
+ if (inMemoryItem === undefined) {
158
+ return null;
159
+ }
160
+
161
+ return inMemoryItem.key;
162
+ },
163
+ removeItem: key => {
164
+ const inMemoryItem = inMemoryItems.find(item => item.key === key);
165
+
166
+ if (inMemoryItem === undefined) {
167
+ return;
168
+ }
169
+
170
+ inMemoryItem.removeFromSessionStorage?.();
171
+
172
+ const index = inMemoryItems.indexOf(inMemoryItem);
173
+
174
+ inMemoryItems.splice(index, 1);
175
+ },
176
+ clear: () => {
177
+ for (let i = 0; i < storage.length; i++) {
178
+ const key = storage.key(i);
179
+ assert(key !== null, "290875");
180
+ storage.removeItem(key);
181
+ }
182
+ },
183
+ getItem: key => {
184
+ const inMemoryItem = inMemoryItems.find(item => item.key === key);
185
+ if (inMemoryItem === undefined) {
186
+ return null;
187
+ }
188
+ return inMemoryItem.value;
189
+ },
190
+ setItem: (key, value) => {
191
+ let existingInMemoryItemIndex: number | undefined = undefined;
192
+
193
+ {
194
+ const inMemoryItem = inMemoryItems.find(item => item.key === key);
195
+
196
+ if (inMemoryItem !== undefined) {
197
+ inMemoryItem.removeFromSessionStorage?.();
198
+ existingInMemoryItemIndex = inMemoryItems.indexOf(inMemoryItem);
199
+ }
200
+ }
201
+
202
+ const inMemoryItem_new = isPersistenceEnabled
203
+ ? createStoreInSessionStorageAndScheduleRemovalInMemoryItem({
204
+ key,
205
+ value,
206
+ remainingTtlMs: sessionStorageTtlMs
207
+ })
208
+ : id<InMemoryItem>({
209
+ key,
210
+ value,
211
+ removeFromSessionStorage: undefined
212
+ });
213
+
214
+ if (existingInMemoryItemIndex !== undefined) {
215
+ inMemoryItems[existingInMemoryItemIndex] = inMemoryItem_new;
216
+ } else {
217
+ inMemoryItems.push(inMemoryItem_new);
218
+ }
219
+ }
220
+ };
221
+
222
+ return storage;
223
+ }
@@ -0,0 +1,56 @@
1
+ import { Deferred } from "./Deferred";
2
+ import { assert, is } from "../vendor/frontend/tsafe";
3
+
4
+ export type NonPostableEvt<T> = {
5
+ waitFor: () => Promise<T>;
6
+ subscribe: (next: (data: T) => void) => { unsubscribe: () => void };
7
+ postCount: number;
8
+ };
9
+
10
+ export type Evt<T> = NonPostableEvt<T> & {
11
+ post: (data: T) => void;
12
+ };
13
+
14
+ export function createEvt<T>(): Evt<T> {
15
+ const eventTarget = new EventTarget();
16
+ const KEY = "event";
17
+
18
+ let postCount = 0;
19
+
20
+ const evt: Evt<T> = {
21
+ subscribe: next => {
22
+ const listener = (e: Event) => {
23
+ assert(is<CustomEvent<T>>(e));
24
+
25
+ next(e.detail);
26
+ };
27
+
28
+ eventTarget.addEventListener(KEY, listener);
29
+
30
+ return {
31
+ unsubscribe: () => {
32
+ eventTarget.removeEventListener(KEY, listener);
33
+ }
34
+ };
35
+ },
36
+ waitFor: () => {
37
+ const d = new Deferred<T>();
38
+
39
+ const { unsubscribe } = evt.subscribe(data => {
40
+ unsubscribe();
41
+ d.resolve(data);
42
+ });
43
+
44
+ return d.pr;
45
+ },
46
+ post: (data: T) => {
47
+ postCount++;
48
+ eventTarget.dispatchEvent(new CustomEvent(KEY, { detail: data }));
49
+ },
50
+ get postCount() {
51
+ return postCount;
52
+ }
53
+ };
54
+
55
+ return evt;
56
+ }
@@ -0,0 +1,38 @@
1
+ export type StatefulEvt<T> = {
2
+ current: T;
3
+ subscribe: (next: (data: T) => void) => Subscription;
4
+ };
5
+
6
+ export type StatefulReadonlyEvt<T> = {
7
+ readonly current: T;
8
+ subscribe: (next: (data: T) => void) => Subscription;
9
+ };
10
+
11
+ export type Subscription = {
12
+ unsubscribe: () => void;
13
+ };
14
+
15
+ export function createStatefulEvt<T>(getInitialValue: () => T): StatefulEvt<T> {
16
+ let nextFunctions: ((data: T) => void)[] = [];
17
+
18
+ let wrappedState: [T] | undefined = undefined;
19
+
20
+ return {
21
+ get current() {
22
+ if (wrappedState === undefined) {
23
+ wrappedState = [getInitialValue()];
24
+ }
25
+ return wrappedState[0];
26
+ },
27
+ set current(data: T) {
28
+ wrappedState = [data];
29
+
30
+ nextFunctions.forEach(next => next(data));
31
+ },
32
+ subscribe: (next: (data: T) => void) => {
33
+ nextFunctions.push(next);
34
+
35
+ return { unsubscribe: () => nextFunctions.splice(nextFunctions.indexOf(next), 1) };
36
+ }
37
+ };
38
+ }
@@ -0,0 +1 @@
1
+ export type ValueOrAsyncGetter<T> = T | (() => Promise<T>);
@@ -0,0 +1,184 @@
1
+ type AsymmetricKeys = {
2
+ publicKey: string; // base64-encoded JSON export of CryptoKey
3
+ privateKey: string; // base64-encoded JSON export of CryptoKey
4
+ };
5
+
6
+ const INFO_LABEL = "oidc-spa/tools/asymmetricEncryption";
7
+
8
+ export async function generateKeys(): Promise<AsymmetricKeys> {
9
+ const keyPair = await crypto.subtle.generateKey(
10
+ {
11
+ name: "ECDH",
12
+ namedCurve: "P-256"
13
+ },
14
+ true,
15
+ ["deriveKey", "deriveBits"]
16
+ );
17
+
18
+ const publicKeyRaw = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
19
+ const privateKeyRaw = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
20
+
21
+ return {
22
+ publicKey: btoa(JSON.stringify(publicKeyRaw)),
23
+ privateKey: btoa(JSON.stringify(privateKeyRaw))
24
+ };
25
+ }
26
+
27
+ export async function asymmetricEncrypt(params: {
28
+ publicKey: string;
29
+ message: string;
30
+ }): Promise<{ encryptedMessage: string }> {
31
+ const { publicKey, message } = params;
32
+
33
+ const importedPublicKey = await crypto.subtle.importKey(
34
+ "jwk",
35
+ JSON.parse(atob(publicKey)),
36
+ {
37
+ name: "ECDH",
38
+ namedCurve: "P-256"
39
+ },
40
+ false,
41
+ []
42
+ );
43
+
44
+ const ephemeralKeyPair = await crypto.subtle.generateKey(
45
+ {
46
+ name: "ECDH",
47
+ namedCurve: "P-256"
48
+ },
49
+ true,
50
+ ["deriveKey", "deriveBits"]
51
+ );
52
+
53
+ const sharedSecret = await crypto.subtle.deriveBits(
54
+ {
55
+ name: "ECDH",
56
+ public: importedPublicKey
57
+ },
58
+ ephemeralKeyPair.privateKey,
59
+ 256
60
+ );
61
+
62
+ const salt = crypto.getRandomValues(new Uint8Array(16));
63
+ const infoBytes = new TextEncoder().encode(INFO_LABEL);
64
+
65
+ const hkdfKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
66
+
67
+ const derivedKey = await crypto.subtle.deriveKey(
68
+ {
69
+ name: "HKDF",
70
+ hash: "SHA-256",
71
+ salt,
72
+ info: infoBytes
73
+ },
74
+ hkdfKey,
75
+ { name: "AES-GCM", length: 256 },
76
+ false,
77
+ ["encrypt"]
78
+ );
79
+
80
+ const iv = crypto.getRandomValues(new Uint8Array(12));
81
+ const encodedMessage = new TextEncoder().encode(message);
82
+
83
+ const ciphertext = await crypto.subtle.encrypt(
84
+ {
85
+ name: "AES-GCM",
86
+ iv
87
+ },
88
+ derivedKey,
89
+ encodedMessage
90
+ );
91
+
92
+ const ephemeralPubKeyRaw = await crypto.subtle.exportKey("jwk", ephemeralKeyPair.publicKey);
93
+
94
+ const payload = {
95
+ ephemeralPubKey: ephemeralPubKeyRaw,
96
+ iv: Array.from(iv),
97
+ salt: Array.from(salt),
98
+ ciphertext: Array.from(new Uint8Array(ciphertext))
99
+ };
100
+
101
+ return {
102
+ encryptedMessage: btoa(JSON.stringify(payload))
103
+ };
104
+ }
105
+
106
+ export async function asymmetricDecrypt(params: {
107
+ privateKey: string;
108
+ encryptedMessage: string;
109
+ }): Promise<{ message: string }> {
110
+ const { privateKey, encryptedMessage } = params;
111
+
112
+ const {
113
+ ephemeralPubKey,
114
+ iv,
115
+ salt,
116
+ ciphertext
117
+ }: {
118
+ ephemeralPubKey: JsonWebKey;
119
+ iv: number[];
120
+ salt: number[];
121
+ ciphertext: number[];
122
+ } = JSON.parse(atob(encryptedMessage));
123
+
124
+ const importedPrivateKey = await crypto.subtle.importKey(
125
+ "jwk",
126
+ JSON.parse(atob(privateKey)),
127
+ {
128
+ name: "ECDH",
129
+ namedCurve: "P-256"
130
+ },
131
+ false,
132
+ ["deriveKey", "deriveBits"]
133
+ );
134
+
135
+ const importedEphemeralPubKey = await crypto.subtle.importKey(
136
+ "jwk",
137
+ ephemeralPubKey,
138
+ {
139
+ name: "ECDH",
140
+ namedCurve: "P-256"
141
+ },
142
+ false,
143
+ []
144
+ );
145
+
146
+ const sharedSecret = await crypto.subtle.deriveBits(
147
+ {
148
+ name: "ECDH",
149
+ public: importedEphemeralPubKey
150
+ },
151
+ importedPrivateKey,
152
+ 256
153
+ );
154
+
155
+ const infoBytes = new TextEncoder().encode(INFO_LABEL);
156
+
157
+ const hkdfKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
158
+
159
+ const derivedKey = await crypto.subtle.deriveKey(
160
+ {
161
+ name: "HKDF",
162
+ hash: "SHA-256",
163
+ salt: new Uint8Array(salt),
164
+ info: infoBytes
165
+ },
166
+ hkdfKey,
167
+ { name: "AES-GCM", length: 256 },
168
+ false,
169
+ ["decrypt"]
170
+ );
171
+
172
+ const decryptedBuffer = await crypto.subtle.decrypt(
173
+ {
174
+ name: "AES-GCM",
175
+ iv: new Uint8Array(iv)
176
+ },
177
+ derivedKey,
178
+ new Uint8Array(ciphertext)
179
+ );
180
+
181
+ return {
182
+ message: new TextDecoder().decode(decryptedBuffer)
183
+ };
184
+ }
@@ -0,0 +1,7 @@
1
+ export function decodeBase64(encoded: string): string {
2
+ return new TextDecoder().decode(Uint8Array.from(atob(encoded), c => c.charCodeAt(0)));
3
+ }
4
+
5
+ export function encodeBase64(decoded: string): string {
6
+ return btoa(new TextEncoder().encode(decoded).reduce((acc, c) => acc + String.fromCharCode(c), ""));
7
+ }
@@ -0,0 +1,40 @@
1
+ const keyIsTrapped = "isTrapped_zSskDe9d";
2
+
3
+ export class AccessError extends Error {
4
+ constructor(message: string) {
5
+ super(message);
6
+ Object.setPrototypeOf(this, new.target.prototype);
7
+ }
8
+ }
9
+
10
+ export function createObjectThatThrowsIfAccessed<T extends object>(params?: {
11
+ debugMessage?: string;
12
+ isPropertyWhitelisted?: (prop: string | number | symbol) => boolean;
13
+ }): T {
14
+ const { debugMessage = "", isPropertyWhitelisted = () => false } = params ?? {};
15
+
16
+ const get: NonNullable<ProxyHandler<T>["get"]> = (...args) => {
17
+ const [, prop] = args;
18
+
19
+ if (isPropertyWhitelisted(prop)) {
20
+ return Reflect.get(...args);
21
+ }
22
+
23
+ if (prop === keyIsTrapped) {
24
+ return true;
25
+ }
26
+
27
+ throw new AccessError(`Cannot access ${String(prop)} yet ${debugMessage}`);
28
+ };
29
+
30
+ const trappedObject = new Proxy<T>({} as any, {
31
+ get,
32
+ set: get
33
+ });
34
+
35
+ return trappedObject;
36
+ }
37
+
38
+ export function isObjectThatThrowIfAccessed(obj: object) {
39
+ return (obj as any)[keyIsTrapped] === true;
40
+ }
@@ -0,0 +1,95 @@
1
+ // Copy pasted jwt-decode v4.0.0
2
+
3
+ export interface JwtDecodeOptions {
4
+ header?: boolean;
5
+ }
6
+
7
+ export interface JwtHeader {
8
+ typ?: string;
9
+ alg?: string;
10
+ kid?: string;
11
+ }
12
+
13
+ export interface JwtPayload {
14
+ iss?: string;
15
+ sub?: string;
16
+ aud?: string[] | string;
17
+ exp?: number;
18
+ nbf?: number;
19
+ iat?: number;
20
+ jti?: string;
21
+ }
22
+
23
+ export class InvalidTokenError extends Error {}
24
+
25
+ InvalidTokenError.prototype.name = "InvalidTokenError";
26
+
27
+ function b64DecodeUnicode(str: string) {
28
+ return decodeURIComponent(
29
+ atob(str).replace(/(.)/g, (_m, p) => {
30
+ let code = (p as string).charCodeAt(0).toString(16).toUpperCase();
31
+ if (code.length < 2) {
32
+ code = "0" + code;
33
+ }
34
+ return "%" + code;
35
+ })
36
+ );
37
+ }
38
+
39
+ function base64UrlDecode(str: string) {
40
+ let output = str.replace(/-/g, "+").replace(/_/g, "/");
41
+ switch (output.length % 4) {
42
+ case 0:
43
+ break;
44
+ case 2:
45
+ output += "==";
46
+ break;
47
+ case 3:
48
+ output += "=";
49
+ break;
50
+ default:
51
+ throw new Error("base64 string is not of the correct length");
52
+ }
53
+
54
+ try {
55
+ return b64DecodeUnicode(output);
56
+ } catch (err) {
57
+ return atob(output);
58
+ }
59
+ }
60
+
61
+ function jwtDecode<T = JwtHeader>(token: string, options: JwtDecodeOptions & { header: true }): T;
62
+ function jwtDecode<T = JwtPayload>(token: string, options?: JwtDecodeOptions): T;
63
+ function jwtDecode<T = JwtHeader | JwtPayload>(token: string, options?: JwtDecodeOptions): T {
64
+ if (typeof token !== "string") {
65
+ throw new InvalidTokenError("Invalid token specified: must be a string");
66
+ }
67
+
68
+ options ||= {};
69
+
70
+ const pos = options.header === true ? 0 : 1;
71
+ const part = token.split(".")[pos];
72
+
73
+ if (typeof part !== "string") {
74
+ throw new InvalidTokenError(`Invalid token specified: missing part #${pos + 1}`);
75
+ }
76
+
77
+ let decoded: string;
78
+ try {
79
+ decoded = base64UrlDecode(part);
80
+ } catch (e) {
81
+ throw new InvalidTokenError(
82
+ `Invalid token specified: invalid base64 for part #${pos + 1} (${(e as Error).message})`
83
+ );
84
+ }
85
+
86
+ try {
87
+ return JSON.parse(decoded) as T;
88
+ } catch (e) {
89
+ throw new InvalidTokenError(
90
+ `Invalid token specified: invalid json for part #${pos + 1} (${(e as Error).message})`
91
+ );
92
+ }
93
+ }
94
+
95
+ export const decodeJwt = jwtDecode;
@@ -0,0 +1,26 @@
1
+ export function generateUrlSafeRandom(params: { length: number }): string {
2
+ const { length } = params;
3
+
4
+ // Compute required byte length before encoding
5
+ const byteLength = Math.ceil((length * 3) / 4);
6
+
7
+ const crypto = window.crypto || (window as any).msCrypto;
8
+ const array = new Uint8Array(byteLength);
9
+ crypto.getRandomValues(array);
10
+
11
+ // Encode and apply Base64URL transformations
12
+ let base64url = btoa(String.fromCharCode(...array))
13
+ .replace(/\+/g, "-") // Base64URL encoding
14
+ .replace(/\//g, "_")
15
+ .replace(/=+$/, ""); // Remove padding
16
+
17
+ // Trim or regenerate if too short
18
+ if (base64url.length > length) {
19
+ return base64url.substring(0, length);
20
+ } else if (base64url.length < length) {
21
+ // If trimming removes too much, recursively generate more
22
+ return base64url + generateUrlSafeRandom({ length: length - base64url.length });
23
+ }
24
+
25
+ return base64url;
26
+ }