feat: remove default personal orgs from custom sso (#2741)

This commit is contained in:
David Nguyen
2026-05-05 14:50:07 +10:00
committed by GitHub
parent 6a6ef8d2ad
commit f10d3284ba
9 changed files with 67 additions and 6 deletions
@@ -51,6 +51,7 @@ const ZProviderFormSchema = ZUpdateOrganisationAuthenticationPortalRequestSchema
clientId: true,
autoProvisionUsers: true,
defaultOrganisationRole: true,
allowPersonalOrganisations: true,
})
.extend({
clientSecret: z.string().nullable(),
@@ -120,6 +121,7 @@ const SSOProviderForm = ({ authenticationPortal }: SSOProviderFormProps) => {
autoProvisionUsers: authenticationPortal.autoProvisionUsers,
defaultOrganisationRole: authenticationPortal.defaultOrganisationRole,
allowedDomains: authenticationPortal.allowedDomains.join(' '),
allowPersonalOrganisations: authenticationPortal.allowPersonalOrganisations,
},
});
@@ -161,6 +163,7 @@ const SSOProviderForm = ({ authenticationPortal }: SSOProviderFormProps) => {
autoProvisionUsers: values.autoProvisionUsers,
defaultOrganisationRole: values.defaultOrganisationRole,
allowedDomains: values.allowedDomains.split(' ').filter(Boolean),
allowPersonalOrganisations: values.allowPersonalOrganisations,
},
});
@@ -390,6 +393,30 @@ const SSOProviderForm = ({ authenticationPortal }: SSOProviderFormProps) => {
)}
/> */}
<FormField
control={form.control}
name="allowPersonalOrganisations"
render={({ field }) => (
<FormItem className="flex items-center justify-between rounded-lg border px-4 py-3">
<div className="space-y-0.5">
<FormLabel>
<Trans>Allow Personal Organisations</Trans>
</FormLabel>
<p className="text-sm text-muted-foreground">
<Trans>
When enabled, users signing in via SSO for the first time will also receive
their own personal organisation.
</Trans>
</p>
</div>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="enabled"
@@ -76,7 +76,10 @@ export const handleOAuthOrganisationCallbackUrl = async (
},
});
await onCreateUserHook(userToLink).catch((err) => {
await onCreateUserHook(userToLink, {
skipPersonalOrganisation:
!organisation.organisationAuthenticationPortal.allowPersonalOrganisations,
}).catch((err) => {
// Todo: (RR7) Add logging.
console.error(err);
});
+16 -2
View File
@@ -60,13 +60,27 @@ export const createUser = async ({ name, email, password, signature }: CreateUse
return user;
};
export type OnCreateUserHookOptions = {
/**
* When true, do not create a "Personal Organisation" for the new user.
* Used by the Organisation SSO signup path, where the user is intended
* to operate inside the SSO organisation rather than a personal space.
*
* Defaults to false — preserves the historical behaviour of creating a
* personal organisation for every new user.
*/
skipPersonalOrganisation?: boolean;
};
/**
* Should be run after a user is created, example during email password signup or google sign in.
*
* @returns User
*/
export const onCreateUserHook = async (user: User) => {
await createPersonalOrganisation({ userId: user.id });
export const onCreateUserHook = async (user: User, options: OnCreateUserHookOptions = {}) => {
if (!options.skipPersonalOrganisation) {
await createPersonalOrganisation({ userId: user.id });
}
return user;
};
@@ -0,0 +1,10 @@
-- AlterTable
-- Add the column with a temporary default of `true` so that all existing rows
-- (representing organisations created before this feature) are backfilled to
-- `true` — preserving the historical behaviour of creating personal
-- organisations for SSO-provisioned users.
ALTER TABLE "OrganisationAuthenticationPortal" ADD COLUMN "allowPersonalOrganisations" BOOLEAN NOT NULL DEFAULT true;
-- Switch the column default to `false` so that any organisations created from
-- now on opt out of personal-organisation creation by default.
ALTER TABLE "OrganisationAuthenticationPortal" ALTER COLUMN "allowPersonalOrganisations" SET DEFAULT false;
+4 -3
View File
@@ -1100,9 +1100,10 @@ model OrganisationAuthenticationPortal {
clientSecret String @default("")
wellKnownUrl String @default("")
defaultOrganisationRole OrganisationMemberRole @default(MEMBER)
autoProvisionUsers Boolean @default(true)
allowedDomains String[] @default([])
defaultOrganisationRole OrganisationMemberRole @default(MEMBER)
autoProvisionUsers Boolean @default(true)
allowedDomains String[] @default([])
allowPersonalOrganisations Boolean @default(false)
}
model Counter {
@@ -52,6 +52,7 @@ export const getOrganisationAuthenticationPortal = async ({
wellKnownUrl: true,
autoProvisionUsers: true,
allowedDomains: true,
allowPersonalOrganisations: true,
clientSecret: true,
},
},
@@ -79,6 +80,7 @@ export const getOrganisationAuthenticationPortal = async ({
wellKnownUrl: portal.wellKnownUrl,
autoProvisionUsers: portal.autoProvisionUsers,
allowedDomains: portal.allowedDomains,
allowPersonalOrganisations: portal.allowPersonalOrganisations,
clientSecretProvided: Boolean(portal.clientSecret),
};
};
@@ -14,6 +14,7 @@ export const ZGetOrganisationAuthenticationPortalResponseSchema =
wellKnownUrl: true,
autoProvisionUsers: true,
allowedDomains: true,
allowPersonalOrganisations: true,
}).extend({
/**
* Whether we have the client secret in the database.
@@ -61,6 +61,7 @@ export const updateOrganisationAuthenticationPortalRoute = authenticatedProcedur
wellKnownUrl,
autoProvisionUsers,
allowedDomains,
allowPersonalOrganisations,
} = data;
if (
@@ -104,6 +105,7 @@ export const updateOrganisationAuthenticationPortalRoute = authenticatedProcedur
wellKnownUrl,
autoProvisionUsers,
allowedDomains,
allowPersonalOrganisations,
},
});
});
@@ -14,6 +14,7 @@ export const ZUpdateOrganisationAuthenticationPortalRequestSchema = z.object({
wellKnownUrl: z.union([z.string().url(), z.literal('')]),
autoProvisionUsers: z.boolean(),
allowedDomains: z.array(z.string().regex(domainRegex)),
allowPersonalOrganisations: z.boolean(),
}),
});