From 7ae69e0b1e0d713335dd942e8d39dccc7f3ebdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 9 Nov 2023 12:45:29 +0100 Subject: [PATCH] move responsibility of bypass --- .../src/apps/app/app.element.ts | 11 ++------ .../src/shared/auth/auth-flow.ts | 26 +++++++++++++++-- .../src/shared/auth/auth.context.ts | 28 +++++++++++++------ 3 files changed, 46 insertions(+), 19 deletions(-) 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 429faf759f..4adbcfed32 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 @@ -109,7 +109,7 @@ export class UmbAppElement extends UmbLitElement { OpenAPI.BASE = this.serverUrl; const redirectUrl = `${window.location.origin}${this.backofficePath}`; - this.#authContext = new UmbAuthContext(this, this.serverUrl, redirectUrl); + this.#authContext = new UmbAuthContext(this, this.serverUrl, redirectUrl, this.bypassAuth); this.provideContext(UMB_AUTH_CONTEXT, this.#authContext); @@ -197,13 +197,9 @@ export class UmbAppElement extends UmbLitElement { OpenAPI.WITH_CREDENTIALS = true; } + // TODO: This feels like an od placement, move this into some method regarding starting the application/not install/... this.#listenForLanguageChange(); - if (this.#authContext?.isAuthorized()) { - this.#authContext.isLoggedIn.next(true); - } else { - this.#authContext?.isLoggedIn.next(false); - } } #redirect() { @@ -244,8 +240,7 @@ export class UmbAppElement extends UmbLitElement { } #isAuthorized(): boolean { - if (!this.#authContext) return false; - return this.bypassAuth ? true : this.#authContext.isAuthorized(); + return this.#authContext?.isAuthorized() ?? false; } #isAuthorizedGuard(): Guard { diff --git a/src/Umbraco.Web.UI.Client/src/shared/auth/auth-flow.ts b/src/Umbraco.Web.UI.Client/src/shared/auth/auth-flow.ts index 8f9ec99743..f53f89028d 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/auth/auth-flow.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/auth/auth-flow.ts @@ -24,12 +24,13 @@ import { AuthorizationServiceConfiguration, GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN, - RevokeTokenRequest, + //RevokeTokenRequest, TokenRequest, TokenResponse, LocationLike, StringMap, } from '@umbraco-cms/backoffice/external/openid'; +import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; const requestor = new FetchRequestor(); @@ -82,6 +83,7 @@ class UmbNoHashQueryStringUtils extends BasicQueryStringUtils { * 4. After login, get the latest token before each request to the server by calling the `performWithFreshTokens` method */ export class UmbAuthFlow { + // handlers readonly #notifier: AuthorizationNotifier; readonly #authorizationHandler: RedirectRequestHandler; @@ -98,6 +100,9 @@ export class UmbAuthFlow { #refreshToken: string | undefined; #accessTokenResponse: TokenResponse | undefined; + readonly #authorized = new UmbBooleanState(false); + readonly authorized = this.#authorized.asObservable(); + constructor( openIdConnectUrl: string, redirectUri: string, @@ -142,7 +147,6 @@ export class UmbAuthFlow { await this.#makeRefreshTokenRequest(response.code, codeVerifier); await this.performWithFreshTokens(); - await this.#saveTokenState(); } }); } @@ -167,6 +171,7 @@ export class UmbAuthFlow { if (response.isValid()) { this.#accessTokenResponse = response; this.#refreshToken = this.#accessTokenResponse.refreshToken; + this.checkAuthorization(); } } @@ -214,7 +219,7 @@ export class UmbAuthFlow { } /** - * This method will check if the user is logged in by validating the timestamp of the stored token. + * Checks if the user is logged in by validating the timestamp of the stored token. * If no token is stored, it will return false. * * @returns true if the user is logged in, false otherwise. @@ -223,6 +228,17 @@ export class UmbAuthFlow { return !!this.#accessTokenResponse && this.#accessTokenResponse.isValid(); } + /** + * Checks if the user is logged in by validating the token, this will update authorized state as well. + * + * @returns true if the user is logged in, false otherwise. + */ + checkAuthorization() { + const authorized = this.isAuthorized(); + this.#authorized.next(authorized); + return authorized; + } + /** * This method will sign the user out of the application. */ @@ -241,6 +257,7 @@ export class UmbAuthFlow { // await this.#tokenHandler.performRevokeTokenRequest(this.#configuration, tokenRevokeRequest); this.#accessTokenResponse = undefined; + this.checkAuthorization(); } if (this.#refreshToken) { @@ -285,6 +302,8 @@ export class UmbAuthFlow { const response = await this.#tokenHandler.performTokenRequest(this.#configuration, request); this.#accessTokenResponse = response; + await this.#saveTokenState(); + this.checkAuthorization(); return response.accessToken; } @@ -320,5 +339,6 @@ export class UmbAuthFlow { const response = await this.#tokenHandler.performTokenRequest(this.#configuration, request); this.#refreshToken = response.refreshToken; this.#accessTokenResponse = response; + this.checkAuthorization(); } } diff --git a/src/Umbraco.Web.UI.Client/src/shared/auth/auth.context.ts b/src/Umbraco.Web.UI.Client/src/shared/auth/auth.context.ts index d01a714e5f..0e69a50484 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/auth/auth.context.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/auth/auth.context.ts @@ -12,14 +12,26 @@ export class UmbAuthContext extends UmbBaseController implements IUmbAuth { #currentUser = new UmbObjectState(undefined); readonly currentUser = this.#currentUser.asObservable(); - readonly isLoggedIn = new UmbBooleanState(false); + #isLoggedIn = new UmbBooleanState(false); + readonly isLoggedIn = this.#isLoggedIn.asObservable(); readonly languageIsoCode = this.#currentUser.asObservablePart((user) => user?.languageIsoCode ?? 'en-us'); #authFlow; - constructor(host: UmbControllerHostElement, serverUrl: string, redirectUrl: string) { + constructor(host: UmbControllerHostElement, serverUrl: string, redirectUrl: string, bypassAuth: boolean) { super(host) - this.#authFlow = new UmbAuthFlow(serverUrl, redirectUrl); + if(bypassAuth) { + this.#isLoggedIn.next(true); + } else { + this.#authFlow = new UmbAuthFlow(serverUrl, redirectUrl); + this.observe(this.#authFlow.authorized, (isAuthorized) => { + if (isAuthorized) { + this.#isLoggedIn.next(true); + } else { + this.#isLoggedIn.next(false); + } + }); + } this.observe(this.isLoggedIn, (isLoggedIn) => { if (isLoggedIn) { @@ -32,15 +44,15 @@ export class UmbAuthContext extends UmbBaseController implements IUmbAuth { * Initiates the login flow. */ login(): void { - return this.#authFlow.makeAuthorizationRequest(); + return this.#authFlow?.makeAuthorizationRequest(); } isAuthorized() { - return this.#authFlow.isAuthorized(); + return this.#authFlow?.isAuthorized() ?? true; } setInitialState(): Promise { - return this.#authFlow.setInitialState(); + return this.#authFlow?.setInitialState() ?? Promise.resolve(); } async fetchCurrentUser(): Promise { @@ -60,14 +72,14 @@ export class UmbAuthContext extends UmbBaseController implements IUmbAuth { * @returns The latest token from the Management API */ getLatestToken(): Promise { - return this.#authFlow.performWithFreshTokens(); + return this.#authFlow?.performWithFreshTokens() ?? Promise.resolve('bypass'); } /** * Signs the user out by removing any tokens from the browser. */ signOut(): Promise { - return this.#authFlow.signOut(); + return this.#authFlow?.signOut() ?? Promise.resolve(); } /**