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:
45
src/Umbraco.Web.UI.Client/src/apps/app/app-oauth.element.ts
Normal file
45
src/Umbraco.Web.UI.Client/src/apps/app/app-oauth.element.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user