From defea5950d0607bc192080c924ddb9e720c724b9 Mon Sep 17 00:00:00 2001 From: Zouheir Layine <58786497+zlayine@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:15:41 +0300 Subject: [PATCH] fix multi tenancy (#24) --- resources/js/api/index.ts | 6 ++- resources/js/components/pages/Setup.vue | 18 +++++--- resources/js/components/pages/auth/Login.vue | 9 ++-- .../js/components/pages/auth/Register.vue | 2 +- .../pages/auth/RequestResetPassword.vue | 2 +- .../components/pages/auth/ResetPassword.vue | 2 +- resources/js/config.json.example | 2 +- resources/js/store/index.ts | 45 +++++++++++-------- resources/js/util/auth.guard.ts | 11 +++-- src/Console/InstallPlatformUi.php | 12 ++--- 10 files changed, 65 insertions(+), 44 deletions(-) diff --git a/resources/js/api/index.ts b/resources/js/api/index.ts index d305a37..7b20853 100644 --- a/resources/js/api/index.ts +++ b/resources/js/api/index.ts @@ -59,10 +59,14 @@ export class ApiService { const appStore = useAppStore(); return new Promise((resolve, reject) => { + if (!appStore.config.url) { + reject({ field: 'Error', message: 'No URL provided' }); + } + ApiService.request({ url: `${appStore.config.url}graphql${schema}`, data, - credentials: useAppStore().isMultiTenant ? 'include' : 'omit', + credentials: appStore.isMultiTenant ? 'include' : 'omit', }).then((res: any) => { if (res.errors) { const message = res.errors[0].message; diff --git a/resources/js/components/pages/Setup.vue b/resources/js/components/pages/Setup.vue index d2ca66b..6874670 100644 --- a/resources/js/components/pages/Setup.vue +++ b/resources/js/components/pages/Setup.vue @@ -15,7 +15,7 @@ input-class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6" /> import { Form } from 'vee-validate'; -import { ref } from 'vue'; -import type { Ref } from 'vue'; +import { ref, watch } from 'vue'; import { useRouter } from 'vue-router'; import { useAppStore } from '~/store'; import Btn from '~/components/Btn.vue'; @@ -45,7 +44,7 @@ const router = useRouter(); const appStore = useAppStore(); const isLoading = ref(false); -const url: Ref = ref(); +const url = ref(); const authorizationToken = ref(''); const formRef = ref(); @@ -75,7 +74,7 @@ const setupAccount = async () => { throw new Error('You must use an https hostname'); } - const parsedUrl = new URL(url.value!); + const parsedUrl = new URL(url.value); if (!(await appStore.checkURL(parsedUrl))) return; if ( @@ -94,7 +93,14 @@ const setupAccount = async () => { }; (async () => { - if (appStore.config.url && appStore.config.authorization_token) redirectToCollections(); + if (appStore.hasValidConfig) redirectToCollections(); url.value = appStore.config.url as URL; })(); + +watch( + () => appStore.hasValidConfig, + () => { + if (appStore.hasValidConfig) router.push({ name: 'platform.collections' }); + } +); diff --git a/resources/js/components/pages/auth/Login.vue b/resources/js/components/pages/auth/Login.vue index 752d632..5df6c4b 100644 --- a/resources/js/components/pages/auth/Login.vue +++ b/resources/js/components/pages/auth/Login.vue @@ -91,9 +91,10 @@ const login = async () => { isLoading.value = false; return; } - snackbar.success({ title: 'Logged in successfully', save: false }); - await appStore.init(); - redirectToCollections(); + if (await appStore.init()) { + snackbar.success({ title: 'Logged in successfully', save: false }); + redirectToCollections(); + } } catch (e) { if (snackbarErrors(e)) return; snackbar.error({ @@ -118,7 +119,7 @@ const checkVerified = () => { (async () => { checkVerified(); - if (appStore.loggedIn) { + if (appStore.hasValidConfig) { redirectToCollections(); } })(); diff --git a/resources/js/components/pages/auth/Register.vue b/resources/js/components/pages/auth/Register.vue index 7e61313..253b9b5 100644 --- a/resources/js/components/pages/auth/Register.vue +++ b/resources/js/components/pages/auth/Register.vue @@ -119,7 +119,7 @@ const register = async () => { }; (async () => { - if (appStore.loggedIn) { + if (appStore.hasValidConfig) { redirectToLogin(); } })(); diff --git a/resources/js/components/pages/auth/RequestResetPassword.vue b/resources/js/components/pages/auth/RequestResetPassword.vue index 5a95d3b..8f5808b 100644 --- a/resources/js/components/pages/auth/RequestResetPassword.vue +++ b/resources/js/components/pages/auth/RequestResetPassword.vue @@ -93,7 +93,7 @@ const requestReset = async () => { }; (async () => { - if (appStore.loggedIn) { + if (appStore.hasValidConfig) { redirectToCollections(); } })(); diff --git a/resources/js/components/pages/auth/ResetPassword.vue b/resources/js/components/pages/auth/ResetPassword.vue index dd83caa..990c7bf 100644 --- a/resources/js/components/pages/auth/ResetPassword.vue +++ b/resources/js/components/pages/auth/ResetPassword.vue @@ -106,7 +106,7 @@ const resetPassword = async () => { (async () => { email.value = route.query.email as string; - if (appStore.loggedIn) { + if (appStore.hasValidConfig) { redirectToCollections(); } })(); diff --git a/resources/js/config.json.example b/resources/js/config.json.example index 0e943f6..f72d552 100644 --- a/resources/js/config.json.example +++ b/resources/js/config.json.example @@ -3,6 +3,6 @@ "authorization_token": "AUTHORIZATION_TOKEN_VALUE", "route": "ROUTE_VALUE", "tenant": "MULTI_TENANCY_VALUE", - "websocket": "WEBSOCKET_URL_VALUE", + "websocket": "WEBSOCKET_VALUE", "channel": "WEBSOCKET_CHANNEL_VALUE" } diff --git a/resources/js/store/index.ts b/resources/js/store/index.ts index 79067a9..627641e 100644 --- a/resources/js/store/index.ts +++ b/resources/js/store/index.ts @@ -21,11 +21,7 @@ const RPC_URLS = { }; const parseConfigURL = (url: string): URL => { - try { - return new URL(url); - } catch { - return new URL('https://' + url); - } + return new URL(url); }; const updateTransaction = async ({ @@ -115,7 +111,6 @@ export const useAppStore = defineStore('app', { this.setConfig(); if (!this.config.url) return false; - if (this.isMultiTenant && this.loggedIn) await this.getUser(); const urlConfig = await this.checkURL(this.config.url); this.config.network = urlConfig.network; this.config.packages = Object.entries(urlConfig.packages).map(([key, value]: any[]) => { @@ -131,14 +126,17 @@ export const useAppStore = defineStore('app', { link, }; }); - if (this.hasBeamPackage) this.addBeamNavigation(); if (this.hasFuelTanksPackage) this.addFuelTanksNavigation(); if (this.hasMarketplacePackage) this.addMarketplaceNavigation(); + this.loggedIn = true; + if (this.isMultiTenant && this.loggedIn) await this.getUser(); + return await this.fetchCollectionIds(); } catch (error: any) { snackbar.error({ title: error }); + this.clearLogin(); } return false; @@ -146,23 +144,34 @@ export const useAppStore = defineStore('app', { async setupAccount({ url, authorization_token }: { url: URL; authorization_token: string }) { this.url = url; this.authorization_token = authorization_token; - this.config.authorization_token = authorization_token; - this.loggedIn = true; return await this.init(); }, setConfig() { - if (appConfig?.url) this.config.url = parseConfigURL(appConfig.url); - else if (window?.bootstrap?.hostname) this.config.url = parseConfigURL(window.location.origin); - else this.config.url = this.url; + if (appConfig?.tenant) { + this.config.tenant = appConfig.tenant === 'true'; + } - if (appConfig?.authorization_token?.length) this.config.authorization_token = appConfig.authorization_token; - else this.config.authorization_token = this.authorization_token; + if (appConfig?.url) { + this.config.url = parseConfigURL(appConfig.url); + } else if (window?.bootstrap?.hostname) { + this.config.url = parseConfigURL(window.location.origin); + } else { + this.config.url = this.url; + } - if (appConfig?.tenant) this.config.tenant = appConfig.tenant; + if (!this.config.tenant && appConfig?.authorization_token?.length) { + this.config.authorization_token = appConfig.authorization_token; + } else if (!this.config.tenant) { + this.config.authorization_token = this.authorization_token; + } - if (appConfig.websocket.length) this.config.webSocket = appConfig.websocket; - if (appConfig.channel.length) this.config.channel = appConfig.channel; + if (appConfig.websocket.length) { + this.config.webSocket = appConfig.websocket; + } + if (appConfig.channel.length) { + this.config.channel = appConfig.channel; + } }, async checkURL(url: URL) { try { @@ -431,7 +440,7 @@ export const useAppStore = defineStore('app', { return state.loggedIn && state.user?.apiTokens?.length > 0 && state.user?.account; } - return state.loggedIn && state.config.url && state.config.authorization_token.length > 0; + return state.loggedIn && state.config.url; }, isMultiTenant(state: AppState) { return state.config.tenant; diff --git a/resources/js/util/auth.guard.ts b/resources/js/util/auth.guard.ts index 1f9cbc2..0cfa75f 100644 --- a/resources/js/util/auth.guard.ts +++ b/resources/js/util/auth.guard.ts @@ -7,7 +7,6 @@ export function initAuthGuard(router: Router) { const validConfig = appStore.hasValidConfig; const isMultiTenant = appStore.isMultiTenant; - const isLoggedIn = appStore.loggedIn; const requiresAuth = to.matched.some((record) => record.meta.requiresAuth); const requiresToken = to.matched.some((record) => record.meta.requiresToken); @@ -21,21 +20,21 @@ export function initAuthGuard(router: Router) { return; } - if (requiresAuth && !isLoggedIn) { + if (requiresAuth && !validConfig) { next({ name: 'platform.auth.login' }); } else if (requiresToken && appStore.user && !validConfig) { next({ name: 'platform.user.settings' }); - } else if (to.name == 'platform.auth.login' && isLoggedIn) { + } else if (to.name == 'platform.auth.login' && validConfig) { next({ name: 'platform.collections' }); } else { next(); } } else { - if (requiresAuth && (!validConfig || !isLoggedIn)) { + if (requiresAuth && !validConfig) { next({ name: 'platform.setup' }); - } else if (to.name == 'platform.auth.login' && !isLoggedIn) { + } else if (to.name == 'platform.auth.login' && !validConfig) { next({ name: 'platform.setup' }); - } else if (to.name == 'platform.setup' && validConfig && isLoggedIn) { + } else if (to.name == 'platform.setup' && validConfig) { next({ name: 'platform.collections' }); } else { next(); diff --git a/src/Console/InstallPlatformUi.php b/src/Console/InstallPlatformUi.php index e27531f..499a70c 100644 --- a/src/Console/InstallPlatformUi.php +++ b/src/Console/InstallPlatformUi.php @@ -16,17 +16,19 @@ public function handle() { $this->resetJSON(); - $tenant = $this->askForInput('Do you want to enable multi-tenancy? (yes/no)', 'MULTI_TENANCY_VALUE', false, 'tenant'); + $tenant = $this->askForInput('Do you want to enable multi-tenancy? (yes/no)', 'MULTI_TENANCY_VALUE', 'false', 'tenant'); $this->askForInput('Please enter your Enjin Platform URL', 'URL_VALUE', '', 'host'); if ($tenant !=='true') { $this->askForInput('Please enter the authorization token', 'AUTHORIZATION_TOKEN_VALUE', '', 'token'); + } else { + $this->updateConfig('AUTHORIZATION_TOKEN_VALUE', ''); } $route = $this->askForInput('Please enter the default route path', 'ROUTE_VALUE', '', 'route'); $this->updateRoute($route); - $this->askForInput('Please enter WebSocket URL? (optional)', 'WEBSOCKET_URL_VALUE', '', 'wsurl'); + $this->askForInput('Please enter WebSocket URL? (optional)', 'WEBSOCKET_VALUE', '', 'wsurl'); $this->askForInput('Please enter WebSocket channel? (optional)', 'WEBSOCKET_CHANNEL_VALUE', '', 'wschannel'); $this->comment('Installing Platform UI dependencies...'); @@ -58,7 +60,7 @@ private function askForInput($label, $key, $default = null, $option = null) { if (!is_null($optionValue = $this->option($option))) { if ($option === 'tenant') { - $optionValue = Str::lower($optionValue) === 'yes'; + $optionValue = Str::lower($optionValue) === 'yes' ? 'true' : 'false'; } $this->updateConfig($key, $optionValue); @@ -79,7 +81,7 @@ private function askForInput($label, $key, $default = null, $option = null) } if ($option === 'tenant') { - $value = Str::lower($value) === 'yes'; + $value = Str::lower($value) === 'yes' ? 'true' : 'false'; } $this->updateConfig($key, $value); @@ -95,7 +97,7 @@ private function updateConfig($key, $value) file_put_contents( $this->BASE_DIR . 'resources/js/config.json', str_replace( - is_bool($value) ? "$key" : $key, + $key, $value, $appConfig )