@sinequa/atomic-angular 1.0.18 → 1.0.20

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.
@@ -2880,7 +2880,7 @@ function withSavedSearchesFeatures() {
2880
2880
  return { ...state, savedSearches: [...savedSearches] };
2881
2881
  });
2882
2882
  },
2883
- getSavedSearch(search) {
2883
+ getSavedSearch(search, tab) {
2884
2884
  if (!search)
2885
2885
  return undefined;
2886
2886
  const savedSearches = store.savedSearches();
@@ -2889,7 +2889,15 @@ function withSavedSearchesFeatures() {
2889
2889
  if (!queryParams)
2890
2890
  return false;
2891
2891
  const q = queryParams.split('&').find(qp => qp.startsWith('q='));
2892
- return !!q && decodeURI(q.substring(2)) === search;
2892
+ const qMatch = !!q && decodeURI(q.substring(2)) === search;
2893
+ if (!qMatch)
2894
+ return false;
2895
+ // If tab is provided, check it; otherwise accept any tab
2896
+ if (tab) {
2897
+ const t = queryParams.split('&').find(qp => qp.startsWith('t='));
2898
+ return !!t && decodeURI(t.substring(2)) === tab;
2899
+ }
2900
+ return true;
2893
2901
  });
2894
2902
  }
2895
2903
  })));
@@ -4644,9 +4652,10 @@ class SavedSearchesService {
4644
4652
  *
4645
4653
  * @throws {Error} If saving an empty search is attempted.
4646
4654
  */
4647
- saveSearch(saveName) {
4648
- const { text } = getState(this.queryParamsStore);
4655
+ saveSearch(saveName, saveTab) {
4656
+ const { text, tab } = getState(this.queryParamsStore);
4649
4657
  const display = saveName || text;
4658
+ const tabName = saveTab || tab;
4650
4659
  if (!display) {
4651
4660
  console.error("Saving empty search is not allowed");
4652
4661
  return;
@@ -4654,6 +4663,7 @@ class SavedSearchesService {
4654
4663
  const savedSearch = {
4655
4664
  url: window.location.hash.substring(1),
4656
4665
  date: new Date().toISOString(),
4666
+ tab: tabName,
4657
4667
  display
4658
4668
  };
4659
4669
  const savedSearches = this.userSettingsStore.savedSearches();
@@ -10930,6 +10940,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10930
10940
  }]
10931
10941
  }], propDecorators: { cancel: [{ type: i0.Output, args: ["cancel"] }], success: [{ type: i0.Output, args: ["success"] }], userName: [{ type: i0.Input, args: [{ isSignal: true, alias: "userName", required: false }] }, { type: i0.Output, args: ["userNameChange"] }] } });
10932
10942
 
10943
+ /**
10944
+ * Mirrors @sinequa/atomic's `AUTH_REDIRECT_ATTEMPT_KEY` (src/authentication/constants.ts): the
10945
+ * sessionStorage flag set right before a provider full-page redirect (`window.location.href`).
10946
+ * Reading it lets us tell "an IdP redirect is under way" from "no session", without a blind timer.
10947
+ */
10948
+ const AUTH_REDIRECT_ATTEMPT_KEY = "sinequa-auth-redirect-attempt";
10933
10949
  /**
10934
10950
  * Represents the LoginComponent class, which is responsible for handling the login functionality.
10935
10951
  * This component is used to authenticate users and manage the user's authentication status.
@@ -10971,6 +10987,13 @@ class SignInComponent {
10971
10987
  authenticated = signal(isAuthenticated(), ...(ngDevMode ? [{ debugName: "authenticated" }] : []));
10972
10988
  user = signal(getState(this.principalStore), ...(ngDevMode ? [{ debugName: "user" }] : []));
10973
10989
  expiresSoonNotified = signal(false, ...(ngDevMode ? [{ debugName: "expiresSoonNotified" }] : []));
10990
+ /**
10991
+ * Forces the credentials form to show even while `externalAuth` is true. Set when a silent
10992
+ * SSO/browser auth attempt fails and no OAuth/SAML provider is configured: in that case
10993
+ * `useSSO` only meant "unknown — try SSO, then credentials", so we fall back to the form
10994
+ * instead of routing to /error.
10995
+ */
10996
+ showCredentialsFallback = signal(false, ...(ngDevMode ? [{ debugName: "showCredentialsFallback" }] : []));
10974
10997
  constructor(destroyRef) {
10975
10998
  this.destroyRef = destroyRef;
10976
10999
  // If the user is already authenticated when landing here (e.g. page refresh on
@@ -10980,21 +11003,45 @@ class SignInComponent {
10980
11003
  const url = this.route.snapshot.queryParams["returnUrl"] || "/";
10981
11004
  this.router.navigateByUrl(url);
10982
11005
  }
10983
- // When authentication is delegated to the browser/proxy (SSO) or an OAuth/SAML
10984
- // provider, no credentials form is shown: this screen shows a loader and initiates
10985
- // the handshake automatically by calling `handleLogin()`. If the handshake never
10986
- // completes, fall back to /error after 5s; the fallback is cancelled as soon as
10987
- // the login succeeds (the `authenticated` event then drives navigation).
11006
+ // When authentication is delegated to the browser/proxy (SSO) or an OAuth/SAML provider, no
11007
+ // credentials form is shown: this screen shows a loader and starts the handshake automatically.
11008
+ // Drive what happens next from the OUTCOME of `login()`, never a blind timer racing the network
11009
+ // call (the old 5s timer fired while a slow but legitimate provider call — up to its own 10s
11010
+ // network timeout — was still in flight, flashing /error just before the IdP redirect):
11011
+ // • rejected → real failure (network timeout, 5xx, callback loop) → /error WITH the reason;
11012
+ // • resolved true → authenticated → the `authenticated` event / returnUrl drives navigation;
11013
+ // • resolved false → either an OAuth/SAML full-page redirect is under way (the redirect flag was
11014
+ // set just before `window.location.href`; the page is about to unload, so do
11015
+ // nothing and let it leave for the IdP), or no provider is configured
11016
+ // (ambiguous SSO → show the credentials form), or the provider returned no
11017
+ // redirect at all (→ /error).
10988
11018
  if (this.externalAuth && !this.authenticated()) {
10989
- const timeout = setTimeout(() => {
11019
+ login()
11020
+ .then(result => {
11021
+ if (result) {
11022
+ this.auditService.notifyLogin();
11023
+ return;
11024
+ }
11025
+ const hasAutoProvider = !!(globalConfig.autoOAuthProvider || globalConfig.autoSAMLProvider);
11026
+ if (!hasAutoProvider) {
11027
+ this.showCredentialsFallback.set(true);
11028
+ return;
11029
+ }
11030
+ // A full-page redirect to the IdP was initiated → the page is about to unload. Routing to
11031
+ // /error here is exactly what caused the transient "error" flash, so skip it.
11032
+ if (sessionStorage.getItem(AUTH_REDIRECT_ATTEMPT_KEY))
11033
+ return;
11034
+ // Provider configured but the server returned no redirectUrl and there is no session → error.
10990
11035
  this.router.navigate(["/error"], {
10991
11036
  queryParams: { returnUrl: this.route.snapshot.queryParams["returnUrl"] }
10992
11037
  });
10993
- }, 5000);
10994
- destroyRef.onDestroy(() => clearTimeout(timeout));
10995
- this.handleLogin().then(result => {
10996
- if (result)
10997
- clearTimeout(timeout);
11038
+ })
11039
+ .catch((err) => {
11040
+ warn("An error occurred while logging in", err);
11041
+ this.auditService.notify({ type: "Login_Denied" });
11042
+ // Surface the failure reason on the error page (e.g. "OAuth provider not found: identity-dev").
11043
+ const message = (err?.errorMessage ?? err?.message) || undefined;
11044
+ this.router.navigate(["error"], { queryParams: { message } });
10998
11045
  });
10999
11046
  }
11000
11047
  effect(() => {
@@ -11106,7 +11153,7 @@ class SignInComponent {
11106
11153
  }
11107
11154
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SignInComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
11108
11155
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SignInComponent, isStandalone: true, selector: "signIn, signin, sign-in", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null }, password: { classPropertyName: "password", publicName: "password", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { forgotPassword: "forgotPassword", username: "usernameChange", password: "passwordChange" }, host: { properties: { "class": "cn('grid h-dvh w-full place-content-center', class())" } }, providers: [provideTranslocoScope("login")], ngImport: i0, template: `
11109
- @if (!authenticated() && !externalAuth) {
11156
+ @if (!authenticated() && (!externalAuth || showCredentialsFallback())) {
11110
11157
  <Card
11111
11158
  hover="no"
11112
11159
  cdkTrapFocus
@@ -11179,7 +11226,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
11179
11226
  CardHeaderComponent,
11180
11227
  CardContentComponent
11181
11228
  ], providers: [provideTranslocoScope("login")], template: `
11182
- @if (!authenticated() && !externalAuth) {
11229
+ @if (!authenticated() && (!externalAuth || showCredentialsFallback())) {
11183
11230
  <Card
11184
11231
  hover="no"
11185
11232
  cdkTrapFocus
@@ -15888,7 +15935,7 @@ class SavedSearchDialog {
15888
15935
  this.dialog()?.showModal();
15889
15936
  }
15890
15937
  async confirm() {
15891
- this.savedSearchesService.saveSearch(this.saveName());
15938
+ this.savedSearchesService.saveSearch(this.saveName(), this.queryParamsStore.tab?.());
15892
15939
  notify.success(this.transloco.translate("searches.saved.saved"), { duration: 2000 });
15893
15940
  // emit close output of the component
15894
15941
  this.dialog()?.close("dialog-confirm");