Bypass password onboarding for enforced sso (#9851)
This commit is contained in:
parent
2127717987
commit
4c43ba2943
|
@ -62,3 +62,8 @@ export const PluginSource = {
|
|||
GITHUB: "Github",
|
||||
FILE: "File Upload",
|
||||
}
|
||||
|
||||
export const OnboardingType = {
|
||||
EMAIL: "email",
|
||||
PASSWORD: "password",
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
let formData = {}
|
||||
let onboarding = false
|
||||
let errors = {}
|
||||
let loaded = false
|
||||
|
||||
$: company = $organisation.company || "Budibase"
|
||||
|
||||
|
@ -39,6 +40,11 @@
|
|||
if (invite?.email) {
|
||||
formData.email = invite?.email
|
||||
}
|
||||
if ($organisation.isSSOEnforced) {
|
||||
// auto accept invite and redirect to login
|
||||
await users.acceptInvite(inviteCode)
|
||||
$goto("../auth")
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error(error.message)
|
||||
}
|
||||
|
@ -61,130 +67,135 @@
|
|||
try {
|
||||
await organisation.init()
|
||||
await getInvite()
|
||||
loaded = true
|
||||
} catch (error) {
|
||||
notifications.error("Error getting invite config")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<TestimonialPage>
|
||||
<Layout gap="M" noPadding>
|
||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||
<Layout gap="XS" noPadding>
|
||||
<Heading size="M">Join {company}</Heading>
|
||||
<Body size="M">Create your account to access your budibase apps!</Body>
|
||||
</Layout>
|
||||
{#if loaded}
|
||||
<TestimonialPage>
|
||||
<Layout gap="M" noPadding>
|
||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||
<Layout gap="XS" noPadding>
|
||||
<Heading size="M">Join {company}</Heading>
|
||||
<Body size="M">Create your account to access your budibase apps!</Body>
|
||||
</Layout>
|
||||
|
||||
<Layout gap="S" noPadding>
|
||||
<FancyForm bind:this={form}>
|
||||
<FancyInput
|
||||
label="Email"
|
||||
value={formData.email}
|
||||
disabled={true}
|
||||
error={errors.email}
|
||||
/>
|
||||
<FancyInput
|
||||
label="First name"
|
||||
value={formData.firstName}
|
||||
on:change={e => {
|
||||
formData = {
|
||||
...formData,
|
||||
firstName: e.detail,
|
||||
}
|
||||
}}
|
||||
validate={() => {
|
||||
let fieldError = {
|
||||
firstName: !formData.firstName
|
||||
? "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"
|
||||
<Layout gap="S" noPadding>
|
||||
<FancyForm bind:this={form}>
|
||||
<FancyInput
|
||||
label="Email"
|
||||
value={formData.email}
|
||||
disabled={true}
|
||||
error={errors.email}
|
||||
/>
|
||||
<FancyInput
|
||||
label="First name"
|
||||
value={formData.firstName}
|
||||
on:change={e => {
|
||||
formData = {
|
||||
...formData,
|
||||
firstName: e.detail,
|
||||
}
|
||||
}}
|
||||
validate={() => {
|
||||
let fieldError = {
|
||||
firstName: !formData.firstName
|
||||
? "Please enter your first name"
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
errors = handleError({ ...errors, ...fieldError })
|
||||
}}
|
||||
error={errors.confirmationPassword}
|
||||
disabled={onboarding}
|
||||
/>
|
||||
</FancyForm>
|
||||
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}
|
||||
/>
|
||||
{#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>
|
||||
<div>
|
||||
<Button
|
||||
size="L"
|
||||
disabled={Object.keys(errors).length > 0 || onboarding}
|
||||
cta
|
||||
on:click={acceptInvite}
|
||||
>
|
||||
Create account
|
||||
</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</TestimonialPage>
|
||||
</TestimonialPage>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
img {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<script>
|
||||
import { ModalContent, Body, Layout, Icon } from "@budibase/bbui"
|
||||
import { OnboardingType } from "../../../../../../constants"
|
||||
|
||||
export let chooseCreationType
|
||||
let emailOnboardingKey = "emailOnboarding"
|
||||
let basicOnboaridngKey = "basicOnboarding"
|
||||
|
||||
let selectedOnboardingType
|
||||
</script>
|
||||
|
@ -20,9 +19,9 @@
|
|||
<Layout noPadding gap="S">
|
||||
<div
|
||||
class="onboarding-type item"
|
||||
class:selected={selectedOnboardingType == emailOnboardingKey}
|
||||
class:selected={selectedOnboardingType == OnboardingType.EMAIL}
|
||||
on:click={() => {
|
||||
selectedOnboardingType = emailOnboardingKey
|
||||
selectedOnboardingType = OnboardingType.EMAIL
|
||||
}}
|
||||
>
|
||||
<div class="content onboarding-type-wrap">
|
||||
|
@ -32,7 +31,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div style="color: var(--spectrum-global-color-green-600); float: right">
|
||||
{#if selectedOnboardingType == emailOnboardingKey}
|
||||
{#if selectedOnboardingType == OnboardingType.EMAIL}
|
||||
<div class="checkmark-spacing">
|
||||
<Icon size="S" name="CheckmarkCircle" />
|
||||
</div>
|
||||
|
@ -42,9 +41,9 @@
|
|||
|
||||
<div
|
||||
class="onboarding-type item"
|
||||
class:selected={selectedOnboardingType == basicOnboaridngKey}
|
||||
class:selected={selectedOnboardingType == OnboardingType.PASSWORD}
|
||||
on:click={() => {
|
||||
selectedOnboardingType = basicOnboaridngKey
|
||||
selectedOnboardingType = OnboardingType.PASSWORD
|
||||
}}
|
||||
>
|
||||
<div class="content onboarding-type-wrap">
|
||||
|
@ -54,7 +53,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div style="color: var(--spectrum-global-color-green-600); float: right">
|
||||
{#if selectedOnboardingType == basicOnboaridngKey}
|
||||
{#if selectedOnboardingType == OnboardingType.PASSWORD}
|
||||
<div class="checkmark-spacing">
|
||||
<Icon size="S" name="CheckmarkCircle" />
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
Divider,
|
||||
} from "@budibase/bbui"
|
||||
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 DeleteRowsButton from "components/backend/DataTable/buttons/DeleteRowsButton.svelte"
|
||||
import GroupsTableRenderer from "./_components/GroupsTableRenderer.svelte"
|
||||
|
@ -27,6 +27,7 @@
|
|||
import { get } from "svelte/store"
|
||||
import { Constants, Utils, fetchData } from "@budibase/frontend-core"
|
||||
import { API } from "api"
|
||||
import { OnboardingType } from "../../../../../constants"
|
||||
|
||||
const fetch = fetchData({
|
||||
API,
|
||||
|
@ -105,10 +106,18 @@
|
|||
const debouncedUpdateFetch = Utils.debounce(updateFetch, 250)
|
||||
|
||||
const showOnboardingTypeModal = async addUsersData => {
|
||||
// no-op if users already exist
|
||||
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() {
|
||||
|
@ -181,7 +190,7 @@
|
|||
}
|
||||
|
||||
async function chooseCreationType(onboardingType) {
|
||||
if (onboardingType === "emailOnboarding") {
|
||||
if (onboardingType === OnboardingType.EMAIL) {
|
||||
await createUserFlow()
|
||||
} else {
|
||||
await createUsers()
|
||||
|
|
|
@ -50,8 +50,8 @@ function buildInviteAcceptValidation() {
|
|||
// prettier-ignore
|
||||
return auth.joiValidator.body(Joi.object({
|
||||
inviteCode: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
firstName: Joi.string().required(),
|
||||
password: Joi.string().optional(),
|
||||
firstName: Joi.string().optional(),
|
||||
lastName: Joi.string().optional(),
|
||||
}).required().unknown(true))
|
||||
}
|
||||
|
|
|
@ -140,7 +140,12 @@ const buildUser = async (
|
|||
hashedPassword = opts.hashPassword ? await utils.hash(password) : password
|
||||
} else if (dbUser) {
|
||||
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."
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ async function getACode(db: string, code: string, deleteCode = true) {
|
|||
const client = await getClient(db)
|
||||
const value = await client.get(code)
|
||||
if (!value) {
|
||||
throw "Invalid code."
|
||||
throw new Error("Invalid code.")
|
||||
}
|
||||
if (deleteCode) {
|
||||
await client.delete(code)
|
||||
|
|
Loading…
Reference in New Issue