diff --git a/packages/builder/src/pages/builder/admin/index.svelte b/packages/builder/src/pages/builder/admin/index.svelte index 2921f065d3..a84858c8d6 100644 --- a/packages/builder/src/pages/builder/admin/index.svelte +++ b/packages/builder/src/pages/builder/admin/index.svelte @@ -36,10 +36,7 @@ await API.createAdminUser(adminUser) notifications.success("Admin user created") await admin.init() - await auth.login({ - username: formData?.email.trim(), - password: formData?.password, - }) + await auth.login(formData?.email.trim(), formData?.password) $goto("../portal") } catch (error) { submitted = false diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 7bb2a3ad49..650bbc4bf5 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -35,10 +35,7 @@ return } try { - await auth.login({ - username: formData?.username.trim(), - password: formData?.password, - }) + await auth.login(formData?.username.trim(), formData?.password) if ($auth?.user?.forceResetPassword) { $goto("./reset") } else { diff --git a/packages/builder/src/pages/builder/invite/index.svelte b/packages/builder/src/pages/builder/invite/index.svelte index 597deed7c9..60f3b18f77 100644 --- a/packages/builder/src/pages/builder/invite/index.svelte +++ b/packages/builder/src/pages/builder/invite/index.svelte @@ -66,10 +66,7 @@ async function login() { try { - await auth.login({ - username: formData.email.trim(), - password: formData.password.trim(), - }) + await auth.login(formData.email.trim(), formData.password.trim()) notifications.success("Logged in successfully") $goto("../portal") } catch (err) { diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js deleted file mode 100644 index 71f4841053..0000000000 --- a/packages/builder/src/stores/portal/auth.js +++ /dev/null @@ -1,161 +0,0 @@ -import { derived, writable, get } from "svelte/store" -import { API } from "api" -import { admin } from "stores/portal" -import analytics from "analytics" - -export function createAuthStore() { - const auth = writable({ - user: null, - accountPortalAccess: false, - tenantId: "default", - tenantSet: false, - loaded: false, - postLogout: false, - }) - const store = derived(auth, $store => { - return { - user: $store.user, - accountPortalAccess: $store.accountPortalAccess, - tenantId: $store.tenantId, - tenantSet: $store.tenantSet, - loaded: $store.loaded, - postLogout: $store.postLogout, - isSSO: !!$store.user?.provider, - } - }) - - function setUser(user) { - auth.update(store => { - store.loaded = true - store.user = user - store.accountPortalAccess = user?.accountPortalAccess - if (user) { - store.tenantId = user.tenantId || "default" - store.tenantSet = true - } - return store - }) - - if (user) { - analytics - .activate() - .then(() => { - analytics.identify(user._id) - }) - .catch(() => { - // This request may fail due to browser extensions blocking requests - // containing the word analytics, so we don't want to spam users with - // an error here. - }) - } - } - - async function setOrganisation(tenantId) { - const prevId = get(store).tenantId - auth.update(store => { - store.tenantId = tenantId - store.tenantSet = !!tenantId - return store - }) - if (prevId !== tenantId) { - // re-init admin after setting org - await admin.init() - } - } - - async function setInitInfo(info) { - await API.setInitInfo(info) - auth.update(store => { - store.initInfo = info - return store - }) - return info - } - - function setPostLogout() { - auth.update(store => { - store.postLogout = true - return store - }) - } - - async function getInitInfo() { - const info = await API.getInitInfo() - auth.update(store => { - store.initInfo = info - return store - }) - return info - } - - const actions = { - checkQueryString: async () => { - const urlParams = new URLSearchParams(window.location.search) - if (urlParams.has("tenantId")) { - const tenantId = urlParams.get("tenantId") - await setOrganisation(tenantId) - } - }, - setOrg: async tenantId => { - await setOrganisation(tenantId) - }, - getSelf: async () => { - // We need to catch this locally as we never want this to fail, even - // though normally we never want to swallow API errors at the store level. - // We're either logged in or we aren't. - // We also need to always update the loaded flag. - try { - const user = await API.fetchBuilderSelf() - setUser(user) - } catch (error) { - setUser(null) - } - }, - login: async creds => { - const tenantId = get(store).tenantId - await API.logIn(tenantId, creds.username, creds.password) - await actions.getSelf() - }, - logout: async () => { - await API.logOut() - setPostLogout() - setUser(null) - await setInitInfo({}) - }, - updateSelf: async fields => { - await API.updateSelf({ ...fields }) - // Refetch to enrich after update. - try { - const user = await API.fetchBuilderSelf() - setUser(user) - } catch (error) { - setUser(null) - } - }, - forgotPassword: async email => { - const tenantId = get(store).tenantId - await API.requestForgotPassword(tenantId, email) - }, - resetPassword: async (password, resetCode) => { - const tenantId = get(store).tenantId - await API.resetPassword(tenantId, password, resetCode) - }, - generateAPIKey: async () => { - return API.generateAPIKey() - }, - fetchAPIKey: async () => { - const info = await API.fetchDeveloperInfo() - return info?.apiKey - }, - } - - return { - subscribe: store.subscribe, - setOrganisation, - getInitInfo, - setInitInfo, - ...actions, - } -} - -export const auth = createAuthStore() diff --git a/packages/builder/src/stores/portal/auth.ts b/packages/builder/src/stores/portal/auth.ts new file mode 100644 index 0000000000..1f9646dd56 --- /dev/null +++ b/packages/builder/src/stores/portal/auth.ts @@ -0,0 +1,168 @@ +import { get } from "svelte/store" +import { API } from "api" +import { admin } from "stores/portal" +import analytics from "analytics" +import BudiStore from "stores/BudiStore" +import { + isSSOUser, + SetInitInfoRequest, + UpdateSelfRequest, + User, +} from "@budibase/types" + +interface PortalAuthStore { + user?: User + initInfo?: Record + accountPortalAccess: boolean + loaded: boolean + isSSO: boolean + tenantId: string + tenantSet: boolean + postLogout: boolean +} + +class AuthStore extends BudiStore { + constructor() { + super({ + accountPortalAccess: false, + tenantId: "default", + tenantSet: false, + loaded: false, + postLogout: false, + isSSO: false, + }) + } + + setUser(user?: User) { + this.set({ + loaded: true, + user: user, + accountPortalAccess: !!user?.accountPortalAccess, + tenantId: user?.tenantId || "default", + tenantSet: !!user, + isSSO: user != null && isSSOUser(user), + postLogout: false, + }) + + if (user) { + analytics + .activate() + .then(() => { + analytics.identify(user._id) + }) + .catch(() => { + // This request may fail due to browser extensions blocking requests + // containing the word analytics, so we don't want to spam users with + // an error here. + }) + } + } + + async setOrganisation(tenantId: string) { + const prevId = get(this.store).tenantId + auth.update(store => { + store.tenantId = tenantId + store.tenantSet = !!tenantId + return store + }) + if (prevId !== tenantId) { + // re-init admin after setting org + await admin.init() + } + } + + async setInitInfo(info: SetInitInfoRequest) { + await API.setInitInfo(info) + auth.update(store => { + store.initInfo = info + return store + }) + return info + } + + setPostLogout() { + auth.update(store => { + store.postLogout = true + return store + }) + } + + async getInitInfo() { + const info = await API.getInitInfo() + auth.update(store => { + store.initInfo = info + return store + }) + return info + } + + async checkQueryString() { + const urlParams = new URLSearchParams(window.location.search) + const tenantId = urlParams.get("tenantId") + if (tenantId) { + await this.setOrganisation(tenantId) + } + } + + async setOrg(tenantId: string) { + await this.setOrganisation(tenantId) + } + + async getSelf() { + // We need to catch this locally as we never want this to fail, even + // though normally we never want to swallow API errors at the store level. + // We're either logged in or we aren't. + // We also need to always update the loaded flag. + try { + const user = await API.fetchBuilderSelf() + this.setUser(user) + } catch (error) { + this.setUser() + } + } + + async login(username: string, password: string) { + const tenantId = get(this.store).tenantId + await API.logIn(tenantId, username, password) + await this.getSelf() + } + + async logout() { + await API.logOut() + this.setPostLogout() + this.setUser() + await this.setInitInfo({}) + } + + async updateSelf(fields: UpdateSelfRequest) { + await API.updateSelf(fields) + // Refetch to enrich after update. + try { + const user = await API.fetchBuilderSelf() + this.setUser(user) + } catch (error) { + this.setUser() + } + } + + async forgotPassword(email: string) { + const tenantId = get(this.store).tenantId + await API.requestForgotPassword(tenantId, email) + } + + async resetPassword(password: string, resetCode: string) { + const tenantId = get(this.store).tenantId + await API.resetPassword(tenantId, password, resetCode) + } + + async generateAPIKey() { + return API.generateAPIKey() + } + + async fetchAPIKey() { + const info = await API.fetchDeveloperInfo() + return info?.apiKey + } +} + +export const auth = new AuthStore() diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index 529223e88d..1008cd59fe 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -70,6 +70,8 @@ export interface User extends Document { appFavourites?: string[] ssoId?: string appSort?: string + budibaseAccess?: boolean + accountPortalAccess?: boolean } export interface UserBindings extends Document {