Skip to content

Commit b23906a

Browse files
iOvergaardCopilot
andauthored
V16: Unwarranted redirect after auth (#19935)
* fix: uses isAuthorized to check if user is logged in before terminating the observer * feat: adds new function to redirect to stored path * fix: always redirect to stored path even on failure the user may have landed up on the page by mistake * Revert "fix: always redirect to stored path even on failure" This reverts commit 0c0cc02. * fix: sends back the result * fix: waits for the initial authorization request to come back before listening to the authorization signal (and then only listen once for it) also check if the request was null, which means we can safely redirect the user * docs: clarify what happens * chore: converts the promise code to async/await pattern * fix: tokenResponse should validate its internal object state * feat: allows function to force a window redirect * fix: checks if the user happens to already be authorized, because then we do not need a new code check * Update src/Umbraco.Web.UI.Client/src/packages/core/utils/path/stored-path.function.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent ae8411e commit b23906a

File tree

4 files changed

+50
-21
lines changed

4 files changed

+50
-21
lines changed

src/Umbraco.Web.UI.Client/src/apps/app/app.element.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
umbExtensionsRegistry,
2020
} from '@umbraco-cms/backoffice/extension-registry';
2121
import { filter, first, firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
22-
import { hasOwnOpener, retrieveStoredPath } from '@umbraco-cms/backoffice/utils';
22+
import { hasOwnOpener, redirectToStoredPath } from '@umbraco-cms/backoffice/utils';
2323
import { UmbApiInterceptorController } from '@umbraco-cms/backoffice/resources';
2424
import { umbHttpClient } from '@umbraco-cms/backoffice/http-client';
2525

@@ -61,7 +61,7 @@ export class UmbAppElement extends UmbLitElement {
6161
{
6262
path: 'oauth_complete',
6363
component: () => import('./app-oauth.element.js'),
64-
setup: (component) => {
64+
setup: async (component) => {
6565
if (!this.#authContext) {
6666
(component as UmbAppOauthElement).failure = true;
6767
console.error('[Fatal] Auth context is not available');
@@ -76,26 +76,37 @@ export class UmbAppElement extends UmbLitElement {
7676
return;
7777
}
7878

79-
// If we are in the main window (i.e. no opener), we should redirect to the root after the authorization request is completed.
80-
// The authorization request will be completed in the active window (main or popup) and the authorization signal will be sent.
81-
// If we are in a popup window, the storage event in UmbAuthContext will catch the signal and close the window.
82-
// If we are in the main window, the signal will be caught right here and the user will be redirected to the root.
83-
if (!hasOwnOpener(this.backofficePath)) {
84-
this.observe(this.#authContext.authorizationSignal, () => {
85-
// Redirect to the saved state or root
86-
const url = retrieveStoredPath();
87-
const isBackofficePath = url?.pathname.startsWith(this.backofficePath) ?? true;
88-
89-
if (isBackofficePath) {
90-
history.replaceState(null, '', url?.toString() ?? '');
91-
} else {
92-
window.location.href = url?.toString() ?? this.backofficePath;
93-
}
94-
});
79+
// Check that we are not already authorized
80+
if (this.#authContext.getIsAuthorized()) {
81+
redirectToStoredPath(this.backofficePath, true);
82+
return;
9583
}
9684

9785
// Complete the authorization request, which will send the authorization signal
98-
this.#authContext.completeAuthorizationRequest().catch(() => undefined);
86+
try {
87+
const result = await this.#authContext.completeAuthorizationRequest();
88+
89+
if (result === null) {
90+
// If the result is null, it could mean that no new token was required, so we can redirect the user
91+
// This could happen if the user is already authorized or accidentally enters the oauth_complete url
92+
redirectToStoredPath(this.backofficePath, true);
93+
return;
94+
}
95+
96+
// If we are in the main window (i.e. no opener), we should redirect to the root after the authorization request is completed.
97+
// The authorization request will be completed in the active window (main or popup) and the authorization signal will be sent.
98+
// If we are in a popup window, the storage event in UmbAuthContext will catch the signal and close the window.
99+
// If we are in the main window, the signal will be caught right here and the user will be redirected to their previous path (or root).
100+
if (hasOwnOpener(this.backofficePath)) return;
101+
102+
// Listen for the first authorization signal after the initial authorization request
103+
await firstValueFrom(this.#authContext.authorizationSignal);
104+
// When it hits, we should redirect the user to the backoffice
105+
redirectToStoredPath(this.backofficePath);
106+
} catch {
107+
(component as UmbAppOauthElement).failure = true;
108+
console.error('[Fatal] Authorization request failed');
109+
}
99110
},
100111
},
101112
{

src/Umbraco.Web.UI.Client/src/external/openid/src/authorization_request_handler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export abstract class AuthorizationRequestHandler {
118118
/**
119119
* Completes the authorization request if necessary & when possible.
120120
*/
121-
completeAuthorizationRequestIfPossible(): Promise<void> {
121+
completeAuthorizationRequestIfPossible(): Promise<AuthorizationRequestResponse | null> {
122122
// call complete authorization if possible to see there might
123123
// be a response that needs to be delivered.
124124
log(`Checking to see if there is an authorization response to be delivered.`);
@@ -133,6 +133,7 @@ export abstract class AuthorizationRequestHandler {
133133
if (result && this.notifier) {
134134
this.notifier.onAuthorizationComplete(result.request, result.response, result.error);
135135
}
136+
return result;
136137
});
137138
}
138139

src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export class UmbAuthFlow {
236236
* @returns true if the user is logged in, false otherwise.
237237
*/
238238
isAuthorized(): boolean {
239-
return !!this.#tokenResponse;
239+
return !!this.#tokenResponse.getValue();
240240
}
241241

242242
/**

src/Umbraco.Web.UI.Client/src/packages/core/utils/path/stored-path.function.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,20 @@ export function setStoredPath(path: string): void {
3030
}
3131
sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, url.toString());
3232
}
33+
34+
/**
35+
* Redirect the user to the stored path or the base path if not available.
36+
* If the basePath matches the start of the stored path, the browser will replace the state instead of redirecting.
37+
* @param {string} basePath - The base path to redirect to if no stored path is available.
38+
* @param {boolean} [force=false] - If true, will redirect using Location
39+
*/
40+
export function redirectToStoredPath(basePath: string, force = false): void {
41+
const url = retrieveStoredPath();
42+
const isBackofficePath = url?.pathname.startsWith(basePath) ?? false;
43+
44+
if (isBackofficePath && !force) {
45+
history.replaceState(null, '', url?.toString() ?? '');
46+
} else {
47+
window.location.href = url?.toString() ?? basePath;
48+
}
49+
}

0 commit comments

Comments
 (0)