@sinequa/atomic-angular 1.6.0 → 1.6.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.
@@ -4470,9 +4470,11 @@ class PreviewService {
4470
4470
  getEntityId(entity, value, index) {
4471
4471
  if (!this.previewData)
4472
4472
  return undefined;
4473
- // Combine filters for better performance
4473
+ // Combine filters for better performance.
4474
+ // NB: positionInCategories[entity] is a zero-based index, so the first occurrence is 0.
4475
+ // Use a presence check (not truthiness) to avoid dropping that first occurrence.
4474
4476
  const entitySearched = this.previewData.highlightsPerLocation
4475
- .filter((item) => item.positionInCategories[entity] && item.values.some((v) => v === value))
4477
+ .filter((item) => item.positionInCategories[entity] !== undefined && item.values.some((v) => v === value))
4476
4478
  .map((e) => ({ id: e.positionInCategories[entity] }));
4477
4479
  const id = entitySearched[index]?.id;
4478
4480
  return id;
@@ -10881,6 +10883,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
10881
10883
  }]
10882
10884
  }] });
10883
10885
 
10886
+ /**
10887
+ * Mirrors @sinequa/atomic's `AUTH_REDIRECT_ATTEMPT_KEY` (src/authentication/constants.ts): the
10888
+ * sessionStorage flag set right before a provider full-page redirect (`window.location.href`).
10889
+ * Reading it lets us tell "an IdP redirect is under way" from "no session", without a blind timer.
10890
+ */
10891
+ const AUTH_REDIRECT_ATTEMPT_KEY = "sinequa-auth-redirect-attempt";
10884
10892
  /**
10885
10893
  * Represents the LoginComponent class, which is responsible for handling the login functionality.
10886
10894
  * This component is used to authenticate users and manage the user's authentication status.
@@ -10922,6 +10930,13 @@ class SignInComponent {
10922
10930
  authenticated = signal(isAuthenticated(), ...(ngDevMode ? [{ debugName: "authenticated" }] : []));
10923
10931
  user = signal(getState(this.principalStore), ...(ngDevMode ? [{ debugName: "user" }] : []));
10924
10932
  expiresSoonNotified = signal(false, ...(ngDevMode ? [{ debugName: "expiresSoonNotified" }] : []));
10933
+ /**
10934
+ * Forces the credentials form to show even while `externalAuth` is true. Set when a silent
10935
+ * SSO/browser auth attempt fails and no OAuth/SAML provider is configured: in that case
10936
+ * `useSSO` only meant "unknown — try SSO, then credentials", so we fall back to the form
10937
+ * instead of routing to /error.
10938
+ */
10939
+ showCredentialsFallback = signal(false, ...(ngDevMode ? [{ debugName: "showCredentialsFallback" }] : []));
10925
10940
  constructor(destroyRef) {
10926
10941
  this.destroyRef = destroyRef;
10927
10942
  // If the user is already authenticated when landing here (e.g. page refresh on
@@ -10931,21 +10946,45 @@ class SignInComponent {
10931
10946
  const url = this.route.snapshot.queryParams["returnUrl"] || "/";
10932
10947
  this.router.navigateByUrl(url);
10933
10948
  }
10934
- // When authentication is delegated to the browser/proxy (SSO) or an OAuth/SAML
10935
- // provider, no credentials form is shown: this screen shows a loader and initiates
10936
- // the handshake automatically by calling `handleLogin()`. If the handshake never
10937
- // completes, fall back to /error after 5s; the fallback is cancelled as soon as
10938
- // the login succeeds (the `authenticated` event then drives navigation).
10949
+ // When authentication is delegated to the browser/proxy (SSO) or an OAuth/SAML provider, no
10950
+ // credentials form is shown: this screen shows a loader and starts the handshake automatically.
10951
+ // Drive what happens next from the OUTCOME of `login()`, never a blind timer racing the network
10952
+ // call (the old 5s timer fired while a slow but legitimate provider call — up to its own 10s
10953
+ // network timeout was still in flight, flashing /error just before the IdP redirect):
10954
+ // • rejected → real failure (network timeout, 5xx, callback loop) → /error WITH the reason;
10955
+ // • resolved true → authenticated → the `authenticated` event / returnUrl drives navigation;
10956
+ // • resolved false → either an OAuth/SAML full-page redirect is under way (the redirect flag was
10957
+ // set just before `window.location.href`; the page is about to unload, so do
10958
+ // nothing and let it leave for the IdP), or no provider is configured
10959
+ // (ambiguous SSO → show the credentials form), or the provider returned no
10960
+ // redirect at all (→ /error).
10939
10961
  if (this.externalAuth && !this.authenticated()) {
10940
- const timeout = setTimeout(() => {
10962
+ login()
10963
+ .then(result => {
10964
+ if (result) {
10965
+ this.auditService.notifyLogin();
10966
+ return;
10967
+ }
10968
+ const hasAutoProvider = !!(globalConfig.autoOAuthProvider || globalConfig.autoSAMLProvider);
10969
+ if (!hasAutoProvider) {
10970
+ this.showCredentialsFallback.set(true);
10971
+ return;
10972
+ }
10973
+ // A full-page redirect to the IdP was initiated → the page is about to unload. Routing to
10974
+ // /error here is exactly what caused the transient "error" flash, so skip it.
10975
+ if (sessionStorage.getItem(AUTH_REDIRECT_ATTEMPT_KEY))
10976
+ return;
10977
+ // Provider configured but the server returned no redirectUrl and there is no session → error.
10941
10978
  this.router.navigate(["/error"], {
10942
10979
  queryParams: { returnUrl: this.route.snapshot.queryParams["returnUrl"] }
10943
10980
  });
10944
- }, 5000);
10945
- destroyRef.onDestroy(() => clearTimeout(timeout));
10946
- this.handleLogin().then(result => {
10947
- if (result)
10948
- clearTimeout(timeout);
10981
+ })
10982
+ .catch((err) => {
10983
+ warn("An error occurred while logging in", err);
10984
+ this.auditService.notify({ type: "Login_Denied" });
10985
+ // Surface the failure reason on the error page (e.g. "OAuth provider not found: identity-dev").
10986
+ const message = (err?.errorMessage ?? err?.message) || undefined;
10987
+ this.router.navigate(["error"], { queryParams: { message } });
10949
10988
  });
10950
10989
  }
10951
10990
  effect(() => {
@@ -11057,7 +11096,7 @@ class SignInComponent {
11057
11096
  }
11058
11097
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SignInComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
11059
11098
  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: `
11060
- @if (!authenticated() && !externalAuth) {
11099
+ @if (!authenticated() && (!externalAuth || showCredentialsFallback())) {
11061
11100
  <Card
11062
11101
  hover="no"
11063
11102
  cdkTrapFocus
@@ -11130,7 +11169,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
11130
11169
  CardHeaderComponent,
11131
11170
  CardContentComponent
11132
11171
  ], providers: [provideTranslocoScope("login")], template: `
11133
- @if (!authenticated() && !externalAuth) {
11172
+ @if (!authenticated() && (!externalAuth || showCredentialsFallback())) {
11134
11173
  <Card
11135
11174
  hover="no"
11136
11175
  cdkTrapFocus