refactor(fe): security page
This commit is contained in:
@@ -1,38 +1,36 @@
|
||||
<template>
|
||||
<section class="flex flex-col gap-3">
|
||||
<SettingsSectionHeader title="Authentication" subheading class="mb-3" />
|
||||
<section class="flex flex-col gap-2 border-t border-outline-2 py-8 mt-8">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex flex-col">
|
||||
<h4 class="text-heading-lg">SSO</h4>
|
||||
<p class="text-body-xs text-foreground-2">
|
||||
Allow logins through your OpenID identity provider.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="workspace.hasAccessToSSO">
|
||||
<FormButton
|
||||
v-if="isWorkspaceAdmin"
|
||||
:disabled="isFormVisible || !!provider"
|
||||
@click="handleConfigureClick"
|
||||
>
|
||||
Configure
|
||||
</FormButton>
|
||||
|
||||
<div v-else v-tippy="`You must be a workspace admin`">
|
||||
<FormButton disabled>Configure</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormButton v-else :to="settingsWorkspaceRoutes.billing.route(workspace.slug)">
|
||||
Upgrade to Business
|
||||
</FormButton>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="flex justify-center">
|
||||
<CommonLoadingIcon />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="flex-1 flex-col pr-6 gap-y-1">
|
||||
<p class="text-body-xs font-medium text-foreground">Enable SSO</p>
|
||||
<p class="text-body-2xs text-foreground-2 leading-5 max-w-md">
|
||||
Allow logins through your OpenID identity provider.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="workspace.hasAccessToSSO">
|
||||
<FormButton
|
||||
v-if="isWorkspaceAdmin"
|
||||
:disabled="isFormVisible || !!provider"
|
||||
@click="handleConfigureClick"
|
||||
>
|
||||
Configure
|
||||
</FormButton>
|
||||
|
||||
<div v-else v-tippy="`You must be a workspace admin`">
|
||||
<FormButton disabled>Configure</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormButton v-else :to="settingsWorkspaceRoutes.billing.route(workspace.slug)">
|
||||
Upgrade to Business
|
||||
</FormButton>
|
||||
</div>
|
||||
|
||||
<CommonCard
|
||||
v-if="!workspace.hasAccessToSSO && workspace.sso?.provider?.id"
|
||||
class="bg-foundation"
|
||||
|
||||
@@ -5,81 +5,97 @@
|
||||
title="Security"
|
||||
text="Manage verified workspace domains and associated features."
|
||||
/>
|
||||
<template v-if="isSsoEnabled">
|
||||
<SettingsWorkspacesSecuritySsoWrapper v-if="workspace" :workspace="workspace" />
|
||||
<hr class="my-6 md:my-8 border-outline-2" />
|
||||
</template>
|
||||
<section>
|
||||
<SettingsSectionHeader
|
||||
title="Allowed email domains"
|
||||
class="pb-4 md:pb-6"
|
||||
subheading
|
||||
/>
|
||||
<CommonCard v-if="workspace?.sso?.provider?.id" class="bg-foundation mb-4">
|
||||
With SSO enabled, allowed domains are configured on your identity provider's
|
||||
side.
|
||||
</CommonCard>
|
||||
<ul v-if="hasWorkspaceDomains">
|
||||
<li
|
||||
v-for="domain in workspaceDomains"
|
||||
:key="domain.id"
|
||||
class="border-x border-b first:border-t first:rounded-t-lg border-outline-2 last:rounded-b-lg p-6 py-4 flex items-center"
|
||||
>
|
||||
<p class="text-body-xs font-medium flex-1">@{{ domain.domain }}</p>
|
||||
<FormButton
|
||||
:disabled="workspaceDomains.length === 1 && isDomainProtectionEnabled"
|
||||
color="outline"
|
||||
@click="openRemoveDialog(domain)"
|
||||
<div class="flex flex-col divide-y divide-outline-2">
|
||||
<section>
|
||||
<SettingsSectionHeader title="Workspace discoverability" subheading />
|
||||
<p class="text-body-xs text-foreground-2 mt-2 mb-6">
|
||||
Let users discover the workspace if they sign up with a matching email.
|
||||
</p>
|
||||
<CommonCard v-if="workspace?.sso?.provider?.id" class="bg-foundation mb-4">
|
||||
With SSO enabled, allowed domains are configured on your identity provider's
|
||||
side.
|
||||
</CommonCard>
|
||||
<ul v-if="hasWorkspaceDomains">
|
||||
<li
|
||||
v-for="domain in workspaceDomains"
|
||||
:key="domain.id"
|
||||
class="border-x border-b first:border-t first:rounded-t-lg border-outline-2 last:rounded-b-lg p-6 py-4 flex items-center"
|
||||
>
|
||||
Delete
|
||||
</FormButton>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="text-body-xs font-medium flex-1">@{{ domain.domain }}</p>
|
||||
<FormButton color="outline" size="sm" @click="openRemoveDialog(domain)">
|
||||
Delete
|
||||
</FormButton>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p
|
||||
v-else
|
||||
class="text-body-xs text-foreground-2 border border-outline-2 p-6 rounded-lg"
|
||||
>
|
||||
No verified domains yet
|
||||
</p>
|
||||
</section>
|
||||
<section class="mt-8">
|
||||
<div class="grid grid-cols-2 gap-x-6 items-center">
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<p class="text-body-xs font-medium text-foreground">New domain</p>
|
||||
<p class="text-body-2xs text-foreground-2 leading-5">
|
||||
Add a domain from a list of email domains for your active account.
|
||||
</p>
|
||||
<p
|
||||
v-else
|
||||
class="text-body-xs text-center text-foreground-2 border border-outline-2 p-6 rounded-lg"
|
||||
>
|
||||
No verified domains yet
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-x-6 mt-6">
|
||||
<div class="flex flex-col gap-y-1">
|
||||
<p class="text-body-xs font-medium text-foreground">New domain</p>
|
||||
<p class="text-body-2xs text-foreground-2 leading-5">
|
||||
Add a domain from a list of email domains for your active account.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-x-3">
|
||||
<FormSelectBase
|
||||
v-model="selectedDomain"
|
||||
:items="verifiedUserDomains"
|
||||
:disabled-item-predicate="disabledItemPredicate"
|
||||
disabled-item-tooltip="This domain can't be used for verified workspace domains"
|
||||
name="workspaceDomains"
|
||||
label="Verified domains"
|
||||
class="w-full"
|
||||
>
|
||||
<template #nothing-selected>Select domain</template>
|
||||
<template #something-selected="{ value }">@{{ value }}</template>
|
||||
<template #option="{ item }">
|
||||
<div class="flex items-center">@{{ item }}</div>
|
||||
</template>
|
||||
</FormSelectBase>
|
||||
<FormButton :disabled="!selectedDomain" @click="addDomain">
|
||||
Add
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-x-3">
|
||||
<FormSelectBase
|
||||
v-model="selectedDomain"
|
||||
:items="verifiedUserDomains"
|
||||
:disabled-item-predicate="disabledItemPredicate"
|
||||
disabled-item-tooltip="This domain can't be used for verified workspace domains"
|
||||
name="workspaceDomains"
|
||||
label="Verified domains"
|
||||
class="w-full"
|
||||
>
|
||||
<template #nothing-selected>Select domain</template>
|
||||
<template #something-selected="{ value }">@{{ value }}</template>
|
||||
<template #option="{ item }">
|
||||
<div class="flex items-center">@{{ item }}</div>
|
||||
</template>
|
||||
</FormSelectBase>
|
||||
<FormButton :disabled="!selectedDomain" @click="addDomain">Add</FormButton>
|
||||
|
||||
<div class="mt-6 flex flex-col gap-2">
|
||||
<p class="text-body-xs font-medium text-foreground">New user policy</p>
|
||||
<FormRadio
|
||||
v-for="option in radioOptions"
|
||||
:key="option.value"
|
||||
:label="option.title"
|
||||
:description="option.description"
|
||||
:value="option.value"
|
||||
name="measurementType"
|
||||
:checked="joinPolicy === option.value"
|
||||
size="sm"
|
||||
@change="joinPolicy = option.value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="flex flex-col space-y-3 mt-8">
|
||||
<div class="flex flex-col space-y-8">
|
||||
<div class="flex items-center">
|
||||
</section>
|
||||
|
||||
<template v-if="isSsoEnabled">
|
||||
<SettingsWorkspacesSecuritySsoWrapper
|
||||
v-if="workspace"
|
||||
:workspace="workspace"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<section class="py-8">
|
||||
<SettingsSectionHeader subheading title="Allowed email domains" />
|
||||
<p class="text-body-xs text-foreground-2 mt-2 mb-6">
|
||||
Only users with email addresses from your verified domains can be added as
|
||||
workspace members or administrators.
|
||||
</p>
|
||||
<div class="flex">
|
||||
<div class="flex-1 flex-col pr-6 gap-y-1">
|
||||
<div class="flex items-center">
|
||||
<p class="text-body-xs font-medium text-foreground">
|
||||
Domain protection
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-body-xs font-medium text-foreground">Domain protection</p>
|
||||
<p class="text-body-2xs text-foreground-2 leading-5 max-w-md">
|
||||
Only users with email addresses from your verified domains can be added
|
||||
as workspace members or administrators.
|
||||
@@ -96,25 +112,8 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1 flex-col pr-6 gap-y-1">
|
||||
<p class="text-body-xs font-medium text-foreground">
|
||||
Domain-based discoverability
|
||||
</p>
|
||||
<p class="text-body-2xs text-foreground-2 leading-5 max-w-md">
|
||||
When enabled, users with a verified email address from your verified
|
||||
domain list will be able to request to join this workspace.
|
||||
</p>
|
||||
</div>
|
||||
<FormSwitch
|
||||
v-model="isDomainDiscoverabilityEnabled"
|
||||
name="domain-discoverability"
|
||||
:disabled="!hasWorkspaceDomains"
|
||||
:show-label="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SettingsWorkspacesSecurityDomainRemoveDialog
|
||||
@@ -141,6 +140,7 @@ import {
|
||||
workspaceUpdateDiscoverabilityMutation
|
||||
} from '~/lib/workspaces/graphql/mutations'
|
||||
import { useVerifiedUserEmailDomains } from '~/lib/workspaces/composables/security'
|
||||
import { FormRadio } from '@speckle/ui-components'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesSecurity_Workspace on Workspace {
|
||||
@@ -172,6 +172,11 @@ useHead({
|
||||
title: 'Settings | Workspace - Security'
|
||||
})
|
||||
|
||||
enum JoinPolicy {
|
||||
AdminApproval = 'admin-approval',
|
||||
AutoJoin = 'auto-join'
|
||||
}
|
||||
|
||||
const slug = computed(() => (route.params.slug as string) || '')
|
||||
|
||||
const { domains: userEmailDomains } = useVerifiedUserEmailDomains({
|
||||
@@ -193,6 +198,7 @@ const showRemoveDomainDialog = ref(false)
|
||||
const removeDialogDomain =
|
||||
ref<SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragment>()
|
||||
const blockedDomainItems: ShallowRef<string[]> = shallowRef(blockedDomains)
|
||||
const joinPolicy = ref<JoinPolicy>(JoinPolicy.AdminApproval)
|
||||
|
||||
const { result } = useQuery(settingsWorkspacesSecurityQuery, {
|
||||
slug: slug.value
|
||||
@@ -278,13 +284,15 @@ const tooltipText = computed(() => {
|
||||
|
||||
const addDomain = async () => {
|
||||
if (!selectedDomain.value || !workspace.value) return
|
||||
const isFirstDomain = !hasWorkspaceDomains.value
|
||||
|
||||
await addWorkspaceDomain.mutate(
|
||||
{
|
||||
domain: selectedDomain.value,
|
||||
workspaceId: workspace.value.id
|
||||
},
|
||||
workspace.value.domains ?? [],
|
||||
workspace.value.discoverabilityEnabled,
|
||||
isFirstDomain,
|
||||
workspace.value.domainBasedMembershipProtectionEnabled,
|
||||
workspace.value.hasAccessToSSO,
|
||||
workspace.value.hasAccessToDomainBasedSecurityPolicies
|
||||
@@ -308,6 +316,20 @@ const disabledItemPredicate = (item: string) => {
|
||||
return blockedDomainItems.value.includes(item)
|
||||
}
|
||||
|
||||
const radioOptions = [
|
||||
{
|
||||
title: 'Admin approval required',
|
||||
description: 'Users must be approved by an admin to join the workspace.',
|
||||
value: JoinPolicy.AdminApproval
|
||||
},
|
||||
{
|
||||
title: 'Auto-join',
|
||||
description:
|
||||
'Users with a verified email address can join the workspace without admin approval.',
|
||||
value: JoinPolicy.AutoJoin
|
||||
}
|
||||
] as const
|
||||
|
||||
watch(
|
||||
() => workspaceDomains.value,
|
||||
() => {
|
||||
|
||||
Reference in New Issue
Block a user