Merge pull request #3424 from Infisical/misc/allow-org-admins-to-bypass-sso-enforcement

misc: allow org admins to bypass sso enforcement
This commit is contained in:
Daniel Hougaard
2025-04-17 00:22:18 +04:00
committed by GitHub
33 changed files with 385 additions and 55 deletions
@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled"))) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.boolean("bypassOrgAuthEnabled").defaultTo(false).notNullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled")) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.dropColumn("bypassOrgAuthEnabled");
});
}
}
+2 -1
View File
@@ -26,7 +26,8 @@ export const OrganizationsSchema = z.object({
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(), allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
shouldUseNewPrivilegeSystem: z.boolean().default(true), shouldUseNewPrivilegeSystem: z.boolean().default(true),
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(), privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
privilegeUpgradeInitiatedAt: z.date().nullable().optional() privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
bypassOrgAuthEnabled: z.boolean().default(false)
}); });
export type TOrganizations = z.infer<typeof OrganizationsSchema>; export type TOrganizations = z.infer<typeof OrganizationsSchema>;
@@ -3,6 +3,7 @@ import { z } from "zod";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { import {
IdentityProjectMembershipRoleSchema, IdentityProjectMembershipRoleSchema,
OrgMembershipRole,
OrgMembershipsSchema, OrgMembershipsSchema,
TableName, TableName,
TProjectRoles, TProjectRoles,
@@ -53,6 +54,7 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"), db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"),
db.ref("permissions").withSchema(TableName.OrgRoles), db.ref("permissions").withSchema(TableName.OrgRoles),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"),
db.ref("groupId").withSchema("userGroups"), db.ref("groupId").withSchema("userGroups"),
db.ref("groupOrgId").withSchema("userGroups"), db.ref("groupOrgId").withSchema("userGroups"),
db.ref("groupName").withSchema("userGroups"), db.ref("groupName").withSchema("userGroups"),
@@ -71,6 +73,7 @@ export const permissionDALFactory = (db: TDbClient) => {
OrgMembershipsSchema.extend({ OrgMembershipsSchema.extend({
permissions: z.unknown(), permissions: z.unknown(),
orgAuthEnforced: z.boolean().optional().nullable(), orgAuthEnforced: z.boolean().optional().nullable(),
bypassOrgAuthEnabled: z.boolean(),
customRoleSlug: z.string().optional().nullable(), customRoleSlug: z.string().optional().nullable(),
shouldUseNewPrivilegeSystem: z.boolean() shouldUseNewPrivilegeSystem: z.boolean()
}).parse(el), }).parse(el),
@@ -571,6 +574,11 @@ export const permissionDALFactory = (db: TDbClient) => {
}) })
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId])) .join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`) .join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.join(TableName.OrgMembership, (qb) => {
void qb
.on(`${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.andOn(`${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`);
})
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => { .leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
void queryBuilder void queryBuilder
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`) .on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
@@ -670,6 +678,8 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"), db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"),
db.ref("role").withSchema(TableName.OrgMembership).as("orgRole"),
db.ref("orgId").withSchema(TableName.Project), db.ref("orgId").withSchema(TableName.Project),
db.ref("type").withSchema(TableName.Project).as("projectType"), db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("id").withSchema(TableName.Project).as("projectId"), db.ref("id").withSchema(TableName.Project).as("projectId"),
@@ -683,6 +693,7 @@ export const permissionDALFactory = (db: TDbClient) => {
orgId, orgId,
username, username,
orgAuthEnforced, orgAuthEnforced,
orgRole,
membershipId, membershipId,
groupMembershipId, groupMembershipId,
membershipCreatedAt, membershipCreatedAt,
@@ -690,10 +701,12 @@ export const permissionDALFactory = (db: TDbClient) => {
groupMembershipUpdatedAt, groupMembershipUpdatedAt,
membershipUpdatedAt, membershipUpdatedAt,
projectType, projectType,
shouldUseNewPrivilegeSystem shouldUseNewPrivilegeSystem,
bypassOrgAuthEnabled
}) => ({ }) => ({
orgId, orgId,
orgAuthEnforced, orgAuthEnforced,
orgRole: orgRole as OrgMembershipRole,
userId, userId,
projectId, projectId,
username, username,
@@ -701,7 +714,8 @@ export const permissionDALFactory = (db: TDbClient) => {
id: membershipId || groupMembershipId, id: membershipId || groupMembershipId,
createdAt: membershipCreatedAt || groupMembershipCreatedAt, createdAt: membershipCreatedAt || groupMembershipCreatedAt,
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt, updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt,
shouldUseNewPrivilegeSystem shouldUseNewPrivilegeSystem,
bypassOrgAuthEnabled
}), }),
childrenMapper: [ childrenMapper: [
{ {
@@ -2,7 +2,7 @@
import { ForbiddenError, MongoAbility, PureAbility, subject } from "@casl/ability"; import { ForbiddenError, MongoAbility, PureAbility, subject } from "@casl/ability";
import { z } from "zod"; import { z } from "zod";
import { TOrganizations } from "@app/db/schemas"; import { OrgMembershipRole, TOrganizations } from "@app/db/schemas";
import { validatePermissionBoundary } from "@app/lib/casl/boundary"; import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type"; import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
@@ -118,11 +118,20 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
].includes(actorAuthMethod); ].includes(actorAuthMethod);
} }
function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrganizations["authEnforced"]) { function validateOrgSSO(
actorAuthMethod: ActorAuthMethod,
isOrgSsoEnforced: TOrganizations["authEnforced"],
isOrgSsoBypassEnabled: TOrganizations["bypassOrgAuthEnabled"],
orgRole: OrgMembershipRole
) {
if (actorAuthMethod === undefined) { if (actorAuthMethod === undefined) {
throw new UnauthorizedError({ name: "No auth method defined" }); throw new UnauthorizedError({ name: "No auth method defined" });
} }
if (isOrgSsoEnforced && isOrgSsoBypassEnabled && orgRole === OrgMembershipRole.Admin) {
return;
}
if ( if (
isOrgSsoEnforced && isOrgSsoEnforced &&
actorAuthMethod !== null && actorAuthMethod !== null &&
@@ -139,7 +139,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
} }
validateOrgSSO(authMethod, membership.orgAuthEnforced); validateOrgSSO(
authMethod,
membership.orgAuthEnforced,
membership.bypassOrgAuthEnabled,
membership.role as OrgMembershipRole
);
const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat( const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat(
membership?.groups?.map(({ role, customRolePermission }) => ({ membership?.groups?.map(({ role, customRolePermission }) => ({
@@ -226,7 +231,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
} }
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced); validateOrgSSO(
authMethod,
userProjectPermission.orgAuthEnforced,
userProjectPermission.bypassOrgAuthEnabled,
userProjectPermission.orgRole
);
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) { if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
throw new BadRequestError({ throw new BadRequestError({
@@ -31,7 +31,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
200: z.object({ 200: z.object({
organizations: sanitizedOrganizationSchema organizations: sanitizedOrganizationSchema
.extend({ .extend({
orgAuthMethod: z.string() orgAuthMethod: z.string(),
userRole: z.string()
}) })
.array() .array()
}) })
@@ -259,7 +260,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(), defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
enforceMfa: z.boolean().optional(), enforceMfa: z.boolean().optional(),
selectedMfaMethod: z.nativeEnum(MfaMethod).optional(), selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
allowSecretSharingOutsideOrganization: z.boolean().optional() allowSecretSharingOutsideOrganization: z.boolean().optional(),
bypassOrgAuthEnabled: z.boolean().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
+37 -20
View File
@@ -2,7 +2,7 @@ import bcrypt from "bcrypt";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { Knex } from "knex"; import { Knex } from "knex";
import { TUsers, UserDeviceSchema } from "@app/db/schemas"; import { OrgMembershipRole, TUsers, UserDeviceSchema } from "@app/db/schemas";
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
@@ -174,20 +174,25 @@ export const authLoginServiceFactory = ({
const userEnc = await userDAL.findUserEncKeyByUsername({ const userEnc = await userDAL.findUserEncKeyByUsername({
username: email username: email
}); });
const serverCfg = await getServerCfg(); const serverCfg = await getServerCfg();
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
throw new Error("Failed to find user");
}
if ( if (
serverCfg.enabledLoginMethods && serverCfg.enabledLoginMethods &&
!serverCfg.enabledLoginMethods.includes(LoginMethod.EMAIL) && !serverCfg.enabledLoginMethods.includes(LoginMethod.EMAIL) &&
!providerAuthToken !providerAuthToken
) { ) {
throw new BadRequestError({ // bypass server configuration when user is an organization admin - this is to prevent lockout
message: "Login with email is disabled by administrator." const userOrgs = await orgDAL.findAllOrgsByUserId(userEnc.userId);
}); if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
} throw new BadRequestError({
message: "Login with email is disabled by administrator."
if (!userEnc || (userEnc && !userEnc.isAccepted)) { });
throw new Error("Failed to find user"); }
} }
if (!userEnc.authMethods?.includes(AuthMethod.EMAIL)) { if (!userEnc.authMethods?.includes(AuthMethod.EMAIL)) {
@@ -573,28 +578,40 @@ export const authLoginServiceFactory = ({
switch (authMethod) { switch (authMethod) {
case AuthMethod.GITHUB: { case AuthMethod.GITHUB: {
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITHUB)) { if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITHUB)) {
throw new BadRequestError({ // bypass server configuration when user is an organization admin - this is to prevent lockout
message: "Login with Github is disabled by administrator.", const userOrgs = await orgDAL.findAllOrgsByUserId(user.id);
name: "Oauth 2 login" if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
}); throw new BadRequestError({
message: "Login with Github is disabled by administrator.",
name: "Oauth 2 login"
});
}
} }
break; break;
} }
case AuthMethod.GOOGLE: { case AuthMethod.GOOGLE: {
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GOOGLE)) { if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GOOGLE)) {
throw new BadRequestError({ // bypass server configuration when user is an organization admin - this is to prevent lockout
message: "Login with Google is disabled by administrator.", const userOrgs = await orgDAL.findAllOrgsByUserId(user.id);
name: "Oauth 2 login" if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
}); throw new BadRequestError({
message: "Login with Google is disabled by administrator.",
name: "Oauth 2 login"
});
}
} }
break; break;
} }
case AuthMethod.GITLAB: { case AuthMethod.GITLAB: {
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITLAB)) { if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITLAB)) {
throw new BadRequestError({ // bypass server configuration when user is an organization admin - this is to prevent lockout
message: "Login with Gitlab is disabled by administrator.", const userOrgs = await orgDAL.findAllOrgsByUserId(user.id);
name: "Oauth 2 login" if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
}); throw new BadRequestError({
message: "Login with Gitlab is disabled by administrator.",
name: "Oauth 2 login"
});
}
} }
break; break;
} }
+5 -2
View File
@@ -96,7 +96,9 @@ export const orgDALFactory = (db: TDbClient) => {
}; };
// special query // special query
const findAllOrgsByUserId = async (userId: string): Promise<(TOrganizations & { orgAuthMethod: string })[]> => { const findAllOrgsByUserId = async (
userId: string
): Promise<(TOrganizations & { orgAuthMethod: string; userRole: string })[]> => {
try { try {
const org = (await db const org = (await db
.replicaNode()(TableName.OrgMembership) .replicaNode()(TableName.OrgMembership)
@@ -117,6 +119,7 @@ export const orgDALFactory = (db: TDbClient) => {
); );
}) })
.select(selectAllTableCols(TableName.Organization)) .select(selectAllTableCols(TableName.Organization))
.select(db.ref("role").withSchema(TableName.OrgMembership).as("userRole"))
.select( .select(
db.raw(` db.raw(`
CASE CASE
@@ -125,7 +128,7 @@ export const orgDALFactory = (db: TDbClient) => {
ELSE '' ELSE ''
END as "orgAuthMethod" END as "orgAuthMethod"
`) `)
)) as (TOrganizations & { orgAuthMethod: string })[]; )) as (TOrganizations & { orgAuthMethod: string; userRole: string })[];
return org; return org;
} catch (error) { } catch (error) {
+2 -1
View File
@@ -16,5 +16,6 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
allowSecretSharingOutsideOrganization: true, allowSecretSharingOutsideOrganization: true,
shouldUseNewPrivilegeSystem: true, shouldUseNewPrivilegeSystem: true,
privilegeUpgradeInitiatedByUsername: true, privilegeUpgradeInitiatedByUsername: true,
privilegeUpgradeInitiatedAt: true privilegeUpgradeInitiatedAt: true,
bypassOrgAuthEnabled: true
}); });
+4 -2
View File
@@ -349,7 +349,8 @@ export const orgServiceFactory = ({
defaultMembershipRoleSlug, defaultMembershipRoleSlug,
enforceMfa, enforceMfa,
selectedMfaMethod, selectedMfaMethod,
allowSecretSharingOutsideOrganization allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
} }
}: TUpdateOrgDTO) => { }: TUpdateOrgDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -429,7 +430,8 @@ export const orgServiceFactory = ({
defaultMembershipRole, defaultMembershipRole,
enforceMfa, enforceMfa,
selectedMfaMethod, selectedMfaMethod,
allowSecretSharingOutsideOrganization allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
}); });
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
return org; return org;
+1
View File
@@ -73,6 +73,7 @@ export type TUpdateOrgDTO = {
enforceMfa: boolean; enforceMfa: boolean;
selectedMfaMethod: MfaMethod; selectedMfaMethod: MfaMethod;
allowSecretSharingOutsideOrganization: boolean; allowSecretSharingOutsideOrganization: boolean;
bypassOrgAuthEnabled: boolean;
}>; }>;
} & TOrgPermission; } & TOrgPermission;
@@ -65,7 +65,9 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
We recommend ensuring that your account is provisioned using the application in Auth0 We recommend ensuring that your account is provisioned using the application in Auth0
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
@@ -72,6 +72,10 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Auth0 user with Infisical; To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Auth0 user with Infisical;
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
@@ -106,6 +106,9 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Azure We recommend ensuring that your account is provisioned the application in Azure
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
@@ -66,6 +66,9 @@ Prerequisites:
<Warning> <Warning>
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues. We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
@@ -81,6 +81,9 @@ description: "Learn how to configure Google SAML for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Google We recommend ensuring that your account is provisioned the application in Google
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
@@ -86,6 +86,9 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
We recommend ensuring that your account is provisioned the application in JumpCloud We recommend ensuring that your account is provisioned the application in JumpCloud
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
@@ -92,7 +92,9 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
We recommend ensuring that your account is provisioned using the application in Keycloak We recommend ensuring that your account is provisioned using the application in Keycloak
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
@@ -127,6 +127,9 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Keycloak We recommend ensuring that your account is provisioned the application in Keycloak
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
+3
View File
@@ -94,6 +94,9 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Okta We recommend ensuring that your account is provisioned the application in Okta
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
+1 -1
View File
@@ -1,6 +1,6 @@
import { ProjectMembershipRole, TOrgRole } from "@app/hooks/api/roles/types"; import { ProjectMembershipRole, TOrgRole } from "@app/hooks/api/roles/types";
enum OrgMembershipRole { export enum OrgMembershipRole {
Admin = "admin", Admin = "admin",
Member = "member", Member = "member",
NoAccess = "no-access" NoAccess = "no-access"
@@ -110,7 +110,8 @@ export const useUpdateOrg = () => {
defaultMembershipRoleSlug, defaultMembershipRoleSlug,
enforceMfa, enforceMfa,
selectedMfaMethod, selectedMfaMethod,
allowSecretSharingOutsideOrganization allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
}) => { }) => {
return apiRequest.patch(`/api/v1/organization/${orgId}`, { return apiRequest.patch(`/api/v1/organization/${orgId}`, {
name, name,
@@ -120,7 +121,8 @@ export const useUpdateOrg = () => {
defaultMembershipRoleSlug, defaultMembershipRoleSlug,
enforceMfa, enforceMfa,
selectedMfaMethod, selectedMfaMethod,
allowSecretSharingOutsideOrganization allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
}); });
}, },
onSuccess: () => { onSuccess: () => {
@@ -9,6 +9,7 @@ export type Organization = {
createAt: string; createAt: string;
updatedAt: string; updatedAt: string;
authEnforced: boolean; authEnforced: boolean;
bypassOrgAuthEnabled: boolean;
orgAuthMethod: string; orgAuthMethod: string;
scimEnabled: boolean; scimEnabled: boolean;
slug: string; slug: string;
@@ -17,6 +18,7 @@ export type Organization = {
selectedMfaMethod?: MfaMethod; selectedMfaMethod?: MfaMethod;
shouldUseNewPrivilegeSystem: boolean; shouldUseNewPrivilegeSystem: boolean;
allowSecretSharingOutsideOrganization?: boolean; allowSecretSharingOutsideOrganization?: boolean;
userRole: string;
}; };
export type UpdateOrgDTO = { export type UpdateOrgDTO = {
@@ -29,6 +31,7 @@ export type UpdateOrgDTO = {
enforceMfa?: boolean; enforceMfa?: boolean;
selectedMfaMethod?: MfaMethod; selectedMfaMethod?: MfaMethod;
allowSecretSharingOutsideOrganization?: boolean; allowSecretSharingOutsideOrganization?: boolean;
bypassOrgAuthEnabled?: boolean;
}; };
export type BillingDetails = { export type BillingDetails = {
@@ -0,0 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
import { LoginPage } from "../LoginPage/LoginPage";
export const Route = createFileRoute("/_restrict-login-signup/login/admin")({
component: () => <LoginPage isAdmin />
});
@@ -33,14 +33,21 @@ export const useNavigateToSelectOrganization = () => {
const { config } = useServerConfig(); const { config } = useServerConfig();
const navigate = useNavigate(); const navigate = useNavigate();
const navigateToSelectOrganization = async (cliCallbackPort?: string) => { const navigateToSelectOrganization = async (
cliCallbackPort?: string,
isFromAdminLogin?: boolean
) => {
if (!config.defaultAuthOrgId) { if (!config.defaultAuthOrgId) {
queryClient.invalidateQueries({ queryKey: userKeys.getUser }); queryClient.invalidateQueries({ queryKey: userKeys.getUser });
} }
navigate({ navigate({
to: "/login/select-organization", to: "/login/select-organization",
search: { callback_port: cliCallbackPort, org_id: config.defaultAuthOrgId } search: {
callback_port: cliCallbackPort,
org_id: config.defaultAuthOrgId,
is_admin_login: isFromAdminLogin
}
}); });
}; };
@@ -8,7 +8,7 @@ import { isLoggedIn } from "@app/hooks/api/reactQuery";
import { InitialStep, SSOStep } from "./components"; import { InitialStep, SSOStep } from "./components";
import { useNavigateToSelectOrganization } from "./Login.utils"; import { useNavigateToSelectOrganization } from "./Login.utils";
export const LoginPage = () => { export const LoginPage = ({ isAdmin }: { isAdmin?: boolean }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@@ -44,6 +44,7 @@ export const LoginPage = () => {
case 0: case 0:
return ( return (
<InitialStep <InitialStep
isAdmin={isAdmin}
setStep={setStep} setStep={setStep}
email={email} email={email}
setEmail={setEmail} setEmail={setEmail}
@@ -26,9 +26,17 @@ type Props = {
setEmail: (email: string) => void; setEmail: (email: string) => void;
password: string; password: string;
setPassword: (email: string) => void; setPassword: (email: string) => void;
isAdmin?: boolean;
}; };
export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: Props) => { export const InitialStep = ({
setStep,
email,
setEmail,
password,
setPassword,
isAdmin
}: Props) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -62,7 +70,9 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
}; };
useEffect(() => { useEffect(() => {
if (serverDetails?.samlDefaultOrgSlug) redirectToSaml(serverDetails.samlDefaultOrgSlug); if (serverDetails?.samlDefaultOrgSlug && !isAdmin) {
redirectToSaml(serverDetails.samlDefaultOrgSlug);
}
}, [serverDetails?.samlDefaultOrgSlug]); }, [serverDetails?.samlDefaultOrgSlug]);
const handleSaml = () => { const handleSaml = () => {
@@ -82,7 +92,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
}; };
const shouldDisplayLoginMethod = (method: LoginMethod) => const shouldDisplayLoginMethod = (method: LoginMethod) =>
!config.enabledLoginMethods || config.enabledLoginMethods.includes(method); isAdmin || !config.enabledLoginMethods || config.enabledLoginMethods.includes(method);
const handleLogin = async (e: FormEvent<HTMLFormElement>) => { const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@@ -120,7 +130,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
if (isLoginSuccessful && isLoginSuccessful.success) { if (isLoginSuccessful && isLoginSuccessful.success) {
// case: login was successful // case: login was successful
navigateToSelectOrganization(); navigateToSelectOrganization(undefined, isAdmin);
createNotification({ createNotification({
text: "Successfully logged in", text: "Successfully logged in",
type: "success" type: "success"
@@ -160,7 +170,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
setIsLoading(false); setIsLoading(false);
}; };
if (config.defaultAuthOrgAuthEnforced && config.defaultAuthOrgAuthMethod) { if (config.defaultAuthOrgAuthEnforced && config.defaultAuthOrgAuthMethod && !isAdmin) {
return ( return (
<form <form
onSubmit={handleLogin} onSubmit={handleLogin}
@@ -14,6 +14,7 @@ import { IsCliLoginSuccessful } from "@app/components/utilities/attemptCliLogin"
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button, Spinner } from "@app/components/v2"; import { Button, Spinner } from "@app/components/v2";
import { SessionStorageKeys } from "@app/const"; import { SessionStorageKeys } from "@app/const";
import { OrgMembershipRole } from "@app/helpers/roles";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { import {
useGetOrganizations, useGetOrganizations,
@@ -53,6 +54,7 @@ export const SelectOrganizationPage = () => {
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
const orgId = queryParams.get("org_id"); const orgId = queryParams.get("org_id");
const callbackPort = queryParams.get("callback_port"); const callbackPort = queryParams.get("callback_port");
const isAdminLogin = queryParams.get("is_admin_login") === "true";
const defaultSelectedOrg = organizations.data?.find((org) => org.id === orgId); const defaultSelectedOrg = organizations.data?.find((org) => org.id === orgId);
const logout = useLogoutUser(true); const logout = useLogoutUser(true);
@@ -68,7 +70,12 @@ export const SelectOrganizationPage = () => {
const handleSelectOrganization = useCallback( const handleSelectOrganization = useCallback(
async (organization: Organization) => { async (organization: Organization) => {
if (organization.authEnforced) { const canBypassOrgAuth =
organization.bypassOrgAuthEnabled &&
organization.userRole === OrgMembershipRole.Admin &&
isAdminLogin;
if (organization.authEnforced && !canBypassOrgAuth) {
// org has an org-level auth method enabled (e.g. SAML) // org has an org-level auth method enabled (e.g. SAML)
// -> logout + redirect to SAML SSO // -> logout + redirect to SAML SSO
await logout.mutateAsync(); await logout.mutateAsync();
@@ -6,13 +6,16 @@ import { SelectOrganizationPage } from "./SelectOrgPage";
export const SelectOrganizationPageQueryParams = z.object({ export const SelectOrganizationPageQueryParams = z.object({
org_id: z.string().optional().catch(""), org_id: z.string().optional().catch(""),
callback_port: z.coerce.number().optional().catch(undefined) callback_port: z.coerce.number().optional().catch(undefined),
is_admin_login: z.boolean().optional().catch(false)
}); });
export const Route = createFileRoute("/_restrict-login-signup/login/select-organization")({ export const Route = createFileRoute("/_restrict-login-signup/login/select-organization")({
component: SelectOrganizationPage, component: SelectOrganizationPage,
validateSearch: zodValidator(SelectOrganizationPageQueryParams), validateSearch: zodValidator(SelectOrganizationPageQueryParams),
search: { search: {
middlewares: [stripSearchParams({ org_id: "", callback_port: undefined })] middlewares: [
stripSearchParams({ org_id: "", callback_port: undefined, is_admin_login: false })
]
} }
}); });
@@ -1,7 +1,10 @@
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Switch } from "@app/components/v2"; import { Switch, Tooltip } from "@app/components/v2";
import { import {
OrgPermissionActions, OrgPermissionActions,
OrgPermissionSubjects, OrgPermissionSubjects,
@@ -52,6 +55,28 @@ export const OrgGeneralAuthSection = () => {
} }
}; };
const handleEnableBypassOrgAuthToggle = async (value: boolean) => {
try {
if (!currentOrg?.id) return;
if (!subscription?.samlSSO) {
handlePopUpOpen("upgradePlan");
return;
}
await mutateAsync({
orgId: currentOrg?.id,
bypassOrgAuthEnabled: value
});
createNotification({
text: `Successfully ${value ? "enabled" : "disabled"} admin bypassing of org-level auth`,
type: "success"
});
} catch (err) {
console.error(err);
}
};
return ( return (
<> <>
{/* <div className="py-4"> {/* <div className="py-4">
@@ -72,7 +97,9 @@ export const OrgGeneralAuthSection = () => {
</div> */} </div> */}
<div className="py-4"> <div className="py-4">
<div className="mb-2 flex justify-between"> <div className="mb-2 flex justify-between">
<h3 className="text-md text-mineshaft-100">Enforce SAML SSO</h3> <div className="flex items-center gap-1">
<span className="text-md text-mineshaft-100">Enforce SAML SSO</span>
</div>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}> <OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
{(isAllowed) => ( {(isAllowed) => (
<Switch <Switch
@@ -85,9 +112,62 @@ export const OrgGeneralAuthSection = () => {
</OrgPermissionCan> </OrgPermissionCan>
</div> </div>
<p className="text-sm text-mineshaft-300"> <p className="text-sm text-mineshaft-300">
Enforce members to authenticate via SAML to access this organization Enforce users to authenticate via SAML to access this organization
</p> </p>
</div> </div>
{currentOrg?.authEnforced && (
<div className="py-4">
<div className="mb-2 flex justify-between">
<div className="flex items-center gap-1">
<span className="text-md text-mineshaft-100">Enable Admin SSO Bypass</span>
<Tooltip
className="max-w-lg"
content={
<div>
<span>
When this is enabled, we strongly recommend enforcing MFA at the organization
level.
</span>
<p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "}
<a
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:text-mineshaft-300"
href={`${window.location.origin}/login/admin`}
>
{window.location.origin}/login/admin
</a>
</p>
</div>
}
>
<FontAwesomeIcon
icon={faInfoCircle}
size="sm"
className="mt-0.5 inline-block text-mineshaft-400"
/>
</Tooltip>
</div>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
{(isAllowed) => (
<Switch
id="allow-admin-bypass"
isChecked={currentOrg?.bypassOrgAuthEnabled ?? false}
onCheckedChange={(value) => handleEnableBypassOrgAuthToggle(value)}
isDisabled={!isAllowed}
/>
)}
</OrgPermissionCan>
</div>
<p className="text-sm text-mineshaft-300">
<span>
Allow organization admins to bypass SAML enforcement when SSO is unavailable,
misconfigured, or inaccessible.
</span>
</p>
</div>
)}
<UpgradePlanModal <UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen} isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
@@ -82,6 +82,28 @@ export const OrgOIDCSection = (): JSX.Element => {
} }
}; };
const handleEnableBypassOrgAuthToggle = async (value: boolean) => {
try {
if (!currentOrg?.id) return;
if (!subscription?.oidcSSO) {
handlePopUpOpen("upgradePlan");
return;
}
await updateOrg({
orgId: currentOrg?.id,
bypassOrgAuthEnabled: value
});
createNotification({
text: `Successfully ${value ? "enabled" : "disabled"} admin bypassing of org-level auth`,
type: "success"
});
} catch (err) {
console.error(err);
}
};
const handleOIDCGroupManagement = async (value: boolean) => { const handleOIDCGroupManagement = async (value: boolean) => {
try { try {
if (!currentOrg?.id) return; if (!currentOrg?.id) return;
@@ -158,7 +180,9 @@ export const OrgOIDCSection = (): JSX.Element => {
)} )}
<div className="py-4"> <div className="py-4">
<div className="mb-2 flex justify-between"> <div className="mb-2 flex justify-between">
<h3 className="text-md text-mineshaft-100">Enforce OIDC SSO</h3> <div className="flex items-center gap-1">
<span className="text-md text-mineshaft-100">Enforce OIDC SSO</span>
</div>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}> <OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
{(isAllowed) => ( {(isAllowed) => (
<Switch <Switch
@@ -171,9 +195,62 @@ export const OrgOIDCSection = (): JSX.Element => {
</OrgPermissionCan> </OrgPermissionCan>
</div> </div>
<p className="text-sm text-mineshaft-300"> <p className="text-sm text-mineshaft-300">
Enforce members to authenticate via OIDC to access this organization <span>Enforce users to authenticate via OIDC to access this organization.</span>
</p> </p>
</div> </div>
{currentOrg?.authEnforced && (
<div className="py-4">
<div className="mb-2 flex justify-between">
<div className="flex items-center gap-1">
<span className="text-md text-mineshaft-100">Enable Admin SSO Bypass</span>
<Tooltip
className="max-w-lg"
content={
<div>
<span>
When this is enabled, we strongly recommend enforcing MFA at the organization
level.
</span>
<p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "}
<a
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:text-mineshaft-300"
href={`${window.location.origin}/login/admin`}
>
{window.location.origin}/login/admin
</a>
</p>
</div>
}
>
<FontAwesomeIcon
icon={faInfoCircle}
size="sm"
className="mt-0.5 inline-block text-mineshaft-400"
/>
</Tooltip>
</div>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
{(isAllowed) => (
<Switch
id="allow-admin-bypass"
isChecked={currentOrg?.bypassOrgAuthEnabled ?? false}
onCheckedChange={(value) => handleEnableBypassOrgAuthToggle(value)}
isDisabled={!isAllowed}
/>
)}
</OrgPermissionCan>
</div>
<p className="text-sm text-mineshaft-300">
<span>
Allow organization admins to bypass OIDC enforcement when SSO is unavailable,
misconfigured, or inaccessible.
</span>
</p>
</div>
)}
<div className="py-4"> <div className="py-4">
<div className="mb-2 flex justify-between"> <div className="mb-2 flex justify-between">
<div className="text-md flex items-center text-mineshaft-100"> <div className="text-md flex items-center text-mineshaft-100">
+27
View File
@@ -33,6 +33,7 @@ import { Route as authSignUpSsoPageRouteImport } from './pages/auth/SignUpSsoPag
import { Route as authLoginSsoPageRouteImport } from './pages/auth/LoginSsoPage/route' import { Route as authLoginSsoPageRouteImport } from './pages/auth/LoginSsoPage/route'
import { Route as authSelectOrgPageRouteImport } from './pages/auth/SelectOrgPage/route' import { Route as authSelectOrgPageRouteImport } from './pages/auth/SelectOrgPage/route'
import { Route as authLoginLdapPageRouteImport } from './pages/auth/LoginLdapPage/route' import { Route as authLoginLdapPageRouteImport } from './pages/auth/LoginLdapPage/route'
import { Route as authAdminLoginPageRouteImport } from './pages/auth/AdminLoginPage/route'
import { Route as adminSignUpPageRouteImport } from './pages/admin/SignUpPage/route' import { Route as adminSignUpPageRouteImport } from './pages/admin/SignUpPage/route'
import { Route as organizationNoOrgPageRouteImport } from './pages/organization/NoOrgPage/route' import { Route as organizationNoOrgPageRouteImport } from './pages/organization/NoOrgPage/route'
import { Route as authSignUpPageRouteImport } from './pages/auth/SignUpPage/route' import { Route as authSignUpPageRouteImport } from './pages/auth/SignUpPage/route'
@@ -393,6 +394,12 @@ const authLoginLdapPageRouteRoute = authLoginLdapPageRouteImport.update({
getParentRoute: () => RestrictLoginSignupLoginRoute, getParentRoute: () => RestrictLoginSignupLoginRoute,
} as any) } as any)
const authAdminLoginPageRouteRoute = authAdminLoginPageRouteImport.update({
id: '/admin',
path: '/admin',
getParentRoute: () => RestrictLoginSignupLoginRoute,
} as any)
const adminSignUpPageRouteRoute = adminSignUpPageRouteImport.update({ const adminSignUpPageRouteRoute = adminSignUpPageRouteImport.update({
id: '/admin/signup', id: '/admin/signup',
path: '/admin/signup', path: '/admin/signup',
@@ -1775,6 +1782,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof adminSignUpPageRouteImport preLoaderRoute: typeof adminSignUpPageRouteImport
parentRoute: typeof middlewaresRestrictLoginSignupImport parentRoute: typeof middlewaresRestrictLoginSignupImport
} }
'/_restrict-login-signup/login/admin': {
id: '/_restrict-login-signup/login/admin'
path: '/admin'
fullPath: '/login/admin'
preLoaderRoute: typeof authAdminLoginPageRouteImport
parentRoute: typeof RestrictLoginSignupLoginImport
}
'/_restrict-login-signup/login/ldap': { '/_restrict-login-signup/login/ldap': {
id: '/_restrict-login-signup/login/ldap' id: '/_restrict-login-signup/login/ldap'
path: '/ldap' path: '/ldap'
@@ -3655,6 +3669,7 @@ const middlewaresAuthenticateRouteWithChildren =
interface RestrictLoginSignupLoginRouteChildren { interface RestrictLoginSignupLoginRouteChildren {
authLoginPageRouteRoute: typeof authLoginPageRouteRoute authLoginPageRouteRoute: typeof authLoginPageRouteRoute
authAdminLoginPageRouteRoute: typeof authAdminLoginPageRouteRoute
authLoginLdapPageRouteRoute: typeof authLoginLdapPageRouteRoute authLoginLdapPageRouteRoute: typeof authLoginLdapPageRouteRoute
authSelectOrgPageRouteRoute: typeof authSelectOrgPageRouteRoute authSelectOrgPageRouteRoute: typeof authSelectOrgPageRouteRoute
authLoginSsoPageRouteRoute: typeof authLoginSsoPageRouteRoute authLoginSsoPageRouteRoute: typeof authLoginSsoPageRouteRoute
@@ -3665,6 +3680,7 @@ interface RestrictLoginSignupLoginRouteChildren {
const RestrictLoginSignupLoginRouteChildren: RestrictLoginSignupLoginRouteChildren = const RestrictLoginSignupLoginRouteChildren: RestrictLoginSignupLoginRouteChildren =
{ {
authLoginPageRouteRoute: authLoginPageRouteRoute, authLoginPageRouteRoute: authLoginPageRouteRoute,
authAdminLoginPageRouteRoute: authAdminLoginPageRouteRoute,
authLoginLdapPageRouteRoute: authLoginLdapPageRouteRoute, authLoginLdapPageRouteRoute: authLoginLdapPageRouteRoute,
authSelectOrgPageRouteRoute: authSelectOrgPageRouteRoute, authSelectOrgPageRouteRoute: authSelectOrgPageRouteRoute,
authLoginSsoPageRouteRoute: authLoginSsoPageRouteRoute, authLoginSsoPageRouteRoute: authLoginSsoPageRouteRoute,
@@ -3739,6 +3755,7 @@ export interface FileRoutesByFullPath {
'/signup/': typeof authSignUpPageRouteRoute '/signup/': typeof authSignUpPageRouteRoute
'/organization/none': typeof organizationNoOrgPageRouteRoute '/organization/none': typeof organizationNoOrgPageRouteRoute
'/admin/signup': typeof adminSignUpPageRouteRoute '/admin/signup': typeof adminSignUpPageRouteRoute
'/login/admin': typeof authAdminLoginPageRouteRoute
'/login/ldap': typeof authLoginLdapPageRouteRoute '/login/ldap': typeof authLoginLdapPageRouteRoute
'/login/select-organization': typeof authSelectOrgPageRouteRoute '/login/select-organization': typeof authSelectOrgPageRouteRoute
'/login/sso': typeof authLoginSsoPageRouteRoute '/login/sso': typeof authLoginSsoPageRouteRoute
@@ -3918,6 +3935,7 @@ export interface FileRoutesByTo {
'/signup': typeof authSignUpPageRouteRoute '/signup': typeof authSignUpPageRouteRoute
'/organization/none': typeof organizationNoOrgPageRouteRoute '/organization/none': typeof organizationNoOrgPageRouteRoute
'/admin/signup': typeof adminSignUpPageRouteRoute '/admin/signup': typeof adminSignUpPageRouteRoute
'/login/admin': typeof authAdminLoginPageRouteRoute
'/login/ldap': typeof authLoginLdapPageRouteRoute '/login/ldap': typeof authLoginLdapPageRouteRoute
'/login/select-organization': typeof authSelectOrgPageRouteRoute '/login/select-organization': typeof authSelectOrgPageRouteRoute
'/login/sso': typeof authLoginSsoPageRouteRoute '/login/sso': typeof authLoginSsoPageRouteRoute
@@ -4096,6 +4114,7 @@ export interface FileRoutesById {
'/_restrict-login-signup/signup/': typeof authSignUpPageRouteRoute '/_restrict-login-signup/signup/': typeof authSignUpPageRouteRoute
'/_authenticate/organization/none': typeof organizationNoOrgPageRouteRoute '/_authenticate/organization/none': typeof organizationNoOrgPageRouteRoute
'/_restrict-login-signup/admin/signup': typeof adminSignUpPageRouteRoute '/_restrict-login-signup/admin/signup': typeof adminSignUpPageRouteRoute
'/_restrict-login-signup/login/admin': typeof authAdminLoginPageRouteRoute
'/_restrict-login-signup/login/ldap': typeof authLoginLdapPageRouteRoute '/_restrict-login-signup/login/ldap': typeof authLoginLdapPageRouteRoute
'/_restrict-login-signup/login/select-organization': typeof authSelectOrgPageRouteRoute '/_restrict-login-signup/login/select-organization': typeof authSelectOrgPageRouteRoute
'/_restrict-login-signup/login/sso': typeof authLoginSsoPageRouteRoute '/_restrict-login-signup/login/sso': typeof authLoginSsoPageRouteRoute
@@ -4286,6 +4305,7 @@ export interface FileRouteTypes {
| '/signup/' | '/signup/'
| '/organization/none' | '/organization/none'
| '/admin/signup' | '/admin/signup'
| '/login/admin'
| '/login/ldap' | '/login/ldap'
| '/login/select-organization' | '/login/select-organization'
| '/login/sso' | '/login/sso'
@@ -4464,6 +4484,7 @@ export interface FileRouteTypes {
| '/signup' | '/signup'
| '/organization/none' | '/organization/none'
| '/admin/signup' | '/admin/signup'
| '/login/admin'
| '/login/ldap' | '/login/ldap'
| '/login/select-organization' | '/login/select-organization'
| '/login/sso' | '/login/sso'
@@ -4640,6 +4661,7 @@ export interface FileRouteTypes {
| '/_restrict-login-signup/signup/' | '/_restrict-login-signup/signup/'
| '/_authenticate/organization/none' | '/_authenticate/organization/none'
| '/_restrict-login-signup/admin/signup' | '/_restrict-login-signup/admin/signup'
| '/_restrict-login-signup/login/admin'
| '/_restrict-login-signup/login/ldap' | '/_restrict-login-signup/login/ldap'
| '/_restrict-login-signup/login/select-organization' | '/_restrict-login-signup/login/select-organization'
| '/_restrict-login-signup/login/sso' | '/_restrict-login-signup/login/sso'
@@ -4928,6 +4950,7 @@ export const routeTree = rootRoute
"parent": "/_restrict-login-signup", "parent": "/_restrict-login-signup",
"children": [ "children": [
"/_restrict-login-signup/login/", "/_restrict-login-signup/login/",
"/_restrict-login-signup/login/admin",
"/_restrict-login-signup/login/ldap", "/_restrict-login-signup/login/ldap",
"/_restrict-login-signup/login/select-organization", "/_restrict-login-signup/login/select-organization",
"/_restrict-login-signup/login/sso", "/_restrict-login-signup/login/sso",
@@ -4959,6 +4982,10 @@ export const routeTree = rootRoute
"filePath": "admin/SignUpPage/route.tsx", "filePath": "admin/SignUpPage/route.tsx",
"parent": "/_restrict-login-signup" "parent": "/_restrict-login-signup"
}, },
"/_restrict-login-signup/login/admin": {
"filePath": "auth/AdminLoginPage/route.tsx",
"parent": "/_restrict-login-signup/login"
},
"/_restrict-login-signup/login/ldap": { "/_restrict-login-signup/login/ldap": {
"filePath": "auth/LoginLdapPage/route.tsx", "filePath": "auth/LoginLdapPage/route.tsx",
"parent": "/_restrict-login-signup/login" "parent": "/_restrict-login-signup/login"
+1
View File
@@ -328,6 +328,7 @@ export const routes = rootRoute("root.tsx", [
route("/admin/signup", "admin/SignUpPage/route.tsx"), route("/admin/signup", "admin/SignUpPage/route.tsx"),
route("/login", [ route("/login", [
index("auth/LoginPage/route.tsx"), index("auth/LoginPage/route.tsx"),
route("/admin", "auth/AdminLoginPage/route.tsx"),
route("/select-organization", "auth/SelectOrgPage/route.tsx"), route("/select-organization", "auth/SelectOrgPage/route.tsx"),
route("/sso", "auth/LoginSsoPage/route.tsx"), route("/sso", "auth/LoginSsoPage/route.tsx"),
route("/ldap", "auth/LoginLdapPage/route.tsx"), route("/ldap", "auth/LoginLdapPage/route.tsx"),