Bypass password onboarding for enforced sso (#9851)

This commit is contained in:
Rory Powell 2023-03-01 21:56:30 +00:00 committed by GitHub
parent f368f3a2a7
commit 22d65b004f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 130 deletions

View File

@ -62,3 +62,8 @@ export const PluginSource = {
GITHUB: "Github", GITHUB: "Github",
FILE: "File Upload", FILE: "File Upload",
} }
export const OnboardingType = {
EMAIL: "email",
PASSWORD: "password",
}

View File

@ -13,6 +13,7 @@
let formData = {} let formData = {}
let onboarding = false let onboarding = false
let errors = {} let errors = {}
let loaded = false
$: company = $organisation.company || "Budibase" $: company = $organisation.company || "Budibase"
@ -39,6 +40,11 @@
if (invite?.email) { if (invite?.email) {
formData.email = invite?.email formData.email = invite?.email
} }
if ($organisation.isSSOEnforced) {
// auto accept invite and redirect to login
await users.acceptInvite(inviteCode)
$goto("../auth")
}
} catch (error) { } catch (error) {
notifications.error(error.message) notifications.error(error.message)
} }
@ -61,130 +67,135 @@
try { try {
await organisation.init() await organisation.init()
await getInvite() await getInvite()
loaded = true
} catch (error) { } catch (error) {
notifications.error("Error getting invite config") notifications.error("Error getting invite config")
} }
}) })
</script> </script>
<TestimonialPage> {#if loaded}
<Layout gap="M" noPadding> <TestimonialPage>
<img alt="logo" src={$organisation.logoUrl || Logo} /> <Layout gap="M" noPadding>
<Layout gap="XS" noPadding> <img alt="logo" src={$organisation.logoUrl || Logo} />
<Heading size="M">Join {company}</Heading> <Layout gap="XS" noPadding>
<Body size="M">Create your account to access your budibase apps!</Body> <Heading size="M">Join {company}</Heading>
</Layout> <Body size="M">Create your account to access your budibase apps!</Body>
</Layout>
<Layout gap="S" noPadding> <Layout gap="S" noPadding>
<FancyForm bind:this={form}> <FancyForm bind:this={form}>
<FancyInput <FancyInput
label="Email" label="Email"
value={formData.email} value={formData.email}
disabled={true} disabled={true}
error={errors.email} error={errors.email}
/> />
<FancyInput <FancyInput
label="First name" label="First name"
value={formData.firstName} value={formData.firstName}
on:change={e => { on:change={e => {
formData = { formData = {
...formData, ...formData,
firstName: e.detail, firstName: e.detail,
} }
}} }}
validate={() => { validate={() => {
let fieldError = { let fieldError = {
firstName: !formData.firstName firstName: !formData.firstName
? "Please enter your first name" ? "Please enter your first name"
: undefined,
}
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.firstName}
disabled={onboarding}
/>
<FancyInput
label="Last name (optional)"
value={formData.lastName}
on:change={e => {
formData = {
...formData,
lastName: e.detail,
}
}}
disabled={onboarding}
/>
<FancyInput
label="Password"
value={formData.password}
type="password"
on:change={e => {
formData = {
...formData,
password: e.detail,
}
}}
validate={() => {
let fieldError = {}
fieldError["password"] = !formData.password
? "Please enter a password"
: undefined
fieldError["confirmationPassword"] =
!passwordsMatch(
formData.password,
formData.confirmationPassword
) && formData.confirmationPassword
? "Passwords must match"
: undefined
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.password}
disabled={onboarding}
/>
<FancyInput
label="Repeat password"
value={formData.confirmationPassword}
type="password"
on:change={e => {
formData = {
...formData,
confirmationPassword: e.detail,
}
}}
validate={() => {
let fieldError = {
confirmationPassword:
!passwordsMatch(
formData.password,
formData.confirmationPassword
) && formData.password
? "Passwords must match"
: undefined, : undefined,
} }
errors = handleError({ ...errors, ...fieldError }) errors = handleError({ ...errors, ...fieldError })
}} }}
error={errors.confirmationPassword} error={errors.firstName}
disabled={onboarding} disabled={onboarding}
/> />
</FancyForm> <FancyInput
label="Last name (optional)"
value={formData.lastName}
on:change={e => {
formData = {
...formData,
lastName: e.detail,
}
}}
disabled={onboarding}
/>
{#if !$organisation.isSSOEnforced}
<FancyInput
label="Password"
value={formData.password}
type="password"
on:change={e => {
formData = {
...formData,
password: e.detail,
}
}}
validate={() => {
let fieldError = {}
fieldError["password"] = !formData.password
? "Please enter a password"
: undefined
fieldError["confirmationPassword"] =
!passwordsMatch(
formData.password,
formData.confirmationPassword
) && formData.confirmationPassword
? "Passwords must match"
: undefined
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.password}
disabled={onboarding}
/>
<FancyInput
label="Repeat password"
value={formData.confirmationPassword}
type="password"
on:change={e => {
formData = {
...formData,
confirmationPassword: e.detail,
}
}}
validate={() => {
let fieldError = {
confirmationPassword:
!passwordsMatch(
formData.password,
formData.confirmationPassword
) && formData.password
? "Passwords must match"
: undefined,
}
errors = handleError({ ...errors, ...fieldError })
}}
error={errors.confirmationPassword}
disabled={onboarding}
/>
{/if}
</FancyForm>
</Layout>
<div>
<Button
size="L"
disabled={Object.keys(errors).length > 0 || onboarding}
cta
on:click={acceptInvite}
>
Create account
</Button>
</div>
</Layout> </Layout>
<div> </TestimonialPage>
<Button {/if}
size="L"
disabled={Object.keys(errors).length > 0 || onboarding}
cta
on:click={acceptInvite}
>
Create account
</Button>
</div>
</Layout>
</TestimonialPage>
<style> <style>
img { img {

View File

@ -1,9 +1,8 @@
<script> <script>
import { ModalContent, Body, Layout, Icon } from "@budibase/bbui" import { ModalContent, Body, Layout, Icon } from "@budibase/bbui"
import { OnboardingType } from "../../../../../../constants"
export let chooseCreationType export let chooseCreationType
let emailOnboardingKey = "emailOnboarding"
let basicOnboaridngKey = "basicOnboarding"
let selectedOnboardingType let selectedOnboardingType
</script> </script>
@ -20,9 +19,9 @@
<Layout noPadding gap="S"> <Layout noPadding gap="S">
<div <div
class="onboarding-type item" class="onboarding-type item"
class:selected={selectedOnboardingType == emailOnboardingKey} class:selected={selectedOnboardingType == OnboardingType.EMAIL}
on:click={() => { on:click={() => {
selectedOnboardingType = emailOnboardingKey selectedOnboardingType = OnboardingType.EMAIL
}} }}
> >
<div class="content onboarding-type-wrap"> <div class="content onboarding-type-wrap">
@ -32,7 +31,7 @@
</div> </div>
</div> </div>
<div style="color: var(--spectrum-global-color-green-600); float: right"> <div style="color: var(--spectrum-global-color-green-600); float: right">
{#if selectedOnboardingType == emailOnboardingKey} {#if selectedOnboardingType == OnboardingType.EMAIL}
<div class="checkmark-spacing"> <div class="checkmark-spacing">
<Icon size="S" name="CheckmarkCircle" /> <Icon size="S" name="CheckmarkCircle" />
</div> </div>
@ -42,9 +41,9 @@
<div <div
class="onboarding-type item" class="onboarding-type item"
class:selected={selectedOnboardingType == basicOnboaridngKey} class:selected={selectedOnboardingType == OnboardingType.PASSWORD}
on:click={() => { on:click={() => {
selectedOnboardingType = basicOnboaridngKey selectedOnboardingType = OnboardingType.PASSWORD
}} }}
> >
<div class="content onboarding-type-wrap"> <div class="content onboarding-type-wrap">
@ -54,7 +53,7 @@
</div> </div>
</div> </div>
<div style="color: var(--spectrum-global-color-green-600); float: right"> <div style="color: var(--spectrum-global-color-green-600); float: right">
{#if selectedOnboardingType == basicOnboaridngKey} {#if selectedOnboardingType == OnboardingType.PASSWORD}
<div class="checkmark-spacing"> <div class="checkmark-spacing">
<Icon size="S" name="CheckmarkCircle" /> <Icon size="S" name="CheckmarkCircle" />
</div> </div>

View File

@ -13,7 +13,7 @@
Divider, Divider,
} from "@budibase/bbui" } from "@budibase/bbui"
import AddUserModal from "./_components/AddUserModal.svelte" import AddUserModal from "./_components/AddUserModal.svelte"
import { users, groups, auth, licensing } from "stores/portal" import { users, groups, auth, licensing, organisation } from "stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
import DeleteRowsButton from "components/backend/DataTable/buttons/DeleteRowsButton.svelte" import DeleteRowsButton from "components/backend/DataTable/buttons/DeleteRowsButton.svelte"
import GroupsTableRenderer from "./_components/GroupsTableRenderer.svelte" import GroupsTableRenderer from "./_components/GroupsTableRenderer.svelte"
@ -27,6 +27,7 @@
import { get } from "svelte/store" import { get } from "svelte/store"
import { Constants, Utils, fetchData } from "@budibase/frontend-core" import { Constants, Utils, fetchData } from "@budibase/frontend-core"
import { API } from "api" import { API } from "api"
import { OnboardingType } from "../../../../../constants"
const fetch = fetchData({ const fetch = fetchData({
API, API,
@ -105,10 +106,18 @@
const debouncedUpdateFetch = Utils.debounce(updateFetch, 250) const debouncedUpdateFetch = Utils.debounce(updateFetch, 250)
const showOnboardingTypeModal = async addUsersData => { const showOnboardingTypeModal = async addUsersData => {
// no-op if users already exist
userData = await removingDuplicities(addUsersData) userData = await removingDuplicities(addUsersData)
if (!userData?.users?.length) return if (!userData?.users?.length) {
return
}
onboardingTypeModal.show() if ($organisation.isSSOEnforced) {
// bypass the onboarding type selection of sso is enforced
await chooseCreationType(OnboardingType.EMAIL)
} else {
onboardingTypeModal.show()
}
} }
async function createUserFlow() { async function createUserFlow() {
@ -181,7 +190,7 @@
} }
async function chooseCreationType(onboardingType) { async function chooseCreationType(onboardingType) {
if (onboardingType === "emailOnboarding") { if (onboardingType === OnboardingType.EMAIL) {
await createUserFlow() await createUserFlow()
} else { } else {
await createUsers() await createUsers()

View File

@ -50,8 +50,8 @@ function buildInviteAcceptValidation() {
// prettier-ignore // prettier-ignore
return auth.joiValidator.body(Joi.object({ return auth.joiValidator.body(Joi.object({
inviteCode: Joi.string().required(), inviteCode: Joi.string().required(),
password: Joi.string().required(), password: Joi.string().optional(),
firstName: Joi.string().required(), firstName: Joi.string().optional(),
lastName: Joi.string().optional(), lastName: Joi.string().optional(),
}).required().unknown(true)) }).required().unknown(true))
} }

View File

@ -140,7 +140,12 @@ const buildUser = async (
hashedPassword = opts.hashPassword ? await utils.hash(password) : password hashedPassword = opts.hashPassword ? await utils.hash(password) : password
} else if (dbUser) { } else if (dbUser) {
hashedPassword = dbUser.password hashedPassword = dbUser.password
} else if (opts.requirePassword) { }
// passwords are never required if sso is enforced
const requirePasswords =
opts.requirePassword && !(await pro.features.isSSOEnforced())
if (!hashedPassword && requirePasswords) {
throw "Password must be specified." throw "Password must be specified."
} }

View File

@ -47,7 +47,7 @@ async function getACode(db: string, code: string, deleteCode = true) {
const client = await getClient(db) const client = await getClient(db)
const value = await client.get(code) const value = await client.get(code)
if (!value) { if (!value) {
throw "Invalid code." throw new Error("Invalid code.")
} }
if (deleteCode) { if (deleteCode) {
await client.delete(code) await client.delete(code)