Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/Umbraco.Web.UI.Client/src/apps/app/app-oauth.element.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
31 changes: 15 additions & 16 deletions src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand All @@ -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);
},
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading