{"version":3,"file":"index.js","sources":["../../../../src/packages/core/auth/auth.context.ts","../../../../src/packages/core/auth/modals/umb-app-auth-modal.token.ts"],"sourcesContent":["import { UmbAuthFlow } from './auth-flow.js';\r\nimport { UMB_AUTH_CONTEXT } from './auth.context.token.js';\r\nimport type { UmbOpenApiConfiguration } from './models/openApiConfiguration.js';\r\nimport type { ManifestAuthProvider } from './auth-provider.extension.js';\r\nimport { UMB_STORAGE_TOKEN_RESPONSE_NAME } from './constants.js';\r\nimport { OpenAPI } from '@umbraco-cms/backoffice/external/backend-api';\r\nimport type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';\r\nimport { UmbContextBase } from '@umbraco-cms/backoffice/class-api';\r\nimport { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';\r\nimport { ReplaySubject, Subject, firstValueFrom, switchMap } from '@umbraco-cms/backoffice/external/rxjs';\r\nimport type { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry';\r\n\r\nexport class UmbAuthContext extends UmbContextBase {\r\n\t#isAuthorized = new UmbBooleanState(false);\r\n\t// Timeout is different from `isAuthorized` because it can occur repeatedly\r\n\t#isTimeout = new Subject();\r\n\t#isInitialized = new ReplaySubject(1);\r\n\t#isBypassed;\r\n\t#serverUrl;\r\n\t#backofficePath;\r\n\t#authFlow;\r\n\r\n\t#authWindowProxy?: WindowProxy | null;\r\n\t#previousAuthUrl?: string;\r\n\r\n\t/**\r\n\t * Observable that emits true when the auth context is initialized.\r\n\t * @remark It will only emit once and then complete itself.\r\n\t */\r\n\treadonly isInitialized = this.#isInitialized.asObservable();\r\n\r\n\t/**\r\n\t * Observable that emits true if the user is authorized, otherwise false.\r\n\t * @remark It will only emit when the authorization state changes.\r\n\t */\r\n\treadonly isAuthorized = this.#isAuthorized.asObservable();\r\n\r\n\t/**\r\n\t * Observable that acts as a signal and emits when the user has timed out, i.e. the token has expired.\r\n\t * This can be used to show a timeout message to the user.\r\n\t * @remark It can emit multiple times if more than one request is made after the token has expired.\r\n\t */\r\n\treadonly timeoutSignal = this.#isTimeout.asObservable();\r\n\r\n\t/**\r\n\t * Observable that acts as a signal for when the authorization state changes.\r\n\t */\r\n\tget authorizationSignal() {\r\n\t\treturn this.#authFlow.authorizationSignal;\r\n\t}\r\n\r\n\tconstructor(host: UmbControllerHost, serverUrl: string, backofficePath: string, isBypassed: boolean) {\r\n\t\tsuper(host, UMB_AUTH_CONTEXT);\r\n\t\tthis.#isBypassed = isBypassed;\r\n\t\tthis.#serverUrl = serverUrl;\r\n\t\tthis.#backofficePath = backofficePath;\r\n\r\n\t\tthis.#authFlow = new UmbAuthFlow(\r\n\t\t\tserverUrl,\r\n\t\t\tthis.getRedirectUrl(),\r\n\t\t\tthis.getPostLogoutRedirectUrl(),\r\n\t\t\tthis.#isTimeout,\r\n\t\t);\r\n\r\n\t\t// Observe the authorization signal and close the auth window\r\n\t\tthis.observe(\r\n\t\t\tthis.authorizationSignal,\r\n\t\t\t() => {\r\n\t\t\t\t// Update the authorization state\r\n\t\t\t\tthis.getIsAuthorized();\r\n\t\t\t},\r\n\t\t\t'_authFlowAuthorizationSignal',\r\n\t\t);\r\n\r\n\t\t// Observe changes to local storage and update the authorization state\r\n\t\t// This establishes the tab-to-tab communication\r\n\t\twindow.addEventListener('storage', this.#onStorageEvent.bind(this));\r\n\t}\r\n\r\n\toverride destroy(): void {\r\n\t\tsuper.destroy();\r\n\t\twindow.removeEventListener('storage', this.#onStorageEvent.bind(this));\r\n\t}\r\n\r\n\tasync #onStorageEvent(evt: StorageEvent) {\r\n\t\tif (evt.key === UMB_STORAGE_TOKEN_RESPONSE_NAME) {\r\n\t\t\t// Close any open auth windows\r\n\t\t\tthis.#authWindowProxy?.close();\r\n\t\t\t// Refresh the local storage state into memory\r\n\t\t\tawait this.setInitialState();\r\n\t\t\t// Let any auth listeners (such as the auth modal) know that the auth state has changed\r\n\t\t\tthis.authorizationSignal.next();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Initiates the login flow.\r\n\t * @param identityProvider The provider to use for login. Default is 'Umbraco'.\r\n\t * @param redirect If true, the user will be redirected to the login page.\r\n\t * @param usernameHint The username hint to use for login.\r\n\t * @param manifest The manifest for the registered provider.\r\n\t */\r\n\tasync makeAuthorizationRequest(\r\n\t\tidentityProvider = 'Umbraco',\r\n\t\tredirect?: boolean,\r\n\t\tusernameHint?: string,\r\n\t\tmanifest?: ManifestAuthProvider,\r\n\t) {\r\n\t\tconst redirectUrl = await this.#authFlow.makeAuthorizationRequest(identityProvider, usernameHint);\r\n\t\tif (redirect) {\r\n\t\t\tlocation.href = redirectUrl;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst popupTarget = manifest?.meta?.behavior?.popupTarget ?? 'umbracoAuthPopup';\r\n\t\tconst popupFeatures =\r\n\t\t\tmanifest?.meta?.behavior?.popupFeatures ??\r\n\t\t\t'width=600,height=600,menubar=no,location=no,resizable=yes,scrollbars=yes,status=no,toolbar=no';\r\n\r\n\t\tif (!this.#authWindowProxy || this.#authWindowProxy.closed) {\r\n\t\t\tthis.#authWindowProxy = window.open(redirectUrl, popupTarget, popupFeatures);\r\n\t\t} else if (this.#previousAuthUrl !== redirectUrl) {\r\n\t\t\tthis.#authWindowProxy = window.open(redirectUrl, popupTarget);\r\n\t\t\tthis.#authWindowProxy?.focus();\r\n\t\t}\r\n\r\n\t\tthis.#previousAuthUrl = redirectUrl;\r\n\r\n\t\treturn firstValueFrom(this.authorizationSignal);\r\n\t}\r\n\r\n\t/**\r\n\t * Completes the login flow.\r\n\t */\r\n\tcompleteAuthorizationRequest() {\r\n\t\treturn this.#authFlow.completeAuthorizationIfPossible();\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the user is authorized. If Authorization is bypassed, the user is always authorized.\r\n\t * @returns True if the user is authorized, otherwise false.\r\n\t */\r\n\tgetIsAuthorized() {\r\n\t\tif (this.#isBypassed) {\r\n\t\t\tthis.#isAuthorized.setValue(true);\r\n\t\t\treturn true;\r\n\t\t} else {\r\n\t\t\tconst isAuthorized = this.#authFlow.isAuthorized();\r\n\t\t\tthis.#isAuthorized.setValue(isAuthorized);\r\n\t\t\treturn isAuthorized;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the initial state of the auth flow.\r\n\t * @returns {Promise}\r\n\t */\r\n\tsetInitialState(): Promise {\r\n\t\treturn this.#authFlow.setInitialState();\r\n\t}\r\n\r\n\t/**\r\n\t * Gets the latest token from the Management API.\r\n\t * If the token is expired, it will be refreshed.\r\n\t *\r\n\t * NB! The user may experience being redirected to the login screen if the token is expired.\r\n\t * @example Using the latest token\r\n\t * ```js\r\n\t * const token = await authContext.getLatestToken();\r\n\t * const result = await fetch('https://my-api.com', { headers: { Authorization: `Bearer ${token}` } });\r\n\t * ```\r\n\t * @memberof UmbAuthContext\r\n\t * @returns The latest token from the Management API\r\n\t */\r\n\tgetLatestToken(): Promise {\r\n\t\treturn this.#authFlow.performWithFreshTokens();\r\n\t}\r\n\r\n\t/**\r\n\t * Validates the token against the server and returns true if the token is valid.\r\n\t * @memberof UmbAuthContext\r\n\t * @returns True if the token is valid, otherwise false\r\n\t */\r\n\tasync validateToken(): Promise {\r\n\t\treturn this.#isBypassed || this.#authFlow.makeRefreshTokenRequest();\r\n\t}\r\n\r\n\t/**\r\n\t * Clears the token storage.\r\n\t * @memberof UmbAuthContext\r\n\t */\r\n\tclearTokenStorage() {\r\n\t\treturn this.#authFlow.clearTokenStorage();\r\n\t}\r\n\r\n\t/**\r\n\t * Handles the case where the user has timed out, i.e. the token has expired.\r\n\t * This will clear the token storage and set the user as unauthorized.\r\n\t * @memberof UmbAuthContext\r\n\t */\r\n\ttimeOut() {\r\n\t\tthis.#isAuthorized.setValue(false);\r\n\t\tthis.#isTimeout.next();\r\n\t}\r\n\r\n\t/**\r\n\t * Signs the user out by removing any tokens from the browser.\r\n\t * @memberof UmbAuthContext\r\n\t */\r\n\tsignOut(): Promise {\r\n\t\treturn this.#authFlow.signOut();\r\n\t}\r\n\r\n\t/**\r\n\t * Get the server url to the Management API.\r\n\t * @memberof UmbAuthContext\r\n\t * @example Using the server url\r\n\t * ```js\r\n\t * \tconst serverUrl = authContext.getServerUrl();\r\n\t * \tOpenAPI.BASE = serverUrl;\r\n\t * ```\r\n\t * @example \r\n\t * ```js\r\n\t * \tconst serverUrl = authContext.getServerUrl();\r\n\t * \tconst token = await authContext.getLatestToken();\r\n\t * \tconst result = await fetch(`${serverUrl}/umbraco/management/api/v1/my-resource`, { headers: { Authorization: `Bearer ${token}` } });\r\n\t * ```\r\n\t * @returns The server url to the Management API\r\n\t */\r\n\tgetServerUrl() {\r\n\t\treturn this.#serverUrl;\r\n\t}\r\n\r\n\t/**\r\n\t * Get the default OpenAPI configuration, which is set up to communicate with the Management API.\r\n\t * @remark This is useful if you want to communicate with your own resources generated by the [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) library.\r\n\t * @memberof UmbAuthContext\r\n\t * @example Using the default OpenAPI configuration\r\n\t * ```js\r\n\t * \tconst defaultOpenApi = authContext.getOpenApiConfiguration();\r\n\t * \tOpenAPI.BASE = defaultOpenApi.base;\r\n\t * \t\tOpenAPI.WITH_CREDENTIALS = defaultOpenApi.withCredentials;\r\n\t * \t\tOpenAPI.CREDENTIALS = defaultOpenApi.credentials;\r\n\t * \t\tOpenAPI.TOKEN = defaultOpenApi.token;\r\n\t * ```\r\n\t * @returns The default OpenAPI configuration\r\n\t */\r\n\tgetOpenApiConfiguration(): UmbOpenApiConfiguration {\r\n\t\treturn {\r\n\t\t\tbase: OpenAPI.BASE,\r\n\t\t\tversion: OpenAPI.VERSION,\r\n\t\t\twithCredentials: OpenAPI.WITH_CREDENTIALS,\r\n\t\t\tcredentials: OpenAPI.CREDENTIALS,\r\n\t\t\ttoken: () => this.getLatestToken(),\r\n\t\t\tencodePath: OpenAPI.ENCODE_PATH,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the auth context as initialized, which means that the auth context is ready to be used.\r\n\t * @remark This is used to let the app context know that the core module is ready, which means that the core auth providers are available.\r\n\t */\r\n\tsetInitialized() {\r\n\t\tthis.#isInitialized.next();\r\n\t\tthis.#isInitialized.complete();\r\n\t}\r\n\r\n\t/**\r\n\t * Gets all registered auth providers.\r\n\t * @param extensionsRegistry\r\n\t */\r\n\tgetAuthProviders(extensionsRegistry: UmbBackofficeExtensionRegistry) {\r\n\t\treturn this.#isInitialized.pipe(\r\n\t\t\tswitchMap(() => extensionsRegistry.byType<'authProvider', ManifestAuthProvider>('authProvider')),\r\n\t\t);\r\n\t}\r\n\r\n\t/**\r\n\t * Gets the authorized redirect url.\r\n\t * @returns The redirect url, which is the backoffice path.\r\n\t */\r\n\tgetRedirectUrl() {\r\n\t\treturn `${window.location.origin}${this.#backofficePath}${this.#backofficePath.endsWith('/') ? '' : '/'}oauth_complete`;\r\n\t}\r\n\r\n\t/**\r\n\t * Gets the post logout redirect url.\r\n\t * @returns The post logout redirect url, which is the backoffice path with the logout path appended.\r\n\t */\r\n\tgetPostLogoutRedirectUrl() {\r\n\t\treturn `${window.location.origin}${this.#backofficePath}${this.#backofficePath.endsWith('/') ? '' : '/'}logout`;\r\n\t}\r\n\r\n\t/**\r\n\t * @param provider\r\n\t * @see UmbAuthFlow#linkLogin\r\n\t */\r\n\tlinkLogin(provider: string) {\r\n\t\treturn this.#authFlow.linkLogin(provider);\r\n\t}\r\n\r\n\t/**\r\n\t * @param providerName\r\n\t * @param providerKey\r\n\t * @see UmbAuthFlow#unlinkLogin\r\n\t */\r\n\tunlinkLogin(providerName: string, providerKey: string) {\r\n\t\treturn this.#authFlow.unlinkLogin(providerName, providerKey);\r\n\t}\r\n}\r\n","import { UmbModalToken } from '../../modal/token/index.js';\r\nimport type { UmbUserLoginState } from '../types.js';\r\n\r\nexport type UmbModalAppAuthConfig = {\r\n\tuserLoginState: UmbUserLoginState;\r\n};\r\n\r\nexport type UmbModalAppAuthValue = {\r\n\t/**\r\n\t * An indicator of whether the authentication was successful.\r\n\t * @required\r\n\t */\r\n\tsuccess?: boolean;\r\n};\r\n\r\nexport const UMB_MODAL_APP_AUTH = new UmbModalToken('Umb.Modal.AppAuth', {\r\n\tmodal: {\r\n\t\ttype: 'dialog',\r\n\t},\r\n});\r\n"],"names":["UmbAuthContext","UmbContextBase","host","serverUrl","backofficePath","isBypassed","UMB_AUTH_CONTEXT","#isAuthorized","UmbBooleanState","#isTimeout","Subject","#isInitialized","ReplaySubject","#isBypassed","#serverUrl","#backofficePath","#authFlow","UmbAuthFlow","#onStorageEvent","#authWindowProxy","#previousAuthUrl","evt","UMB_STORAGE_TOKEN_RESPONSE_NAME","identityProvider","redirect","usernameHint","manifest","redirectUrl","popupTarget","popupFeatures","firstValueFrom","isAuthorized","OpenAPI","extensionsRegistry","switchMap","provider","providerName","providerKey","UMB_MODAL_APP_AUTH","UmbModalToken"],"mappings":"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"}