Bypass password onboarding for enforced sso (#9851)
This commit is contained in:
parent
f368f3a2a7
commit
22d65b004f
|
@ -62,3 +62,8 @@ export const PluginSource = {
|
||||||
GITHUB: "Github",
|
GITHUB: "Github",
|
||||||
FILE: "File Upload",
|
FILE: "File Upload",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const OnboardingType = {
|
||||||
|
EMAIL: "email",
|
||||||
|
PASSWORD: "password",
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue