import type { App, Ref } from 'vue'; import { ref } from 'vue'; import type { Router } from 'vue-router'; import type { AppState, Auth0PluginOptions, Auth0VueClient, Auth0VueClientOptions, LogoutOptions, RedirectLoginOptions } from './interfaces'; import { AUTH0_INJECTION_KEY, AUTH0_TOKEN } from './token'; import version from './version'; import type { GetTokenSilentlyOptions, GetTokenSilentlyVerboseResponse, GetTokenWithPopupOptions, IdToken, PopupConfigOptions, PopupLoginOptions, RedirectLoginResult, ConnectAccountRedirectResult, FetcherConfig, Fetcher, CustomFetchMinimalOutput, MfaApiClient } from '@auth0/auth0-spa-js'; import { Auth0Client, User } from '@auth0/auth0-spa-js'; import { bindPluginMethods, deprecateRedirectUri } from './utils'; /** * Helper callback that's used by default before the plugin is installed. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any const PLUGIN_NOT_INSTALLED_HANDLER: any = () => { console.error(`Please ensure Auth0's Vue plugin is correctly installed.`); }; /** * Helper client that's used by default before the plugin is installed. */ const PLUGIN_NOT_INSTALLED_CLIENT: Auth0VueClient = { isLoading: ref(false), isAuthenticated: ref(false), user: ref(undefined), idTokenClaims: ref(undefined), error: ref(null), loginWithPopup: PLUGIN_NOT_INSTALLED_HANDLER, loginWithRedirect: PLUGIN_NOT_INSTALLED_HANDLER, getAccessTokenSilently: PLUGIN_NOT_INSTALLED_HANDLER, getAccessTokenWithPopup: PLUGIN_NOT_INSTALLED_HANDLER, logout: PLUGIN_NOT_INSTALLED_HANDLER, checkSession: PLUGIN_NOT_INSTALLED_HANDLER, handleRedirectCallback: PLUGIN_NOT_INSTALLED_HANDLER, getDpopNonce: PLUGIN_NOT_INSTALLED_HANDLER, setDpopNonce: PLUGIN_NOT_INSTALLED_HANDLER, generateDpopProof: PLUGIN_NOT_INSTALLED_HANDLER, createFetcher: PLUGIN_NOT_INSTALLED_HANDLER, mfa: { setMFAAuthDetails: PLUGIN_NOT_INSTALLED_HANDLER, getAuthenticators: PLUGIN_NOT_INSTALLED_HANDLER, enroll: PLUGIN_NOT_INSTALLED_HANDLER, challenge: PLUGIN_NOT_INSTALLED_HANDLER, verify: PLUGIN_NOT_INSTALLED_HANDLER, getEnrollmentFactors: PLUGIN_NOT_INSTALLED_HANDLER } as unknown as MfaApiClient }; /** * @ignore */ export const client: Ref = ref( // eslint-disable-next-line @typescript-eslint/no-explicit-any PLUGIN_NOT_INSTALLED_CLIENT as any ); /** * @ignore */ export class Auth0Plugin implements Auth0VueClient { private _client!: Auth0Client; public isLoading: Ref = ref(true); public isAuthenticated: Ref = ref(false); public user: Ref = ref({}); public idTokenClaims = ref(); public error = ref(null); public mfa: MfaApiClient = PLUGIN_NOT_INSTALLED_CLIENT.mfa; constructor( private clientOptions: Auth0VueClientOptions, private pluginOptions?: Auth0PluginOptions ) { // Vue Plugins can have issues when passing around the instance to `provide` // Therefor we need to bind all methods correctly to `this`. bindPluginMethods(this, ['constructor']); } install(app: App) { this._client = new Auth0Client({ ...this.clientOptions, auth0Client: { name: 'auth0-vue', version: version } }); this.mfa = this._client.mfa; this.__checkSession(app.config.globalProperties.$router); // eslint-disable-next-line security/detect-object-injection app.config.globalProperties[AUTH0_TOKEN] = this; app.provide(AUTH0_INJECTION_KEY, this as Auth0VueClient); client.value = this as Auth0VueClient; } async loginWithRedirect(options?: RedirectLoginOptions) { deprecateRedirectUri(options); return this._client.loginWithRedirect(options); } async loginWithPopup( options?: PopupLoginOptions, config?: PopupConfigOptions ) { deprecateRedirectUri(options); return this.__proxy(() => this._client.loginWithPopup(options, config)); } async logout(options?: LogoutOptions) { if (options?.openUrl || options?.openUrl === false) { return this.__proxy(() => this._client.logout(options)); } return this._client.logout(options); } /* istanbul ignore next */ async getAccessTokenSilently( options: GetTokenSilentlyOptions & { detailedResponse: true } ): Promise; /* istanbul ignore next */ async getAccessTokenSilently( options?: GetTokenSilentlyOptions ): Promise; /* istanbul ignore next */ async getAccessTokenSilently( options: GetTokenSilentlyOptions = {} ): Promise { deprecateRedirectUri(options); return this.__proxy(() => this._client.getTokenSilently(options)); } async getAccessTokenWithPopup( options?: GetTokenWithPopupOptions, config?: PopupConfigOptions ) { deprecateRedirectUri(options); return this.__proxy(() => this._client.getTokenWithPopup(options, config)); } async checkSession(options?: GetTokenSilentlyOptions) { return this.__proxy(() => this._client.checkSession(options)); } async handleRedirectCallback( url?: string ): Promise< RedirectLoginResult | ConnectAccountRedirectResult > { return this.__proxy(() => this._client.handleRedirectCallback(url) ); } async getDpopNonce(id?: string): Promise { return this.__proxy(() => this._client.getDpopNonce(id)); } async setDpopNonce(nonce: string, id?: string): Promise { return this.__proxy(() => this._client.setDpopNonce(nonce, id)); } async generateDpopProof(params: { url: string; method: string; accessToken: string; nonce?: string; }): Promise { return this.__proxy(() => this._client.generateDpopProof(params)); } createFetcher( config?: FetcherConfig ): Fetcher { return this._client.createFetcher(config); } private async __checkSession(router?: Router) { const search = window.location.search; try { if ( (search.includes('code=') || search.includes('error=')) && search.includes('state=') && !this.pluginOptions?.skipRedirectCallback ) { const result = await this.handleRedirectCallback(); const appState = result?.appState; const target = appState?.target ?? '/'; window.history.replaceState({}, '', '/'); if (router) { router.push(target); } return result; } else { await this.checkSession(); } } catch (e) { // __checkSession should never throw an exception as it will fail installing the plugin. // Instead, errors during __checkSession are propagated using the errors property on `useAuth0`. window.history.replaceState({}, '', '/'); if (router) { router.push(this.pluginOptions?.errorPath || '/'); } } } private async __refreshState() { this.isAuthenticated.value = await this._client.isAuthenticated(); this.user.value = await this._client.getUser(); this.idTokenClaims.value = await this._client.getIdTokenClaims(); this.isLoading.value = false; } private async __proxy(cb: () => T, refreshState = true) { let result; try { result = await cb(); this.error.value = null; } catch (e) { this.error.value = e as Error; throw e; } finally { if (refreshState) { await this.__refreshState(); } } return result; } }