mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
chore(console,core): launch organization jit
This commit is contained in:
parent
fc1699631c
commit
efa884c409
4 changed files with 85 additions and 73 deletions
20
.changeset/smart-laws-compare.md
Normal file
20
.changeset/smart-laws-compare.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
"@logto/console": minor
|
||||
"@logto/phrases": minor
|
||||
"@logto/schemas": minor
|
||||
"@logto/core": minor
|
||||
"@logto/integration-tests": patch
|
||||
---
|
||||
|
||||
feature: just-in-time user provisioning for organizations
|
||||
|
||||
This feature allows organizations to provision users when signing up with their email address or being added by Management API. If the user's email domain matches one of the organization's configured domains, the user will be automatically provisioned to the organization.
|
||||
|
||||
To enable this feature, you can add email domain via the Management API or the Logto Console:
|
||||
|
||||
- We added the following new endpoints to the Management API:
|
||||
- `GET /organizations/{organizationId}/email-domains`
|
||||
- `POST /organizations/{organizationId}/email-domains`
|
||||
- `PUT /organizations/{organizationId}/email-domains`
|
||||
- `DELETE /organizations/{organizationId}/email-domains/{emailDomain}`
|
||||
- In the Logto Console, you can manage email domains in the organization details page -> "Just-in-time provisioning" section.
|
|
@ -9,7 +9,6 @@ import DetailsForm from '@/components/DetailsForm';
|
|||
import FormCard from '@/components/FormCard';
|
||||
import MultiOptionInput from '@/components/MultiOptionInput';
|
||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import CodeEditor from '@/ds-components/CodeEditor';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
|
||||
|
@ -134,72 +133,70 @@ function Settings() {
|
|||
/>
|
||||
</FormField>
|
||||
</FormCard>
|
||||
{isDevFeaturesEnabled && (
|
||||
<FormCard
|
||||
title="organization_details.jit.title"
|
||||
description="organization_details.jit.description"
|
||||
>
|
||||
<FormField title="organization_details.jit.is_enabled_title">
|
||||
<Controller
|
||||
name="isJitEnabled"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className={styles.jitContent}>
|
||||
<RadioGroup
|
||||
name="isJitEnabled"
|
||||
value={String(field.value)}
|
||||
onChange={(value) => {
|
||||
field.onChange(value === 'true');
|
||||
}}
|
||||
>
|
||||
<Radio
|
||||
value="false"
|
||||
title="organization_details.jit.is_enabled_false_description"
|
||||
/>
|
||||
<Radio
|
||||
value="true"
|
||||
title="organization_details.jit.is_enabled_true_description"
|
||||
/>
|
||||
</RadioGroup>
|
||||
{field.value && (
|
||||
<Controller
|
||||
name="jitEmailDomains"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<MultiOptionInput
|
||||
className={styles.emailDomains}
|
||||
values={value}
|
||||
renderValue={(value) => value}
|
||||
validateInput={(input) => {
|
||||
if (!domainRegExp.test(input)) {
|
||||
return t('organization_details.jit.invalid_domain');
|
||||
}
|
||||
<FormCard
|
||||
title="organization_details.jit.title"
|
||||
description="organization_details.jit.description"
|
||||
>
|
||||
<FormField title="organization_details.jit.is_enabled_title">
|
||||
<Controller
|
||||
name="isJitEnabled"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className={styles.jitContent}>
|
||||
<RadioGroup
|
||||
name="isJitEnabled"
|
||||
value={String(field.value)}
|
||||
onChange={(value) => {
|
||||
field.onChange(value === 'true');
|
||||
}}
|
||||
>
|
||||
<Radio
|
||||
value="false"
|
||||
title="organization_details.jit.is_enabled_false_description"
|
||||
/>
|
||||
<Radio
|
||||
value="true"
|
||||
title="organization_details.jit.is_enabled_true_description"
|
||||
/>
|
||||
</RadioGroup>
|
||||
{field.value && (
|
||||
<Controller
|
||||
name="jitEmailDomains"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<MultiOptionInput
|
||||
className={styles.emailDomains}
|
||||
values={value}
|
||||
renderValue={(value) => value}
|
||||
validateInput={(input) => {
|
||||
if (!domainRegExp.test(input)) {
|
||||
return t('organization_details.jit.invalid_domain');
|
||||
}
|
||||
|
||||
if (value.includes(input)) {
|
||||
return t('organization_details.jit.domain_already_added');
|
||||
}
|
||||
if (value.includes(input)) {
|
||||
return t('organization_details.jit.domain_already_added');
|
||||
}
|
||||
|
||||
return { value: input };
|
||||
}}
|
||||
placeholder={t('organization_details.jit.email_domains_placeholder')}
|
||||
error={errors.jitEmailDomains?.message}
|
||||
onChange={onChange}
|
||||
onError={(error) => {
|
||||
setError('jitEmailDomains', { type: 'custom', message: error });
|
||||
}}
|
||||
onClearError={() => {
|
||||
clearErrors('jitEmailDomains');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
</FormCard>
|
||||
)}
|
||||
return { value: input };
|
||||
}}
|
||||
placeholder={t('organization_details.jit.email_domains_placeholder')}
|
||||
error={errors.jitEmailDomains?.message}
|
||||
onChange={onChange}
|
||||
onError={(error) => {
|
||||
setError('jitEmailDomains', { type: 'custom', message: error });
|
||||
}}
|
||||
onClearError={() => {
|
||||
clearErrors('jitEmailDomains');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
</FormCard>
|
||||
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleting && isDirty} />
|
||||
</DetailsForm>
|
||||
);
|
||||
|
|
|
@ -142,11 +142,11 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: If the user's email is not verified, we should not provision the user into any organization.
|
||||
const provisionOrganizations = async (): Promise<readonly string[]> => {
|
||||
// Just-in-time organization provisioning
|
||||
const userEmailDomain = data.primaryEmail?.split('@')[1];
|
||||
// TODO: Remove this check when launching
|
||||
if (EnvSet.values.isDevFeaturesEnabled && userEmailDomain) {
|
||||
if (userEmailDomain) {
|
||||
const organizationQueries = new OrganizationQueries(connection);
|
||||
const organizationIds = await organizationQueries.emailDomains.getOrganizationIdsByDomain(
|
||||
userEmailDomain
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
import { yes } from '@silverhand/essentials';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import koaPagination from '#src/middleware/koa-pagination.js';
|
||||
import koaQuotaGuard from '#src/middleware/koa-quota-guard.js';
|
||||
|
@ -139,11 +138,7 @@ export default function organizationRoutes<T extends ManagementApiRouter>(
|
|||
);
|
||||
|
||||
userRoleRelationRoutes(router, organizations);
|
||||
|
||||
// TODO: Remove this check when launching
|
||||
if (EnvSet.values.isDevFeaturesEnabled) {
|
||||
emailDomainRoutes(router, organizations);
|
||||
}
|
||||
emailDomainRoutes(router, organizations);
|
||||
|
||||
// MARK: Mount sub-routes
|
||||
organizationRoleRoutes(...args);
|
||||
|
|
Loading…
Add table
Reference in a new issue