oidc-spa 6.6.0 → 6.7.0

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.
@@ -57,11 +57,22 @@ export type ParamsOfCreateOidc<
57
57
  **/
58
58
  scopes?: string[];
59
59
  /**
60
- * Transform the url before redirecting to the login pages.
60
+ * Transform the url of the authorization endpoint before redirecting to the login pages.
61
61
  */
62
62
  transformUrlBeforeRedirect?: (url: string) => string;
63
+
63
64
  /**
64
- * Extra query params to be added on the login url.
65
+ * NOTE: Will replace transformUrlBeforeRedirect in the next major version.
66
+ *
67
+ * Transform the url (authorization endpoint) before redirecting to the login pages.
68
+ *
69
+ * The isSilent parameter is true when the redirect is initiated in the background iframe for silent signin.
70
+ * This can be used to omit ui related query parameters (like `ui_locales`).
71
+ */
72
+ transformUrlBeforeRedirect_next?: (params: { isSilent: boolean; url: string }) => string;
73
+
74
+ /**
75
+ * Extra query params to be added to the authorization endpoint url before redirecting or silent signing in.
65
76
  * You can provide a function that returns those extra query params, it will be called
66
77
  * when login() is called.
67
78
  *
@@ -69,7 +80,9 @@ export type ParamsOfCreateOidc<
69
80
  *
70
81
  * This parameter can also be passed to login() directly.
71
82
  */
72
- extraQueryParams?: Record<string, string> | (() => Record<string, string>);
83
+ extraQueryParams?:
84
+ | Record<string, string | undefined>
85
+ | ((params: { isSilent: boolean; url: string }) => Record<string, string | undefined>);
73
86
  /**
74
87
  * Extra body params to be added to the /token POST request.
75
88
  *
@@ -81,7 +94,7 @@ export type ParamsOfCreateOidc<
81
94
  * Example: extraTokenParams: ()=> ({ selectedCustomer: "xxx" })
82
95
  * extraTokenParams: { selectedCustomer: "xxx" }
83
96
  */
84
- extraTokenParams?: Record<string, string> | (() => Record<string, string>);
97
+ extraTokenParams?: Record<string, string | undefined> | (() => Record<string, string | undefined>);
85
98
  /**
86
99
  * Where to redirect after successful login.
87
100
  * Default: window.location.href (here)
@@ -246,6 +259,7 @@ export async function createOidc_nonMemoized<
246
259
  }
247
260
  ): Promise<AutoLogin extends true ? Oidc.LoggedIn<DecodedIdToken> : Oidc<DecodedIdToken>> {
248
261
  const {
262
+ transformUrlBeforeRedirect_next,
249
263
  transformUrlBeforeRedirect,
250
264
  extraQueryParams: extraQueryParamsOrGetter,
251
265
  extraTokenParams: extraTokenParamsOrGetter,
@@ -261,19 +275,29 @@ export async function createOidc_nonMemoized<
261
275
 
262
276
  const { issuerUri, clientId, scopes, configId, log } = preProcessedParams;
263
277
 
264
- const [getExtraQueryParams, getExtraTokenParams] = (
265
- [extraQueryParamsOrGetter, extraTokenParamsOrGetter] as const
266
- ).map(valueOrGetter => {
267
- if (typeof valueOrGetter === "function") {
268
- return valueOrGetter;
278
+ const getExtraQueryParams = (() => {
279
+ if (extraQueryParamsOrGetter === undefined) {
280
+ return undefined;
269
281
  }
270
282
 
271
- if (valueOrGetter !== undefined) {
272
- return () => valueOrGetter;
283
+ if (typeof extraQueryParamsOrGetter !== "function") {
284
+ return () => extraQueryParamsOrGetter;
273
285
  }
274
286
 
275
- return undefined;
276
- });
287
+ return extraQueryParamsOrGetter;
288
+ })();
289
+
290
+ const getExtraTokenParams = (() => {
291
+ if (extraTokenParamsOrGetter === undefined) {
292
+ return undefined;
293
+ }
294
+
295
+ if (typeof extraTokenParamsOrGetter !== "function") {
296
+ return () => extraTokenParamsOrGetter;
297
+ }
298
+
299
+ return extraTokenParamsOrGetter;
300
+ })();
277
301
 
278
302
  const homeAndCallbackUrl = toFullyQualifiedUrl({
279
303
  urlish: homeUrl_params,
@@ -369,8 +393,10 @@ export async function createOidc_nonMemoized<
369
393
  const { loginOrGoToAuthServer } = createLoginOrGoToAuthServer({
370
394
  configId,
371
395
  oidcClientTsUserManager,
372
- getExtraQueryParams,
373
396
  transformUrlBeforeRedirect,
397
+ transformUrlBeforeRedirect_next,
398
+ getExtraQueryParams,
399
+ getExtraTokenParams,
374
400
  homeAndCallbackUrl,
375
401
  evtIsUserLoggedIn,
376
402
  log
@@ -518,67 +544,79 @@ export async function createOidc_nonMemoized<
518
544
  };
519
545
  }
520
546
 
521
- restore_from_http_only_cookie: {
547
+ silent_login_if_possible_and_auto_login: {
522
548
  log?.("Trying to restore the auth from the http only cookie (silent signin with iframe)");
523
549
 
524
550
  const persistedAuthState = getPersistedAuthState({ configId });
525
551
 
526
- if (persistedAuthState === "explicitly logged out") {
552
+ if (persistedAuthState === "explicitly logged out" && !autoLogin) {
527
553
  log?.("Skipping silent signin with iframe, the user has logged out");
528
- break restore_from_http_only_cookie;
554
+ break silent_login_if_possible_and_auto_login;
529
555
  }
530
556
 
531
- const result_loginSilent = await loginSilent({
532
- oidcClientTsUserManager,
533
- stateQueryParamValue_instance,
534
- configId,
535
- getExtraTokenParams
536
- });
537
-
538
- assert(result_loginSilent.outcome !== "token refreshed using refresh token");
557
+ let authResponse_error: string | undefined = undefined;
558
+ let oidcClientTsUser: OidcClientTsUser | undefined = undefined;
539
559
 
540
- if (result_loginSilent.outcome === "failure") {
541
- switch (result_loginSilent.cause) {
542
- case "can't reach well-known oidc endpoint":
543
- return createWellKnownOidcConfigurationEndpointUnreachableInitializationError({
544
- issuerUri
545
- });
546
- case "timeout":
547
- return createIframeTimeoutInitializationError({
548
- homeAndCallbackUrl,
549
- clientId,
550
- issuerUri
551
- });
560
+ actual_silent_signin: {
561
+ if (persistedAuthState === "explicitly logged out") {
562
+ break actual_silent_signin;
552
563
  }
553
564
 
554
- assert<Equals<typeof result_loginSilent.cause, never>>(false);
555
- }
565
+ const result_loginSilent = await loginSilent({
566
+ oidcClientTsUserManager,
567
+ stateQueryParamValue_instance,
568
+ configId,
569
+ transformUrlBeforeRedirect_next,
570
+ getExtraQueryParams,
571
+ getExtraTokenParams
572
+ });
556
573
 
557
- assert<Equals<typeof result_loginSilent.outcome, "got auth response from iframe">>();
574
+ assert(result_loginSilent.outcome !== "token refreshed using refresh token");
558
575
 
559
- const { authResponse } = result_loginSilent;
576
+ if (result_loginSilent.outcome === "failure") {
577
+ switch (result_loginSilent.cause) {
578
+ case "can't reach well-known oidc endpoint":
579
+ return createWellKnownOidcConfigurationEndpointUnreachableInitializationError(
580
+ {
581
+ issuerUri
582
+ }
583
+ );
584
+ case "timeout":
585
+ return createIframeTimeoutInitializationError({
586
+ homeAndCallbackUrl,
587
+ clientId,
588
+ issuerUri
589
+ });
590
+ }
560
591
 
561
- log?.("Silent signin auth response", authResponse);
592
+ assert<Equals<typeof result_loginSilent.cause, never>>(false);
593
+ }
562
594
 
563
- const authResponse_error = authResponse.error;
564
- let oidcClientTsUser: OidcClientTsUser | undefined = undefined;
595
+ assert<Equals<typeof result_loginSilent.outcome, "got auth response from iframe">>();
565
596
 
566
- try {
567
- oidcClientTsUser = await oidcClientTsUserManager.signinRedirectCallback(
568
- authResponseToUrl(authResponse)
569
- );
570
- } catch (error) {
571
- assert(error instanceof Error);
597
+ const { authResponse } = result_loginSilent;
572
598
 
573
- if (error.message === "Failed to fetch") {
574
- return createFailedToFetchTokenEndpointInitializationError({
575
- clientId,
576
- issuerUri
577
- });
578
- }
599
+ log?.("Silent signin auth response", authResponse);
600
+
601
+ authResponse_error = authResponse.error;
579
602
 
580
- if (authResponse_error === undefined) {
581
- return error;
603
+ try {
604
+ oidcClientTsUser = await oidcClientTsUserManager.signinRedirectCallback(
605
+ authResponseToUrl(authResponse)
606
+ );
607
+ } catch (error) {
608
+ assert(error instanceof Error);
609
+
610
+ if (error.message === "Failed to fetch") {
611
+ return createFailedToFetchTokenEndpointInitializationError({
612
+ clientId,
613
+ issuerUri
614
+ });
615
+ }
616
+
617
+ if (authResponse_error === undefined) {
618
+ return error;
619
+ }
582
620
  }
583
621
  }
584
622
 
@@ -612,7 +650,7 @@ export async function createOidc_nonMemoized<
612
650
  doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: autoLogin,
613
651
  extraQueryParams_local: undefined,
614
652
  transformUrlBeforeRedirect_local: undefined,
615
- doForceInteraction: false
653
+ doForceInteraction: persistedAuthState === "explicitly logged out"
616
654
  });
617
655
  assert(false);
618
656
  }
@@ -626,7 +664,7 @@ export async function createOidc_nonMemoized<
626
664
  ].join("")
627
665
  );
628
666
 
629
- break restore_from_http_only_cookie;
667
+ break silent_login_if_possible_and_auto_login;
630
668
  }
631
669
 
632
670
  log?.("Successful silent signed in");
@@ -876,7 +914,9 @@ export async function createOidc_nonMemoized<
876
914
  return new Promise<never>(() => {});
877
915
  },
878
916
  renewTokens: (() => {
879
- async function renewTokens_nonMutexed(params: { extraTokenParams: Record<string, string> }) {
917
+ async function renewTokens_nonMutexed(params: {
918
+ extraTokenParams: Record<string, string | undefined>;
919
+ }) {
880
920
  const { extraTokenParams } = params;
881
921
 
882
922
  log?.("Renewing tokens");
@@ -887,6 +927,8 @@ export async function createOidc_nonMemoized<
887
927
  oidcClientTsUserManager,
888
928
  stateQueryParamValue_instance,
889
929
  configId,
930
+ transformUrlBeforeRedirect_next,
931
+ getExtraQueryParams,
890
932
  getExtraTokenParams: () => extraTokenParams
891
933
  });
892
934
 
@@ -989,7 +1031,7 @@ export async function createOidc_nonMemoized<
989
1031
  let ongoingCall:
990
1032
  | {
991
1033
  pr: Promise<void>;
992
- extraTokenParams: Record<string, string>;
1034
+ extraTokenParams: Record<string, string | undefined>;
993
1035
  }
994
1036
  | undefined = undefined;
995
1037
 
@@ -1,7 +1,7 @@
1
1
  import type { UserManager as OidcClientTsUserManager } from "../vendor/frontend/oidc-client-ts-and-jwt-decode";
2
2
  import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
3
- import { id, assert, type Equals } from "../vendor/frontend/tsafe";
4
- import type { StateData } from "./StateData";
3
+ import { assert, type Equals, noUndefined } from "../vendor/frontend/tsafe";
4
+ import { StateData } from "./StateData";
5
5
  import type { NonPostableEvt } from "../tools/Evt";
6
6
  import { type StatefulEvt, createStatefulEvt } from "../tools/StatefulEvt";
7
7
  import { Deferred } from "../tools/Deferred";
@@ -27,7 +27,7 @@ type Params = Params.Login | Params.GoToAuthServer;
27
27
  namespace Params {
28
28
  type Common = {
29
29
  redirectUrl: string;
30
- extraQueryParams_local: Record<string, string> | undefined;
30
+ extraQueryParams_local: Record<string, string | undefined> | undefined;
31
31
  transformUrlBeforeRedirect_local: ((url: string) => string) | undefined;
32
32
  };
33
33
 
@@ -59,8 +59,15 @@ export function getPrSafelyRestoredFromBfCacheAfterLoginBackNavigation() {
59
59
  export function createLoginOrGoToAuthServer(params: {
60
60
  configId: string;
61
61
  oidcClientTsUserManager: OidcClientTsUserManager;
62
- getExtraQueryParams: (() => Record<string, string>) | undefined;
63
62
  transformUrlBeforeRedirect: ((url: string) => string) | undefined;
63
+ transformUrlBeforeRedirect_next: ((params: { isSilent: false; url: string }) => string) | undefined;
64
+
65
+ getExtraQueryParams:
66
+ | ((params: { isSilent: false; url: string }) => Record<string, string | undefined>)
67
+ | undefined;
68
+
69
+ getExtraTokenParams: (() => Record<string, string | undefined>) | undefined;
70
+
64
71
  homeAndCallbackUrl: string;
65
72
  evtIsUserLoggedIn: NonPostableEvt<boolean>;
66
73
  log: typeof console.log | undefined;
@@ -68,8 +75,13 @@ export function createLoginOrGoToAuthServer(params: {
68
75
  const {
69
76
  configId,
70
77
  oidcClientTsUserManager,
71
- getExtraQueryParams,
78
+
72
79
  transformUrlBeforeRedirect,
80
+ transformUrlBeforeRedirect_next,
81
+ getExtraQueryParams,
82
+
83
+ getExtraTokenParams,
84
+
73
85
  homeAndCallbackUrl,
74
86
  evtIsUserLoggedIn,
75
87
  log
@@ -83,7 +95,7 @@ export function createLoginOrGoToAuthServer(params: {
83
95
  const {
84
96
  redirectUrl: redirectUrl_params,
85
97
  extraQueryParams_local,
86
- transformUrlBeforeRedirect_local,
98
+ transformUrlBeforeRedirect_local: transformUrl,
87
99
  ...rest
88
100
  } = params;
89
101
 
@@ -152,33 +164,82 @@ export function createLoginOrGoToAuthServer(params: {
152
164
 
153
165
  log?.(`redirectUrl: ${redirectUrl}`);
154
166
 
167
+ const stateData: StateData = {
168
+ context: "redirect",
169
+ redirectUrl,
170
+ extraQueryParams: {},
171
+ hasBeenProcessedByCallback: false,
172
+ configId,
173
+ action: "login",
174
+ redirectUrl_consentRequiredCase: (() => {
175
+ switch (rest.action) {
176
+ case "login":
177
+ return lastPublicUrl ?? homeAndCallbackUrl;
178
+ case "go to auth server":
179
+ return redirectUrl;
180
+ }
181
+ })()
182
+ };
183
+
155
184
  const transformUrl_oidcClientTs = (url: string) => {
156
185
  (
157
186
  [
158
- [getExtraQueryParams?.(), transformUrlBeforeRedirect],
159
- [extraQueryParams_local, transformUrlBeforeRedirect_local]
187
+ [
188
+ undefined,
189
+ transformUrlBeforeRedirect_next === undefined
190
+ ? undefined
191
+ : (url: string) => transformUrlBeforeRedirect_next({ url, isSilent: false })
192
+ ],
193
+ [getExtraQueryParams, transformUrlBeforeRedirect],
194
+ [extraQueryParams_local, transformUrl]
160
195
  ] as const
161
- ).forEach(([extraQueryParams, transformUrlBeforeRedirect]) => {
196
+ ).forEach(([extraQueryParamsMaybeGetter, transformUrlBeforeRedirect], i) => {
197
+ const urlObj_before = i !== 2 ? undefined : new URL(url);
198
+
162
199
  add_extra_query_params: {
163
- if (extraQueryParams === undefined) {
200
+ if (extraQueryParamsMaybeGetter === undefined) {
164
201
  break add_extra_query_params;
165
202
  }
166
203
 
204
+ const extraQueryParams =
205
+ typeof extraQueryParamsMaybeGetter === "function"
206
+ ? extraQueryParamsMaybeGetter({ isSilent: false, url })
207
+ : extraQueryParamsMaybeGetter;
208
+
167
209
  const url_obj = new URL(url);
168
210
 
169
211
  for (const [name, value] of Object.entries(extraQueryParams)) {
212
+ if (value === undefined) {
213
+ continue;
214
+ }
170
215
  url_obj.searchParams.set(name, value);
171
216
  }
172
217
 
173
218
  url = url_obj.href;
174
219
  }
175
220
 
176
- apply_transform_before_redirect: {
221
+ apply_transform_url: {
177
222
  if (transformUrlBeforeRedirect === undefined) {
178
- break apply_transform_before_redirect;
223
+ break apply_transform_url;
179
224
  }
180
225
  url = transformUrlBeforeRedirect(url);
181
226
  }
227
+
228
+ update_state: {
229
+ if (urlObj_before === undefined) {
230
+ break update_state;
231
+ }
232
+
233
+ for (const [name, value] of new URL(url).searchParams.entries()) {
234
+ const value_before = urlObj_before.searchParams.get(name);
235
+
236
+ if (value_before === value) {
237
+ continue;
238
+ }
239
+
240
+ stateData.extraQueryParams[name] = value;
241
+ }
242
+ }
182
243
  });
183
244
 
184
245
  return url;
@@ -197,48 +258,9 @@ export function createLoginOrGoToAuthServer(params: {
197
258
 
198
259
  log?.(`redirectMethod: ${redirectMethod}`);
199
260
 
200
- const { extraQueryParams } = (() => {
201
- const extraQueryParams: Record<string, string> = extraQueryParams_local ?? {};
202
-
203
- read_query_params_added_by_transform_before_redirect: {
204
- if (transformUrlBeforeRedirect_local === undefined) {
205
- break read_query_params_added_by_transform_before_redirect;
206
- }
207
-
208
- let url_afterTransform;
209
-
210
- try {
211
- url_afterTransform = transformUrlBeforeRedirect_local("https://dummy.com");
212
- } catch {
213
- break read_query_params_added_by_transform_before_redirect;
214
- }
215
-
216
- for (const [name, value] of new URL(url_afterTransform).searchParams) {
217
- extraQueryParams[name] = value;
218
- }
219
- }
220
-
221
- return { extraQueryParams };
222
- })();
223
-
224
261
  return oidcClientTsUserManager
225
262
  .signinRedirect({
226
- state: id<StateData>({
227
- context: "redirect",
228
- redirectUrl,
229
- extraQueryParams,
230
- hasBeenProcessedByCallback: false,
231
- configId,
232
- action: "login",
233
- redirectUrl_consentRequiredCase: (() => {
234
- switch (rest.action) {
235
- case "login":
236
- return lastPublicUrl ?? homeAndCallbackUrl;
237
- case "go to auth server":
238
- return redirectUrl;
239
- }
240
- })()
241
- }),
263
+ state: stateData,
242
264
  redirectMethod,
243
265
  prompt: (() => {
244
266
  switch (rest.action) {
@@ -249,7 +271,9 @@ export function createLoginOrGoToAuthServer(params: {
249
271
  }
250
272
  assert<Equals<typeof rest, never>>;
251
273
  })(),
252
- transformUrl: transformUrl_oidcClientTs
274
+ transformUrl: transformUrl_oidcClientTs,
275
+ extraTokenParams:
276
+ getExtraTokenParams === undefined ? undefined : noUndefined(getExtraTokenParams())
253
277
  })
254
278
  .then(() => new Promise<never>(() => {}));
255
279
  }
@@ -1,6 +1,6 @@
1
1
  import type { UserManager as OidcClientTsUserManager } from "../vendor/frontend/oidc-client-ts-and-jwt-decode";
2
2
  import { Deferred } from "../tools/Deferred";
3
- import { id, assert } from "../vendor/frontend/tsafe";
3
+ import { id, assert, noUndefined } from "../vendor/frontend/tsafe";
4
4
  import { getStateData, clearStateStore, type StateData } from "./StateData";
5
5
  import { getDownlinkAndRtt } from "../tools/getDownlinkAndRtt";
6
6
  import { getIsDev } from "../tools/isDev";
@@ -25,10 +25,23 @@ export async function loginSilent(params: {
25
25
  oidcClientTsUserManager: OidcClientTsUserManager;
26
26
  stateQueryParamValue_instance: string;
27
27
  configId: string;
28
- getExtraTokenParams: (() => Record<string, string>) | undefined;
28
+
29
+ transformUrlBeforeRedirect_next: ((params: { isSilent: true; url: string }) => string) | undefined;
30
+
31
+ getExtraQueryParams:
32
+ | ((params: { isSilent: true; url: string }) => Record<string, string | undefined>)
33
+ | undefined;
34
+
35
+ getExtraTokenParams: (() => Record<string, string | undefined>) | undefined;
29
36
  }): Promise<ResultOfLoginSilent> {
30
- const { oidcClientTsUserManager, stateQueryParamValue_instance, configId, getExtraTokenParams } =
31
- params;
37
+ const {
38
+ oidcClientTsUserManager,
39
+ stateQueryParamValue_instance,
40
+ configId,
41
+ transformUrlBeforeRedirect_next,
42
+ getExtraQueryParams,
43
+ getExtraTokenParams
44
+ } = params;
32
45
 
33
46
  const dResult = new Deferred<ResultOfLoginSilent>();
34
47
 
@@ -88,6 +101,36 @@ export async function loginSilent(params: {
88
101
 
89
102
  window.addEventListener("message", listener, false);
90
103
 
104
+ const transformUrl_oidcClientTs = (url: string) => {
105
+ add_extra_query_params: {
106
+ if (getExtraQueryParams === undefined) {
107
+ break add_extra_query_params;
108
+ }
109
+
110
+ const extraQueryParams = getExtraQueryParams({ isSilent: true, url });
111
+
112
+ const url_obj = new URL(url);
113
+
114
+ for (const [name, value] of Object.entries(extraQueryParams)) {
115
+ if (value === undefined) {
116
+ continue;
117
+ }
118
+ url_obj.searchParams.set(name, value);
119
+ }
120
+
121
+ url = url_obj.href;
122
+ }
123
+
124
+ apply_transform_url: {
125
+ if (transformUrlBeforeRedirect_next === undefined) {
126
+ break apply_transform_url;
127
+ }
128
+ url = transformUrlBeforeRedirect_next({ url, isSilent: true });
129
+ }
130
+
131
+ return url;
132
+ };
133
+
91
134
  oidcClientTsUserManager
92
135
  .signinSilent({
93
136
  state: id<StateData.IFrame>({
@@ -95,7 +138,9 @@ export async function loginSilent(params: {
95
138
  configId
96
139
  }),
97
140
  silentRequestTimeoutInSeconds: timeoutDelayMs / 1000,
98
- extraTokenParams: getExtraTokenParams?.()
141
+ extraTokenParams:
142
+ getExtraTokenParams === undefined ? undefined : noUndefined(getExtraTokenParams()),
143
+ transformUrl: transformUrl_oidcClientTs
99
144
  })
100
145
  .then(
101
146
  oidcClientTsUser => {
@@ -2,4 +2,5 @@ export { id } from "tsafe/id";
2
2
  export { assert, is, type Equals } from "tsafe/assert";
3
3
  export { typeGuard } from "tsafe/typeGuard";
4
4
  export { overwriteReadonlyProp } from "tsafe/lab/overwriteReadonlyProp";
5
+ export { noUndefined } from "tsafe/noUndefined";
5
6
  export type { Param0 } from "tsafe";