From 26f1734b671623e7c7d4cd3d850c69f0865a7cd5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:28:37 +0100 Subject: [PATCH 1/4] feat: show only a loader on a default login flow if the flow fails, the app-error component will be shown, or if the flow is initialised inside a popup (i.e. the session was lost) --- .../src/apps/app/app-oauth.element.ts | 45 +++++++++++++++++++ .../src/apps/app/app.element.ts | 29 ++++++------ 2 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/apps/app/app-oauth.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app-oauth.element.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app-oauth.element.ts new file mode 100644 index 000000000000..658e390d1bf7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app-oauth.element.ts @@ -0,0 +1,45 @@ +import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +import './app-error.element.js'; + +/** + * A full page error element that can be used either solo or for instance as the error 500 page and BootFailed + */ +@customElement('umb-app-oauth') +export class UmbAppOauthElement extends UmbLitElement { + + /** + * Set to true if the login failed. A message will be shown instead of the loader. + * @attr + */ + @property({ type: Boolean }) + failure = false; + + override render() { + // If we have a message, we show the error page + // this is most likely happening inside a popup + if (this.failure) { + return html``; + } + + // If we don't have a message, we show the loader, this is most likely happening in the main app + // for the normal login flow + return html` + + + + `; + } +} + +export default UmbAppOauthElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-app-oauth': UmbAppOauthElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts index d48bbbd57dac..752bffd7a12c 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts @@ -20,6 +20,7 @@ import { } from '@umbraco-cms/backoffice/extension-registry'; import { filter, first, firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; import { hasOwnOpener, retrieveStoredPath } from '@umbraco-cms/backoffice/utils'; +import UmbAppOauthElement from "./app-oauth.element.ts"; @customElement('umb-app') export class UmbAppElement extends UmbLitElement { @@ -60,31 +61,27 @@ export class UmbAppElement extends UmbLitElement { }, { path: 'oauth_complete', - component: () => import('./app-error.element.js'), + component: () => import('./app-oauth.element.js'), setup: (component) => { if (!this.#authContext) { - throw new Error('[Fatal] Auth context is not available'); + (component as UmbAppOauthElement).failure = true; + console.error('[Fatal] Auth context is not available'); + return; } const searchParams = new URLSearchParams(window.location.search); const hasCode = searchParams.has('code'); - (component as UmbAppErrorElement).hideBackButton = true; - (component as UmbAppErrorElement).errorHeadline = this.localize.term('general_login'); + if (!hasCode) { + (component as UmbAppOauthElement).failure = true; + console.error('[Fatal] No code in query parameters'); + return; + } - // If there is an opener, we are in a popup window, and we should show a different message - // than if we are in the main window. If we are in the main window, we should redirect to the root. + // If we are in the main window (i.e. no opener), we should redirect to the root after the authorization request is completed. // The authorization request will be completed in the active window (main or popup) and the authorization signal will be sent. // If we are in a popup window, the storage event in UmbAuthContext will catch the signal and close the window. // If we are in the main window, the signal will be caught right here and the user will be redirected to the root. - if (hasOwnOpener(this.backofficePath)) { - (component as UmbAppErrorElement).errorMessage = hasCode - ? this.localize.term('errors_externalLoginSuccess') - : this.localize.term('errors_externalLoginFailed'); - } else { - (component as UmbAppErrorElement).errorMessage = hasCode - ? this.localize.term('errors_externalLoginRedirectSuccess') - : this.localize.term('errors_externalLoginFailed'); - + if (!hasOwnOpener(this.backofficePath)) { this.observe(this.#authContext.authorizationSignal, () => { // Redirect to the saved state or root const url = retrieveStoredPath(); @@ -99,7 +96,7 @@ export class UmbAppElement extends UmbLitElement { } // Complete the authorization request, which will send the authorization signal - this.#authContext.completeAuthorizationRequest(); + this.#authContext.completeAuthorizationRequest().catch(() => undefined); }, }, { From a352e9d2ffce59688b26bc7cd82af669f9001bbe Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:29:52 +0100 Subject: [PATCH 2/4] fix: hasOwnOpener did not recognize the local vite url as its own pathname it should work better by checking the `startsWith` comparing the pathname, and besides, it seems to work better for the understanding of the function to inverse the true/false check --- .../src/packages/core/utils/path/has-own-opener.function.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/has-own-opener.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/has-own-opener.function.ts index 72e72090af2b..3fda364240ed 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/has-own-opener.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/has-own-opener.function.ts @@ -22,11 +22,11 @@ export function hasOwnOpener(pathname?: string, windowLike: Window = globalThis. return false; } - if (pathname && openerLocation.pathname !== pathname) { - return false; + if (pathname && openerLocation.pathname.startsWith(pathname)) { + return true; } - return true; + return false; } catch { // If there is a security error, it means that the opener is from a different origin, so we let it fall through return false; From 264cdf815a8360325335fc407e5c4756e6d7bef4 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:52:24 +0100 Subject: [PATCH 3/4] chore: adjust imports --- src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts index 752bffd7a12c..8b7abc3dc5a5 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts @@ -20,7 +20,9 @@ import { } from '@umbraco-cms/backoffice/extension-registry'; import { filter, first, firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; import { hasOwnOpener, retrieveStoredPath } from '@umbraco-cms/backoffice/utils'; -import UmbAppOauthElement from "./app-oauth.element.ts"; +import type { UmbAppOauthElement } from "./app-oauth.element.js"; + +import './app-oauth.element.js'; @customElement('umb-app') export class UmbAppElement extends UmbLitElement { From 8a26424807eb4449db92cc5e88266474e447a4d5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:53:09 +0100 Subject: [PATCH 4/4] chore: formatting --- src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts index 8b7abc3dc5a5..57bd044230e1 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts @@ -20,7 +20,7 @@ import { } from '@umbraco-cms/backoffice/extension-registry'; import { filter, first, firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; import { hasOwnOpener, retrieveStoredPath } from '@umbraco-cms/backoffice/utils'; -import type { UmbAppOauthElement } from "./app-oauth.element.js"; +import type { UmbAppOauthElement } from './app-oauth.element.js'; import './app-oauth.element.js';