V15: Show a loader during the login procedures rather than oddly styled content (#17618)

* 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)

* 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

* chore: adjust imports

* chore: formatting
This commit is contained in:
Jacob Overgaard
2024-11-25 17:32:02 +01:00
committed by GitHub
parent 9c76f5cb37
commit 2383fbcd70
3 changed files with 63 additions and 19 deletions

View File

@@ -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`<umb-app-error
.errorHeadline=${this.localize.term('general_login')}
.errorMessage=${this.localize.term('errors_externalLoginFailed')}
hide-back-button></umb-app-error>`;
}
// 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`
<umb-body-layout id="loader" style="align-items:center;">
<uui-loader></uui-loader>
</umb-body-layout>
`;
}
}
export default UmbAppOauthElement;
declare global {
interface HTMLElementTagNameMap {
'umb-app-oauth': UmbAppOauthElement;
}
}

View File

@@ -20,6 +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 type { UmbAppOauthElement } from './app-oauth.element.js';
import './app-oauth.element.js';
@customElement('umb-app')
export class UmbAppElement extends UmbLitElement {
@@ -60,31 +63,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 +98,7 @@ export class UmbAppElement extends UmbLitElement {
}
// Complete the authorization request, which will send the authorization signal
this.#authContext.completeAuthorizationRequest();
this.#authContext.completeAuthorizationRequest().catch(() => undefined);
},
},
{

View File

@@ -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;