fix(login): remove loginSettings.allowRegister check for IDPs (#11731)

Closes #11138

# Which Problems Are Solved

Previously, the application strictly checked the allowRegister
setting—which typically applies to local account creation—before
considering if a user without an account should be redirected to an IDP,
or whether they could access the register page. This blocked users from
registering via identity providers when local registration was disabled
but IDPs were configured.

# How the Problems Are Solved

- Removed the allowRegister dependency when deciding to redirect an
unknown user to an IDP. The logic now only checks
!allowLocalAuthentication, correctly opening up the IDP registration
flow even when local auth/registration is not allowed.
- Modified the block condition so the register page is only restricted
if allowRegister is false and there are no configured identityProviders
available.
This commit is contained in:
Max Peintner
2026-03-05 08:16:49 +00:00
committed by GitHub
parent 8ad1d28086
commit b33c811a01
3 changed files with 58 additions and 7 deletions
+17 -1
View File
@@ -55,7 +55,23 @@ export default async function Page(props: { searchParams: Promise<Record<string
});
});
if (!loginSettings?.allowRegister) {
if (!loginSettings) {
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col space-y-4">
<h1>
<Translated i18nKey="title" namespace="register" />
</h1>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
<div className="w-full"></div>
</DynamicTheme>
);
}
if (!loginSettings?.allowRegister && (!loginSettings.allowExternalIdp || identityProviders.length === 0)) {
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col space-y-4">
+37 -3
View File
@@ -276,7 +276,7 @@ describe("sendLoginname", () => {
});
mockListIDPLinks.mockResolvedValue({ result: [] });
mockGetActiveIdentityProviders.mockResolvedValue({
identityProviders: [{ id: "org-idp-123", type: 0 }],
identityProviders: [{ id: "org-idp-123", type: 0, options: { isAutoCreation: true } }],
});
mockIdpTypeToSlug.mockReturnValue("google");
mockStartIdentityProviderFlow.mockResolvedValue({ url: "https://org-idp.example.com/auth" });
@@ -469,7 +469,7 @@ describe("sendLoginname", () => {
allowLocalAuthentication: false,
});
mockGetActiveIdentityProviders.mockResolvedValue({
identityProviders: [{ id: "idp123", type: "OIDC" }],
identityProviders: [{ id: "idp123", type: "OIDC", options: { isAutoCreation: true } }],
});
mockStartIdentityProviderFlow.mockResolvedValue({ url: "https://idp.example.com/auth" });
@@ -503,6 +503,7 @@ describe("sendLoginname", () => {
test("should redirect to password when ignoreUnknownUsernames is true", async () => {
mockGetLoginSettings.mockResolvedValue({
ignoreUnknownUsernames: true,
allowLocalAuthentication: true,
});
const result = await sendLoginname({
@@ -532,6 +533,39 @@ describe("sendLoginname", () => {
expect(result).toEqual({ error: "errors.userNotFound" });
});
test("should redirect to IDP when allowRegister: false, allowLocalAuthentication: false and a single active IDP exists", async () => {
mockGetLoginSettings.mockResolvedValue({
allowRegister: false,
allowLocalAuthentication: false,
});
mockGetActiveIdentityProviders.mockResolvedValue({
identityProviders: [{ id: "idp123", type: "OIDC", options: { isAutoCreation: true } }],
});
mockStartIdentityProviderFlow.mockResolvedValue({ url: "https://idp.example.com/auth" });
const result = await sendLoginname({
loginName: "[email protected]",
});
expect(result).toEqual({ redirect: "https://idp.example.com/auth" });
});
test("should not redirect to IDP when single active IDP does not allow creation", async () => {
mockGetLoginSettings.mockResolvedValue({
allowRegister: false,
allowLocalAuthentication: false,
});
mockGetActiveIdentityProviders.mockResolvedValue({
identityProviders: [{ id: "idp123", type: "OIDC", options: { isAutoCreation: false, isCreationAllowed: false } }],
});
const result = await sendLoginname({
loginName: "[email protected]",
});
expect(result).toEqual({ error: "errors.userNotFound" });
});
test("should discover organization from domain suffix when user not found without org context", async () => {
// Mock login settings for instance level (no org context)
mockGetLoginSettings
@@ -592,7 +626,7 @@ describe("sendLoginname", () => {
});
mockGetActiveIdentityProviders.mockResolvedValue({
identityProviders: [{ id: "idp123", type: "OIDC" }],
identityProviders: [{ id: "idp123", type: "OIDC", options: { isAutoCreation: true } }],
});
mockStartIdentityProviderFlow.mockResolvedValue({ url: "https://idp.example.com/auth?org=discovered-org-456" });
+4 -3
View File
@@ -119,7 +119,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
// If no IDP links exist for the user (or no userId provided), try to get active IDPs from the organization
if (identityProviders.length === 0) {
const activeIdps = await getActiveIdentityProviders({ serviceConfig, orgId: organization }).then((resp) => {
return resp.identityProviders;
return resp.identityProviders.filter((idp) => idp.options?.isAutoCreation || idp.options?.isCreationAllowed);
});
// If exactly one active IDP exists in the organization, redirect to it
@@ -525,8 +525,9 @@ export async function sendLoginname(command: SendLoginnameCommand) {
}
}
// user not found, check if register is enabled on instance / organization context
if (effectiveLoginSettings?.allowRegister && !effectiveLoginSettings?.allowLocalAuthentication) {
// user not found, check if IDPs are available when local auth is not allowed
if (!effectiveLoginSettings?.allowLocalAuthentication) {
console.log("redirecting to IDP (register allowed, password not allowed)");
logger.debug("redirecting to IDP (register allowed, password not allowed)");
const resp = await redirectUserToIDP(undefined, discoveredOrganization);
if (resp) {