From d0db5c00e8f731eb072dc453bb4896b869af9033 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 16 Apr 2025 01:35:39 +0800 Subject: [PATCH 1/9] misc: allow org admins to bypass sso enforcement --- .../src/ee/services/permission/permission-dal.ts | 9 +++++++++ .../src/ee/services/permission/permission-fns.ts | 9 +++++++-- .../ee/services/permission/permission-service.ts | 4 ++-- .../src/server/routes/v1/organization-router.ts | 3 ++- backend/src/services/org/org-dal.ts | 7 +++++-- frontend/src/helpers/roles.ts | 2 +- frontend/src/hooks/api/organization/types.ts | 1 + .../components/InitialStep/InitialStep.tsx | 15 ++++++++++++++- .../pages/auth/SelectOrgPage/SelectOrgPage.tsx | 3 ++- .../OrgAuthTab/OrgGeneralAuthSection.tsx | 2 +- .../components/OrgAuthTab/OrgOIDCSection.tsx | 2 +- 11 files changed, 45 insertions(+), 12 deletions(-) diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index 9b23d6113..b76403400 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { TDbClient } from "@app/db"; import { IdentityProjectMembershipRoleSchema, + OrgMembershipRole, OrgMembershipsSchema, TableName, TProjectRoles, @@ -571,6 +572,11 @@ export const permissionDALFactory = (db: TDbClient) => { }) .join(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId])) .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) => { void queryBuilder .on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`) @@ -670,6 +676,7 @@ export const permissionDALFactory = (db: TDbClient) => { db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), + db.ref("role").withSchema(TableName.OrgMembership).as("orgRole"), db.ref("orgId").withSchema(TableName.Project), db.ref("type").withSchema(TableName.Project).as("projectType"), db.ref("id").withSchema(TableName.Project).as("projectId"), @@ -683,6 +690,7 @@ export const permissionDALFactory = (db: TDbClient) => { orgId, username, orgAuthEnforced, + orgRole, membershipId, groupMembershipId, membershipCreatedAt, @@ -694,6 +702,7 @@ export const permissionDALFactory = (db: TDbClient) => { }) => ({ orgId, orgAuthEnforced, + orgRole: orgRole as OrgMembershipRole, userId, projectId, username, diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts index 88bace1f0..410fc5819 100644 --- a/backend/src/ee/services/permission/permission-fns.ts +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, PureAbility, subject } from "@casl/ability"; 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 { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type"; @@ -118,12 +118,17 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) { ].includes(actorAuthMethod); } -function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrganizations["authEnforced"]) { +function validateOrgSSO( + actorAuthMethod: ActorAuthMethod, + isOrgSsoEnforced: TOrganizations["authEnforced"], + orgRole: OrgMembershipRole +) { if (actorAuthMethod === undefined) { throw new UnauthorizedError({ name: "No auth method defined" }); } if ( + orgRole !== OrgMembershipRole.Admin && isOrgSsoEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod) && diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 299f509d7..0a63931aa 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -139,7 +139,7 @@ export const permissionServiceFactory = ({ throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); } - validateOrgSSO(authMethod, membership.orgAuthEnforced); + validateOrgSSO(authMethod, membership.orgAuthEnforced, membership.role as OrgMembershipRole); const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat( membership?.groups?.map(({ role, customRolePermission }) => ({ @@ -226,7 +226,7 @@ export const permissionServiceFactory = ({ throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); } - validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced); + validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced, userProjectPermission.orgRole); if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) { throw new BadRequestError({ diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 93376d261..c6391d4f7 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -31,7 +31,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { 200: z.object({ organizations: sanitizedOrganizationSchema .extend({ - orgAuthMethod: z.string() + orgAuthMethod: z.string(), + userRole: z.string() }) .array() }) diff --git a/backend/src/services/org/org-dal.ts b/backend/src/services/org/org-dal.ts index 85d348854..02bf58321 100644 --- a/backend/src/services/org/org-dal.ts +++ b/backend/src/services/org/org-dal.ts @@ -96,7 +96,9 @@ export const orgDALFactory = (db: TDbClient) => { }; // special query - const findAllOrgsByUserId = async (userId: string): Promise<(TOrganizations & { orgAuthMethod: string })[]> => { + const findAllOrgsByUserId = async ( + userId: string + ): Promise<(TOrganizations & { orgAuthMethod: string; userRole: string })[]> => { try { const org = (await db .replicaNode()(TableName.OrgMembership) @@ -117,6 +119,7 @@ export const orgDALFactory = (db: TDbClient) => { ); }) .select(selectAllTableCols(TableName.Organization)) + .select(db.ref("role").withSchema(TableName.OrgMembership).as("userRole")) .select( db.raw(` CASE @@ -125,7 +128,7 @@ export const orgDALFactory = (db: TDbClient) => { ELSE '' END as "orgAuthMethod" `) - )) as (TOrganizations & { orgAuthMethod: string })[]; + )) as (TOrganizations & { orgAuthMethod: string; userRole: string })[]; return org; } catch (error) { diff --git a/frontend/src/helpers/roles.ts b/frontend/src/helpers/roles.ts index 4e26e1b15..dcf80d452 100644 --- a/frontend/src/helpers/roles.ts +++ b/frontend/src/helpers/roles.ts @@ -1,6 +1,6 @@ import { ProjectMembershipRole, TOrgRole } from "@app/hooks/api/roles/types"; -enum OrgMembershipRole { +export enum OrgMembershipRole { Admin = "admin", Member = "member", NoAccess = "no-access" diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index c563d8c43..6eeebd819 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -17,6 +17,7 @@ export type Organization = { selectedMfaMethod?: MfaMethod; shouldUseNewPrivilegeSystem: boolean; allowSecretSharingOutsideOrganization?: boolean; + userRole: string; }; export type UpdateOrgDTO = { diff --git a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx index 16de4c7ec..f9ba1bf98 100644 --- a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx +++ b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx @@ -34,6 +34,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const [loginError, setLoginError] = useState(false); + const [isOtherLoginMethodsSelected, setIsOtherLoginMethodsSelected] = useState(false); const { config } = useServerConfig(); const queryParams = new URLSearchParams(window.location.search); const [captchaToken, setCaptchaToken] = useState(""); @@ -160,7 +161,11 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: setIsLoading(false); }; - if (config.defaultAuthOrgAuthEnforced && config.defaultAuthOrgAuthMethod) { + if ( + config.defaultAuthOrgAuthEnforced && + config.defaultAuthOrgAuthMethod && + !isOtherLoginMethodsSelected + ) { return (
)} +
); } diff --git a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx index 76d80b805..7dbf8d457 100644 --- a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx +++ b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx @@ -14,6 +14,7 @@ import { IsCliLoginSuccessful } from "@app/components/utilities/attemptCliLogin" import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, Spinner } from "@app/components/v2"; import { SessionStorageKeys } from "@app/const"; +import { OrgMembershipRole } from "@app/helpers/roles"; import { useToggle } from "@app/hooks"; import { useGetOrganizations, @@ -68,7 +69,7 @@ export const SelectOrganizationPage = () => { const handleSelectOrganization = useCallback( async (organization: Organization) => { - if (organization.authEnforced) { + if (organization.authEnforced && organization.userRole !== OrgMembershipRole.Admin) { // org has an org-level auth method enabled (e.g. SAML) // -> logout + redirect to SAML SSO await logout.mutateAsync(); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx index 5e04f173e..6bcdef231 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -85,7 +85,7 @@ export const OrgGeneralAuthSection = () => {

- Enforce members to authenticate via SAML to access this organization + Enforce non-admin users to authenticate via SAML to access this organization

{

- Enforce members to authenticate via OIDC to access this organization + Enforce non-admin users to authenticate via OIDC to access this organization.

From 785173747f3995fbfd9982743cec02c554807137 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 16 Apr 2025 03:28:04 +0800 Subject: [PATCH 2/9] misc: introduce admin login portal --- .../src/services/auth/auth-login-service.ts | 57 ++++++++++++------- .../src/pages/auth/AdminLoginPage/route.tsx | 7 +++ .../src/pages/auth/LoginPage/LoginPage.tsx | 3 +- .../components/InitialStep/InitialStep.tsx | 31 +++++----- .../OrgAuthTab/OrgGeneralAuthSection.tsx | 38 ++++++++++++- .../components/OrgAuthTab/OrgOIDCSection.tsx | 35 +++++++++++- frontend/src/routeTree.gen.ts | 27 +++++++++ frontend/src/routes.ts | 1 + 8 files changed, 157 insertions(+), 42 deletions(-) create mode 100644 frontend/src/pages/auth/AdminLoginPage/route.tsx diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 8dfe69643..0e0f999dd 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -2,7 +2,7 @@ import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; 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 { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; @@ -174,20 +174,25 @@ export const authLoginServiceFactory = ({ const userEnc = await userDAL.findUserEncKeyByUsername({ username: email }); + const serverCfg = await getServerCfg(); + if (!userEnc || (userEnc && !userEnc.isAccepted)) { + throw new Error("Failed to find user"); + } + if ( serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.EMAIL) && !providerAuthToken ) { - throw new BadRequestError({ - message: "Login with email is disabled by administrator." - }); - } - - if (!userEnc || (userEnc && !userEnc.isAccepted)) { - throw new Error("Failed to find user"); + // bypass server configuration when user is an organization admin - this is to prevent lockout + 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.authMethods?.includes(AuthMethod.EMAIL)) { @@ -573,28 +578,40 @@ export const authLoginServiceFactory = ({ switch (authMethod) { case AuthMethod.GITHUB: { if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITHUB)) { - throw new BadRequestError({ - message: "Login with Github is disabled by administrator.", - name: "Oauth 2 login" - }); + // bypass server configuration when user is an organization admin - this is to prevent lockout + const userOrgs = await orgDAL.findAllOrgsByUserId(user.id); + if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) { + throw new BadRequestError({ + message: "Login with Github is disabled by administrator.", + name: "Oauth 2 login" + }); + } } break; } case AuthMethod.GOOGLE: { if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GOOGLE)) { - throw new BadRequestError({ - message: "Login with Google is disabled by administrator.", - name: "Oauth 2 login" - }); + // bypass server configuration when user is an organization admin - this is to prevent lockout + const userOrgs = await orgDAL.findAllOrgsByUserId(user.id); + if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) { + throw new BadRequestError({ + message: "Login with Google is disabled by administrator.", + name: "Oauth 2 login" + }); + } } break; } case AuthMethod.GITLAB: { if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITLAB)) { - throw new BadRequestError({ - message: "Login with Gitlab is disabled by administrator.", - name: "Oauth 2 login" - }); + // bypass server configuration when user is an organization admin - this is to prevent lockout + const userOrgs = await orgDAL.findAllOrgsByUserId(user.id); + if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) { + throw new BadRequestError({ + message: "Login with Gitlab is disabled by administrator.", + name: "Oauth 2 login" + }); + } } break; } diff --git a/frontend/src/pages/auth/AdminLoginPage/route.tsx b/frontend/src/pages/auth/AdminLoginPage/route.tsx new file mode 100644 index 000000000..221c6a34e --- /dev/null +++ b/frontend/src/pages/auth/AdminLoginPage/route.tsx @@ -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: () => +}); diff --git a/frontend/src/pages/auth/LoginPage/LoginPage.tsx b/frontend/src/pages/auth/LoginPage/LoginPage.tsx index 74ced95ef..97bb528c6 100644 --- a/frontend/src/pages/auth/LoginPage/LoginPage.tsx +++ b/frontend/src/pages/auth/LoginPage/LoginPage.tsx @@ -8,7 +8,7 @@ import { isLoggedIn } from "@app/hooks/api/reactQuery"; import { InitialStep, SSOStep } from "./components"; import { useNavigateToSelectOrganization } from "./Login.utils"; -export const LoginPage = () => { +export const LoginPage = ({ isAdmin }: { isAdmin?: boolean }) => { const { t } = useTranslation(); const [step, setStep] = useState(0); const [email, setEmail] = useState(""); @@ -44,6 +44,7 @@ export const LoginPage = () => { case 0: return ( void; password: string; 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 { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const [loginError, setLoginError] = useState(false); - const [isOtherLoginMethodsSelected, setIsOtherLoginMethodsSelected] = useState(false); const { config } = useServerConfig(); const queryParams = new URLSearchParams(window.location.search); const [captchaToken, setCaptchaToken] = useState(""); @@ -63,7 +70,9 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: }; useEffect(() => { - if (serverDetails?.samlDefaultOrgSlug) redirectToSaml(serverDetails.samlDefaultOrgSlug); + if (serverDetails?.samlDefaultOrgSlug && !isAdmin) { + redirectToSaml(serverDetails.samlDefaultOrgSlug); + } }, [serverDetails?.samlDefaultOrgSlug]); const handleSaml = () => { @@ -83,7 +92,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: }; const shouldDisplayLoginMethod = (method: LoginMethod) => - !config.enabledLoginMethods || config.enabledLoginMethods.includes(method); + isAdmin || !config.enabledLoginMethods || config.enabledLoginMethods.includes(method); const handleLogin = async (e: FormEvent) => { e.preventDefault(); @@ -161,11 +170,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: setIsLoading(false); }; - if ( - config.defaultAuthOrgAuthEnforced && - config.defaultAuthOrgAuthMethod && - !isOtherLoginMethodsSelected - ) { + if (config.defaultAuthOrgAuthEnforced && config.defaultAuthOrgAuthMethod && !isAdmin) { return (
)} - ); } diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx index 6bcdef231..291934208 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -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 { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; -import { Switch } from "@app/components/v2"; +import { Switch, Tooltip } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, @@ -72,7 +75,38 @@ export const OrgGeneralAuthSection = () => { */}
-

Enforce SAML SSO

+
+ Enforce SAML SSO + + + Login enforcement is only applied to non-admin users in order to prevent total + lockout from the organization when the SAML provider is unavailable. + + +

+ In case of a lockout, use the admin login portal{" "} + + here. + +

+
+ } + > + + +
{(isAllowed) => ( { )}
-

Enforce OIDC SSO

+
+ Enforce OIDC SSO + + + Login enforcement is only applied to non-admin users in order to prevent total + lockout from the organization when the OIDC provider is unavailable. + + +

+ In case of a lockout, use the admin login portal{" "} + + here. + +

+
+ } + > + + +
{(isAllowed) => ( {

- Enforce non-admin users to authenticate via OIDC to access this organization. + Enforce non-admin users to authenticate via OIDC to access this organization.

diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 7cd0b5ea9..e3ab6df7d 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -33,6 +33,7 @@ import { Route as authSignUpSsoPageRouteImport } from './pages/auth/SignUpSsoPag import { Route as authLoginSsoPageRouteImport } from './pages/auth/LoginSsoPage/route' import { Route as authSelectOrgPageRouteImport } from './pages/auth/SelectOrgPage/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 organizationNoOrgPageRouteImport } from './pages/organization/NoOrgPage/route' import { Route as authSignUpPageRouteImport } from './pages/auth/SignUpPage/route' @@ -393,6 +394,12 @@ const authLoginLdapPageRouteRoute = authLoginLdapPageRouteImport.update({ getParentRoute: () => RestrictLoginSignupLoginRoute, } as any) +const authAdminLoginPageRouteRoute = authAdminLoginPageRouteImport.update({ + id: '/admin', + path: '/admin', + getParentRoute: () => RestrictLoginSignupLoginRoute, +} as any) + const adminSignUpPageRouteRoute = adminSignUpPageRouteImport.update({ id: '/admin/signup', path: '/admin/signup', @@ -1775,6 +1782,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof adminSignUpPageRouteImport 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': { id: '/_restrict-login-signup/login/ldap' path: '/ldap' @@ -3655,6 +3669,7 @@ const middlewaresAuthenticateRouteWithChildren = interface RestrictLoginSignupLoginRouteChildren { authLoginPageRouteRoute: typeof authLoginPageRouteRoute + authAdminLoginPageRouteRoute: typeof authAdminLoginPageRouteRoute authLoginLdapPageRouteRoute: typeof authLoginLdapPageRouteRoute authSelectOrgPageRouteRoute: typeof authSelectOrgPageRouteRoute authLoginSsoPageRouteRoute: typeof authLoginSsoPageRouteRoute @@ -3665,6 +3680,7 @@ interface RestrictLoginSignupLoginRouteChildren { const RestrictLoginSignupLoginRouteChildren: RestrictLoginSignupLoginRouteChildren = { authLoginPageRouteRoute: authLoginPageRouteRoute, + authAdminLoginPageRouteRoute: authAdminLoginPageRouteRoute, authLoginLdapPageRouteRoute: authLoginLdapPageRouteRoute, authSelectOrgPageRouteRoute: authSelectOrgPageRouteRoute, authLoginSsoPageRouteRoute: authLoginSsoPageRouteRoute, @@ -3739,6 +3755,7 @@ export interface FileRoutesByFullPath { '/signup/': typeof authSignUpPageRouteRoute '/organization/none': typeof organizationNoOrgPageRouteRoute '/admin/signup': typeof adminSignUpPageRouteRoute + '/login/admin': typeof authAdminLoginPageRouteRoute '/login/ldap': typeof authLoginLdapPageRouteRoute '/login/select-organization': typeof authSelectOrgPageRouteRoute '/login/sso': typeof authLoginSsoPageRouteRoute @@ -3918,6 +3935,7 @@ export interface FileRoutesByTo { '/signup': typeof authSignUpPageRouteRoute '/organization/none': typeof organizationNoOrgPageRouteRoute '/admin/signup': typeof adminSignUpPageRouteRoute + '/login/admin': typeof authAdminLoginPageRouteRoute '/login/ldap': typeof authLoginLdapPageRouteRoute '/login/select-organization': typeof authSelectOrgPageRouteRoute '/login/sso': typeof authLoginSsoPageRouteRoute @@ -4096,6 +4114,7 @@ export interface FileRoutesById { '/_restrict-login-signup/signup/': typeof authSignUpPageRouteRoute '/_authenticate/organization/none': typeof organizationNoOrgPageRouteRoute '/_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/select-organization': typeof authSelectOrgPageRouteRoute '/_restrict-login-signup/login/sso': typeof authLoginSsoPageRouteRoute @@ -4286,6 +4305,7 @@ export interface FileRouteTypes { | '/signup/' | '/organization/none' | '/admin/signup' + | '/login/admin' | '/login/ldap' | '/login/select-organization' | '/login/sso' @@ -4464,6 +4484,7 @@ export interface FileRouteTypes { | '/signup' | '/organization/none' | '/admin/signup' + | '/login/admin' | '/login/ldap' | '/login/select-organization' | '/login/sso' @@ -4640,6 +4661,7 @@ export interface FileRouteTypes { | '/_restrict-login-signup/signup/' | '/_authenticate/organization/none' | '/_restrict-login-signup/admin/signup' + | '/_restrict-login-signup/login/admin' | '/_restrict-login-signup/login/ldap' | '/_restrict-login-signup/login/select-organization' | '/_restrict-login-signup/login/sso' @@ -4928,6 +4950,7 @@ export const routeTree = rootRoute "parent": "/_restrict-login-signup", "children": [ "/_restrict-login-signup/login/", + "/_restrict-login-signup/login/admin", "/_restrict-login-signup/login/ldap", "/_restrict-login-signup/login/select-organization", "/_restrict-login-signup/login/sso", @@ -4959,6 +4982,10 @@ export const routeTree = rootRoute "filePath": "admin/SignUpPage/route.tsx", "parent": "/_restrict-login-signup" }, + "/_restrict-login-signup/login/admin": { + "filePath": "auth/AdminLoginPage/route.tsx", + "parent": "/_restrict-login-signup/login" + }, "/_restrict-login-signup/login/ldap": { "filePath": "auth/LoginLdapPage/route.tsx", "parent": "/_restrict-login-signup/login" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 47f2d6ab1..d6793ea1a 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -328,6 +328,7 @@ export const routes = rootRoute("root.tsx", [ route("/admin/signup", "admin/SignUpPage/route.tsx"), route("/login", [ index("auth/LoginPage/route.tsx"), + route("/admin", "auth/AdminLoginPage/route.tsx"), route("/select-organization", "auth/SelectOrgPage/route.tsx"), route("/sso", "auth/LoginSsoPage/route.tsx"), route("/ldap", "auth/LoginLdapPage/route.tsx"), From dd0880825b9c985a326df11f13c38357d7d94401 Mon Sep 17 00:00:00 2001 From: Sheen <65645666+sheensantoscapadngan@users.noreply.github.com> Date: Tue, 15 Apr 2025 19:50:49 +0000 Subject: [PATCH 3/9] doc: added reference to admin login portal --- docs/documentation/platform/sso/auth0-oidc.mdx | 4 +++- docs/documentation/platform/sso/auth0-saml.mdx | 4 ++++ docs/documentation/platform/sso/azure.mdx | 3 +++ docs/documentation/platform/sso/general-oidc.mdx | 3 +++ docs/documentation/platform/sso/google-saml.mdx | 3 +++ docs/documentation/platform/sso/jumpcloud.mdx | 3 +++ docs/documentation/platform/sso/keycloak-oidc/overview.mdx | 4 +++- docs/documentation/platform/sso/keycloak-saml.mdx | 3 +++ docs/documentation/platform/sso/okta.mdx | 3 +++ 9 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/documentation/platform/sso/auth0-oidc.mdx b/docs/documentation/platform/sso/auth0-oidc.mdx index 9419d0976..ccc580fc7 100644 --- a/docs/documentation/platform/sso/auth0-oidc.mdx +++ b/docs/documentation/platform/sso/auth0-oidc.mdx @@ -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 prior to enforcing OIDC SSO to prevent any unintended issues. - + + 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. + diff --git a/docs/documentation/platform/sso/auth0-saml.mdx b/docs/documentation/platform/sso/auth0-saml.mdx index b77c733f7..b426d1aae 100644 --- a/docs/documentation/platform/sso/auth0-saml.mdx +++ b/docs/documentation/platform/sso/auth0-saml.mdx @@ -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; Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. + + + 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. + diff --git a/docs/documentation/platform/sso/azure.mdx b/docs/documentation/platform/sso/azure.mdx index 21236cd5b..282cddae5 100644 --- a/docs/documentation/platform/sso/azure.mdx +++ b/docs/documentation/platform/sso/azure.mdx @@ -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 prior to enforcing SAML SSO to prevent any unintended issues. + + 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. + diff --git a/docs/documentation/platform/sso/general-oidc.mdx b/docs/documentation/platform/sso/general-oidc.mdx index 7e3a76ff0..11216b893 100644 --- a/docs/documentation/platform/sso/general-oidc.mdx +++ b/docs/documentation/platform/sso/general-oidc.mdx @@ -66,6 +66,9 @@ Prerequisites: We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues. + + 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. + diff --git a/docs/documentation/platform/sso/google-saml.mdx b/docs/documentation/platform/sso/google-saml.mdx index 7e47d1137..87ffa8412 100644 --- a/docs/documentation/platform/sso/google-saml.mdx +++ b/docs/documentation/platform/sso/google-saml.mdx @@ -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 prior to enforcing SAML SSO to prevent any unintended issues. + + 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. + diff --git a/docs/documentation/platform/sso/jumpcloud.mdx b/docs/documentation/platform/sso/jumpcloud.mdx index ec876b9da..6ca20c752 100644 --- a/docs/documentation/platform/sso/jumpcloud.mdx +++ b/docs/documentation/platform/sso/jumpcloud.mdx @@ -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 prior to enforcing SAML SSO to prevent any unintended issues. + + 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. + diff --git a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx index 4f5bc689e..d2d427c55 100644 --- a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx +++ b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx @@ -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 prior to enforcing OIDC SSO to prevent any unintended issues. - + + 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. + diff --git a/docs/documentation/platform/sso/keycloak-saml.mdx b/docs/documentation/platform/sso/keycloak-saml.mdx index 86352bb9d..7e4004122 100644 --- a/docs/documentation/platform/sso/keycloak-saml.mdx +++ b/docs/documentation/platform/sso/keycloak-saml.mdx @@ -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 prior to enforcing SAML SSO to prevent any unintended issues. + + 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. + diff --git a/docs/documentation/platform/sso/okta.mdx b/docs/documentation/platform/sso/okta.mdx index 9f28f4c4e..1abd03d6f 100644 --- a/docs/documentation/platform/sso/okta.mdx +++ b/docs/documentation/platform/sso/okta.mdx @@ -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 prior to enforcing SAML SSO to prevent any unintended issues. + + 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. + From 76c3f1c152f07388627130d12f5a4e397b488ba3 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 16 Apr 2025 23:58:20 +0800 Subject: [PATCH 4/9] misc: made bypass opt-in --- ...6145120_add-enable-bypass-org-auth-flag.ts | 19 ++++ backend/src/db/schemas/organizations.ts | 3 +- .../ee/services/permission/permission-dal.ts | 9 +- .../ee/services/permission/permission-fns.ts | 6 +- .../services/permission/permission-service.ts | 14 ++- .../server/routes/v1/organization-router.ts | 3 +- backend/src/services/org/org-schema.ts | 3 +- backend/src/services/org/org-service.ts | 6 +- backend/src/services/org/org-types.ts | 1 + .../src/hooks/api/organization/queries.tsx | 6 +- frontend/src/hooks/api/organization/types.ts | 2 + .../auth/SelectOrgPage/SelectOrgPage.tsx | 5 +- .../OrgAuthTab/OrgGeneralAuthSection.tsx | 88 ++++++++++++------- .../components/OrgAuthTab/OrgOIDCSection.tsx | 88 ++++++++++++------- 14 files changed, 180 insertions(+), 73 deletions(-) create mode 100644 backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts diff --git a/backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts b/backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts new file mode 100644 index 000000000..ef2957545 --- /dev/null +++ b/backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts @@ -0,0 +1,19 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasColumn(TableName.Organization, "enableBypassOrgAuth"))) { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.boolean("enableBypassOrgAuth").defaultTo(false).notNullable(); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.Organization, "enableBypassOrgAuth")) { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.dropColumn("enableBypassOrgAuth"); + }); + } +} diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index a18e258c7..3e475fda1 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -26,7 +26,8 @@ export const OrganizationsSchema = z.object({ allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(), shouldUseNewPrivilegeSystem: z.boolean().default(true), privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(), - privilegeUpgradeInitiatedAt: z.date().nullable().optional() + privilegeUpgradeInitiatedAt: z.date().nullable().optional(), + enableBypassOrgAuth: z.boolean().default(false) }); export type TOrganizations = z.infer; diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index b76403400..410e4d48e 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -54,6 +54,7 @@ export const permissionDALFactory = (db: TDbClient) => { db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"), db.ref("permissions").withSchema(TableName.OrgRoles), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), + db.ref("enableBypassOrgAuth").withSchema(TableName.Organization).as("enableBypassOrgAuth"), db.ref("groupId").withSchema("userGroups"), db.ref("groupOrgId").withSchema("userGroups"), db.ref("groupName").withSchema("userGroups"), @@ -72,6 +73,7 @@ export const permissionDALFactory = (db: TDbClient) => { OrgMembershipsSchema.extend({ permissions: z.unknown(), orgAuthEnforced: z.boolean().optional().nullable(), + enableBypassOrgAuth: z.boolean(), customRoleSlug: z.string().optional().nullable(), shouldUseNewPrivilegeSystem: z.boolean() }).parse(el), @@ -676,6 +678,7 @@ export const permissionDALFactory = (db: TDbClient) => { db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), + db.ref("enableBypassOrgAuth").withSchema(TableName.Organization).as("enableBypassOrgAuth"), db.ref("role").withSchema(TableName.OrgMembership).as("orgRole"), db.ref("orgId").withSchema(TableName.Project), db.ref("type").withSchema(TableName.Project).as("projectType"), @@ -698,7 +701,8 @@ export const permissionDALFactory = (db: TDbClient) => { groupMembershipUpdatedAt, membershipUpdatedAt, projectType, - shouldUseNewPrivilegeSystem + shouldUseNewPrivilegeSystem, + enableBypassOrgAuth }) => ({ orgId, orgAuthEnforced, @@ -710,7 +714,8 @@ export const permissionDALFactory = (db: TDbClient) => { id: membershipId || groupMembershipId, createdAt: membershipCreatedAt || groupMembershipCreatedAt, updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt, - shouldUseNewPrivilegeSystem + shouldUseNewPrivilegeSystem, + enableBypassOrgAuth }), childrenMapper: [ { diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts index 410fc5819..e532afe05 100644 --- a/backend/src/ee/services/permission/permission-fns.ts +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -121,14 +121,18 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) { function validateOrgSSO( actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrganizations["authEnforced"], + isOrgSsoBypassEnabled: TOrganizations["enableBypassOrgAuth"], orgRole: OrgMembershipRole ) { if (actorAuthMethod === undefined) { throw new UnauthorizedError({ name: "No auth method defined" }); } + if (isOrgSsoEnforced && isOrgSsoBypassEnabled && orgRole === OrgMembershipRole.Admin) { + return; + } + if ( - orgRole !== OrgMembershipRole.Admin && isOrgSsoEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod) && diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 0a63931aa..69dadec00 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -139,7 +139,12 @@ export const permissionServiceFactory = ({ throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); } - validateOrgSSO(authMethod, membership.orgAuthEnforced, membership.role as OrgMembershipRole); + validateOrgSSO( + authMethod, + membership.orgAuthEnforced, + membership.enableBypassOrgAuth, + membership.role as OrgMembershipRole + ); const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat( membership?.groups?.map(({ role, customRolePermission }) => ({ @@ -226,7 +231,12 @@ export const permissionServiceFactory = ({ throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); } - validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced, userProjectPermission.orgRole); + validateOrgSSO( + authMethod, + userProjectPermission.orgAuthEnforced, + userProjectPermission.enableBypassOrgAuth, + userProjectPermission.orgRole + ); if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) { throw new BadRequestError({ diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index c6391d4f7..25c3ea7f2 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -260,7 +260,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(), enforceMfa: z.boolean().optional(), selectedMfaMethod: z.nativeEnum(MfaMethod).optional(), - allowSecretSharingOutsideOrganization: z.boolean().optional() + allowSecretSharingOutsideOrganization: z.boolean().optional(), + enableBypassOrgAuth: z.boolean().optional() }), response: { 200: z.object({ diff --git a/backend/src/services/org/org-schema.ts b/backend/src/services/org/org-schema.ts index ef49d8178..7b5825e34 100644 --- a/backend/src/services/org/org-schema.ts +++ b/backend/src/services/org/org-schema.ts @@ -16,5 +16,6 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({ allowSecretSharingOutsideOrganization: true, shouldUseNewPrivilegeSystem: true, privilegeUpgradeInitiatedByUsername: true, - privilegeUpgradeInitiatedAt: true + privilegeUpgradeInitiatedAt: true, + enableBypassOrgAuth: true }); diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 98b35f68f..a5df934d2 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -349,7 +349,8 @@ export const orgServiceFactory = ({ defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod, - allowSecretSharingOutsideOrganization + allowSecretSharingOutsideOrganization, + enableBypassOrgAuth } }: TUpdateOrgDTO) => { const appCfg = getConfig(); @@ -429,7 +430,8 @@ export const orgServiceFactory = ({ defaultMembershipRole, enforceMfa, selectedMfaMethod, - allowSecretSharingOutsideOrganization + allowSecretSharingOutsideOrganization, + enableBypassOrgAuth }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); return org; diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index 9d14b092e..136e6cc84 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -73,6 +73,7 @@ export type TUpdateOrgDTO = { enforceMfa: boolean; selectedMfaMethod: MfaMethod; allowSecretSharingOutsideOrganization: boolean; + enableBypassOrgAuth: boolean; }>; } & TOrgPermission; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 90e525cdf..5bc6c2c7b 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -110,7 +110,8 @@ export const useUpdateOrg = () => { defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod, - allowSecretSharingOutsideOrganization + allowSecretSharingOutsideOrganization, + enableBypassOrgAuth }) => { return apiRequest.patch(`/api/v1/organization/${orgId}`, { name, @@ -120,7 +121,8 @@ export const useUpdateOrg = () => { defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod, - allowSecretSharingOutsideOrganization + allowSecretSharingOutsideOrganization, + enableBypassOrgAuth }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 6eeebd819..302b635de 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -9,6 +9,7 @@ export type Organization = { createAt: string; updatedAt: string; authEnforced: boolean; + enableBypassOrgAuth: boolean; orgAuthMethod: string; scimEnabled: boolean; slug: string; @@ -30,6 +31,7 @@ export type UpdateOrgDTO = { enforceMfa?: boolean; selectedMfaMethod?: MfaMethod; allowSecretSharingOutsideOrganization?: boolean; + enableBypassOrgAuth?: boolean; }; export type BillingDetails = { diff --git a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx index 7dbf8d457..601ce08b9 100644 --- a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx +++ b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx @@ -69,7 +69,10 @@ export const SelectOrganizationPage = () => { const handleSelectOrganization = useCallback( async (organization: Organization) => { - if (organization.authEnforced && organization.userRole !== OrgMembershipRole.Admin) { + const canBypassOrgAuth = + organization.enableBypassOrgAuth && organization.userRole === OrgMembershipRole.Admin; + + if (organization.authEnforced && !canBypassOrgAuth) { // org has an org-level auth method enabled (e.g. SAML) // -> logout + redirect to SAML SSO await logout.mutateAsync(); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx index 291934208..462fd75d7 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -55,6 +55,28 @@ export const OrgGeneralAuthSection = () => { } }; + const handleEnableBypassOrgAuthToggle = async (value: boolean) => { + try { + if (!currentOrg?.id) return; + if (!subscription?.oidcSSO) { + handlePopUpOpen("upgradePlan"); + return; + } + + await mutateAsync({ + orgId: currentOrg?.id, + enableBypassOrgAuth: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} admin bypassing of org-level auth`, + type: "success" + }); + } catch (err) { + console.error(err); + } + }; + return ( <> {/*
@@ -77,35 +99,6 @@ export const OrgGeneralAuthSection = () => {
Enforce SAML SSO - - - Login enforcement is only applied to non-admin users in order to prevent total - lockout from the organization when the SAML provider is unavailable. - - -

- In case of a lockout, use the admin login portal{" "} - - here. - -

-
- } - > - -
{(isAllowed) => ( @@ -119,9 +112,44 @@ export const OrgGeneralAuthSection = () => {

- Enforce non-admin users to authenticate via SAML to access this organization + Enforce users to authenticate via SAML to access this organization

+ {currentOrg?.authEnforced && ( +
+
+
+ Enable Admin SSO Bypass + + + +
+ + {(isAllowed) => ( + handleEnableBypassOrgAuthToggle(value)} + isDisabled={!isAllowed} + /> + )} + +
+

+ + Allow organization admins to bypass OIDC enforcement when SSO is unavailable, + misconfigured, or inaccessible. + +

+
+ )} handlePopUpToggle("upgradePlan", isOpen)} diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx index a494115a3..33fbd4aeb 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx @@ -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, + enableBypassOrgAuth: 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) => { try { if (!currentOrg?.id) return; @@ -160,35 +182,6 @@ export const OrgOIDCSection = (): JSX.Element => {
Enforce OIDC SSO - - - Login enforcement is only applied to non-admin users in order to prevent total - lockout from the organization when the OIDC provider is unavailable. - - -

- In case of a lockout, use the admin login portal{" "} - - here. - -

-
- } - > - -
{(isAllowed) => ( @@ -202,9 +195,44 @@ export const OrgOIDCSection = (): JSX.Element => {

- Enforce non-admin users to authenticate via OIDC to access this organization. + Enforce users to authenticate via OIDC to access this organization.

+ {currentOrg?.authEnforced && ( +
+
+
+ Enable Admin SSO Bypass + + + +
+ + {(isAllowed) => ( + handleEnableBypassOrgAuthToggle(value)} + isDisabled={!isAllowed} + /> + )} + +
+

+ + Allow organization admins to bypass OIDC enforcement when SSO is unavailable, + misconfigured, or inaccessible. + +

+
+ )}
From a9dab557d9c3ae7dddd032722380812a38d2f48b Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 17 Apr 2025 00:06:27 +0800 Subject: [PATCH 5/9] misc: correct labels --- .../components/OrgAuthTab/OrgGeneralAuthSection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx index 462fd75d7..6e879b971 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -58,7 +58,7 @@ export const OrgGeneralAuthSection = () => { const handleEnableBypassOrgAuthToggle = async (value: boolean) => { try { if (!currentOrg?.id) return; - if (!subscription?.oidcSSO) { + if (!subscription?.samlSSO) { handlePopUpOpen("upgradePlan"); return; } @@ -144,7 +144,7 @@ export const OrgGeneralAuthSection = () => {

- Allow organization admins to bypass OIDC enforcement when SSO is unavailable, + Allow organization admins to bypass SAML enforcement when SSO is unavailable, misconfigured, or inaccessible.

From 8c87c40467b7e67753505c5064fac612d67d15c7 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 17 Apr 2025 00:33:07 +0800 Subject: [PATCH 6/9] misc: only bypass when from admin login --- frontend/src/pages/auth/LoginPage/Login.utils.tsx | 11 +++++++++-- .../LoginPage/components/InitialStep/InitialStep.tsx | 2 +- .../src/pages/auth/SelectOrgPage/SelectOrgPage.tsx | 5 ++++- frontend/src/pages/auth/SelectOrgPage/route.tsx | 7 +++++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/auth/LoginPage/Login.utils.tsx b/frontend/src/pages/auth/LoginPage/Login.utils.tsx index 11fcf0d2d..87164fc5d 100644 --- a/frontend/src/pages/auth/LoginPage/Login.utils.tsx +++ b/frontend/src/pages/auth/LoginPage/Login.utils.tsx @@ -33,14 +33,21 @@ export const useNavigateToSelectOrganization = () => { const { config } = useServerConfig(); const navigate = useNavigate(); - const navigateToSelectOrganization = async (cliCallbackPort?: string) => { + const navigateToSelectOrganization = async ( + cliCallbackPort?: string, + isFromAdminLogin?: boolean + ) => { if (!config.defaultAuthOrgId) { queryClient.invalidateQueries({ queryKey: userKeys.getUser }); } navigate({ to: "/login/select-organization", - search: { callback_port: cliCallbackPort, org_id: config.defaultAuthOrgId } + search: { + callback_port: cliCallbackPort, + org_id: config.defaultAuthOrgId, + is_admin_login: isFromAdminLogin + } }); }; diff --git a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx index c00b6a909..ac32816e0 100644 --- a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx +++ b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx @@ -130,7 +130,7 @@ export const InitialStep = ({ if (isLoginSuccessful && isLoginSuccessful.success) { // case: login was successful - navigateToSelectOrganization(); + navigateToSelectOrganization(undefined, isAdmin); createNotification({ text: "Successfully logged in", type: "success" diff --git a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx index 601ce08b9..143d7f6bb 100644 --- a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx +++ b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx @@ -54,6 +54,7 @@ export const SelectOrganizationPage = () => { const queryParams = new URLSearchParams(window.location.search); const orgId = queryParams.get("org_id"); const callbackPort = queryParams.get("callback_port"); + const isAdminLogin = queryParams.get("is_admin_login") === "true"; const defaultSelectedOrg = organizations.data?.find((org) => org.id === orgId); const logout = useLogoutUser(true); @@ -70,7 +71,9 @@ export const SelectOrganizationPage = () => { const handleSelectOrganization = useCallback( async (organization: Organization) => { const canBypassOrgAuth = - organization.enableBypassOrgAuth && organization.userRole === OrgMembershipRole.Admin; + organization.enableBypassOrgAuth && + organization.userRole === OrgMembershipRole.Admin && + isAdminLogin; if (organization.authEnforced && !canBypassOrgAuth) { // org has an org-level auth method enabled (e.g. SAML) diff --git a/frontend/src/pages/auth/SelectOrgPage/route.tsx b/frontend/src/pages/auth/SelectOrgPage/route.tsx index 5d617ff5a..445479b3f 100644 --- a/frontend/src/pages/auth/SelectOrgPage/route.tsx +++ b/frontend/src/pages/auth/SelectOrgPage/route.tsx @@ -6,13 +6,16 @@ import { SelectOrganizationPage } from "./SelectOrgPage"; export const SelectOrganizationPageQueryParams = z.object({ 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")({ component: SelectOrganizationPage, validateSearch: zodValidator(SelectOrganizationPageQueryParams), search: { - middlewares: [stripSearchParams({ org_id: "", callback_port: undefined })] + middlewares: [ + stripSearchParams({ org_id: "", callback_port: undefined, is_admin_login: false }) + ] } }); From 5d0bbce12df3b8693646e637519b6a029473951e Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 17 Apr 2025 00:41:42 +0800 Subject: [PATCH 7/9] misc: added admin login url to tooltip --- .../OrgAuthTab/OrgGeneralAuthSection.tsx | 20 ++++++++++++++++++- .../components/OrgAuthTab/OrgOIDCSection.tsx | 20 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx index 6e879b971..b9aa98c95 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -122,7 +122,25 @@ export const OrgGeneralAuthSection = () => { Enable Admin SSO Bypass + + When this is enabled, we strongly recommend enforcing MFA at the organization + level. + +

+ In case of a lockout, admins can use the admin login portal{" "} + + here. + +

+
+ } > { Enable Admin SSO Bypass + + When this is enabled, we strongly recommend enforcing MFA at the organization + level. + +

+ In case of a lockout, admins can use the admin login portal{" "} + + here. + +

+
+ } > Date: Thu, 17 Apr 2025 00:52:24 +0800 Subject: [PATCH 8/9] misc: displayed full admin login url --- .../components/OrgAuthTab/OrgGeneralAuthSection.tsx | 4 ++-- .../SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx index b9aa98c95..fdccaead7 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -129,14 +129,14 @@ export const OrgGeneralAuthSection = () => { level.

- In case of a lockout, admins can use the admin login portal{" "} + In case of a lockout, admins can use the admin login portal in{" "} - here. + {window.location.origin}/login/admin

diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx index 5d48ff583..727591801 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx @@ -212,14 +212,14 @@ export const OrgOIDCSection = (): JSX.Element => { level.

- In case of a lockout, admins can use the admin login portal{" "} + In case of a lockout, admins can use the admin login portal in{" "} - here. + {window.location.origin}/login/admin

From 1137247e69db095d62ca1367c502b38e03e71d3e Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 17 Apr 2025 04:00:02 +0800 Subject: [PATCH 9/9] misc: addressed feedback --- .../20250416145120_add-enable-bypass-org-auth-flag.ts | 8 ++++---- backend/src/db/schemas/organizations.ts | 2 +- backend/src/ee/services/permission/permission-dal.ts | 10 +++++----- backend/src/ee/services/permission/permission-fns.ts | 2 +- .../src/ee/services/permission/permission-service.ts | 4 ++-- backend/src/server/routes/v1/organization-router.ts | 2 +- backend/src/services/org/org-schema.ts | 2 +- backend/src/services/org/org-service.ts | 4 ++-- backend/src/services/org/org-types.ts | 2 +- frontend/src/hooks/api/organization/queries.tsx | 4 ++-- frontend/src/hooks/api/organization/types.ts | 4 ++-- .../src/pages/auth/SelectOrgPage/SelectOrgPage.tsx | 2 +- .../components/OrgAuthTab/OrgGeneralAuthSection.tsx | 6 +++--- .../components/OrgAuthTab/OrgOIDCSection.tsx | 6 +++--- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts b/backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts index ef2957545..fb9a12625 100644 --- a/backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts +++ b/backend/src/db/migrations/20250416145120_add-enable-bypass-org-auth-flag.ts @@ -3,17 +3,17 @@ import { Knex } from "knex"; import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { - if (!(await knex.schema.hasColumn(TableName.Organization, "enableBypassOrgAuth"))) { + if (!(await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled"))) { await knex.schema.alterTable(TableName.Organization, (t) => { - t.boolean("enableBypassOrgAuth").defaultTo(false).notNullable(); + t.boolean("bypassOrgAuthEnabled").defaultTo(false).notNullable(); }); } } export async function down(knex: Knex): Promise { - if (await knex.schema.hasColumn(TableName.Organization, "enableBypassOrgAuth")) { + if (await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled")) { await knex.schema.alterTable(TableName.Organization, (t) => { - t.dropColumn("enableBypassOrgAuth"); + t.dropColumn("bypassOrgAuthEnabled"); }); } } diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index 3e475fda1..eea1808e0 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -27,7 +27,7 @@ export const OrganizationsSchema = z.object({ shouldUseNewPrivilegeSystem: z.boolean().default(true), privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(), privilegeUpgradeInitiatedAt: z.date().nullable().optional(), - enableBypassOrgAuth: z.boolean().default(false) + bypassOrgAuthEnabled: z.boolean().default(false) }); export type TOrganizations = z.infer; diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index 410e4d48e..891d7193e 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -54,7 +54,7 @@ export const permissionDALFactory = (db: TDbClient) => { db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"), db.ref("permissions").withSchema(TableName.OrgRoles), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), - db.ref("enableBypassOrgAuth").withSchema(TableName.Organization).as("enableBypassOrgAuth"), + db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"), db.ref("groupId").withSchema("userGroups"), db.ref("groupOrgId").withSchema("userGroups"), db.ref("groupName").withSchema("userGroups"), @@ -73,7 +73,7 @@ export const permissionDALFactory = (db: TDbClient) => { OrgMembershipsSchema.extend({ permissions: z.unknown(), orgAuthEnforced: z.boolean().optional().nullable(), - enableBypassOrgAuth: z.boolean(), + bypassOrgAuthEnabled: z.boolean(), customRoleSlug: z.string().optional().nullable(), shouldUseNewPrivilegeSystem: z.boolean() }).parse(el), @@ -678,7 +678,7 @@ export const permissionDALFactory = (db: TDbClient) => { db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), - db.ref("enableBypassOrgAuth").withSchema(TableName.Organization).as("enableBypassOrgAuth"), + db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"), db.ref("role").withSchema(TableName.OrgMembership).as("orgRole"), db.ref("orgId").withSchema(TableName.Project), db.ref("type").withSchema(TableName.Project).as("projectType"), @@ -702,7 +702,7 @@ export const permissionDALFactory = (db: TDbClient) => { membershipUpdatedAt, projectType, shouldUseNewPrivilegeSystem, - enableBypassOrgAuth + bypassOrgAuthEnabled }) => ({ orgId, orgAuthEnforced, @@ -715,7 +715,7 @@ export const permissionDALFactory = (db: TDbClient) => { createdAt: membershipCreatedAt || groupMembershipCreatedAt, updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt, shouldUseNewPrivilegeSystem, - enableBypassOrgAuth + bypassOrgAuthEnabled }), childrenMapper: [ { diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts index e532afe05..d645e2bec 100644 --- a/backend/src/ee/services/permission/permission-fns.ts +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -121,7 +121,7 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) { function validateOrgSSO( actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrganizations["authEnforced"], - isOrgSsoBypassEnabled: TOrganizations["enableBypassOrgAuth"], + isOrgSsoBypassEnabled: TOrganizations["bypassOrgAuthEnabled"], orgRole: OrgMembershipRole ) { if (actorAuthMethod === undefined) { diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 69dadec00..0082c3d17 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -142,7 +142,7 @@ export const permissionServiceFactory = ({ validateOrgSSO( authMethod, membership.orgAuthEnforced, - membership.enableBypassOrgAuth, + membership.bypassOrgAuthEnabled, membership.role as OrgMembershipRole ); @@ -234,7 +234,7 @@ export const permissionServiceFactory = ({ validateOrgSSO( authMethod, userProjectPermission.orgAuthEnforced, - userProjectPermission.enableBypassOrgAuth, + userProjectPermission.bypassOrgAuthEnabled, userProjectPermission.orgRole ); diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 25c3ea7f2..90ca3d255 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -261,7 +261,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { enforceMfa: z.boolean().optional(), selectedMfaMethod: z.nativeEnum(MfaMethod).optional(), allowSecretSharingOutsideOrganization: z.boolean().optional(), - enableBypassOrgAuth: z.boolean().optional() + bypassOrgAuthEnabled: z.boolean().optional() }), response: { 200: z.object({ diff --git a/backend/src/services/org/org-schema.ts b/backend/src/services/org/org-schema.ts index 7b5825e34..2aa793c04 100644 --- a/backend/src/services/org/org-schema.ts +++ b/backend/src/services/org/org-schema.ts @@ -17,5 +17,5 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({ shouldUseNewPrivilegeSystem: true, privilegeUpgradeInitiatedByUsername: true, privilegeUpgradeInitiatedAt: true, - enableBypassOrgAuth: true + bypassOrgAuthEnabled: true }); diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index a5df934d2..c83a1a802 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -350,7 +350,7 @@ export const orgServiceFactory = ({ enforceMfa, selectedMfaMethod, allowSecretSharingOutsideOrganization, - enableBypassOrgAuth + bypassOrgAuthEnabled } }: TUpdateOrgDTO) => { const appCfg = getConfig(); @@ -431,7 +431,7 @@ export const orgServiceFactory = ({ enforceMfa, selectedMfaMethod, allowSecretSharingOutsideOrganization, - enableBypassOrgAuth + bypassOrgAuthEnabled }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); return org; diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index 136e6cc84..8a1698015 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -73,7 +73,7 @@ export type TUpdateOrgDTO = { enforceMfa: boolean; selectedMfaMethod: MfaMethod; allowSecretSharingOutsideOrganization: boolean; - enableBypassOrgAuth: boolean; + bypassOrgAuthEnabled: boolean; }>; } & TOrgPermission; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 5bc6c2c7b..902bb6b09 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -111,7 +111,7 @@ export const useUpdateOrg = () => { enforceMfa, selectedMfaMethod, allowSecretSharingOutsideOrganization, - enableBypassOrgAuth + bypassOrgAuthEnabled }) => { return apiRequest.patch(`/api/v1/organization/${orgId}`, { name, @@ -122,7 +122,7 @@ export const useUpdateOrg = () => { enforceMfa, selectedMfaMethod, allowSecretSharingOutsideOrganization, - enableBypassOrgAuth + bypassOrgAuthEnabled }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 302b635de..e0687922d 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -9,7 +9,7 @@ export type Organization = { createAt: string; updatedAt: string; authEnforced: boolean; - enableBypassOrgAuth: boolean; + bypassOrgAuthEnabled: boolean; orgAuthMethod: string; scimEnabled: boolean; slug: string; @@ -31,7 +31,7 @@ export type UpdateOrgDTO = { enforceMfa?: boolean; selectedMfaMethod?: MfaMethod; allowSecretSharingOutsideOrganization?: boolean; - enableBypassOrgAuth?: boolean; + bypassOrgAuthEnabled?: boolean; }; export type BillingDetails = { diff --git a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx index 143d7f6bb..7dddd1a4b 100644 --- a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx +++ b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx @@ -71,7 +71,7 @@ export const SelectOrganizationPage = () => { const handleSelectOrganization = useCallback( async (organization: Organization) => { const canBypassOrgAuth = - organization.enableBypassOrgAuth && + organization.bypassOrgAuthEnabled && organization.userRole === OrgMembershipRole.Admin && isAdminLogin; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx index fdccaead7..21c440957 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -65,7 +65,7 @@ export const OrgGeneralAuthSection = () => { await mutateAsync({ orgId: currentOrg?.id, - enableBypassOrgAuth: value + bypassOrgAuthEnabled: value }); createNotification({ @@ -129,7 +129,7 @@ export const OrgGeneralAuthSection = () => { level.

- In case of a lockout, admins can use the admin login portal in{" "} + In case of a lockout, admins can use the admin login portal at{" "} { {(isAllowed) => ( handleEnableBypassOrgAuthToggle(value)} isDisabled={!isAllowed} /> diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx index 727591801..f9d7b939c 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx @@ -92,7 +92,7 @@ export const OrgOIDCSection = (): JSX.Element => { await updateOrg({ orgId: currentOrg?.id, - enableBypassOrgAuth: value + bypassOrgAuthEnabled: value }); createNotification({ @@ -212,7 +212,7 @@ export const OrgOIDCSection = (): JSX.Element => { level.

- In case of a lockout, admins can use the admin login portal in{" "} + In case of a lockout, admins can use the admin login portal at{" "} { {(isAllowed) => ( handleEnableBypassOrgAuthToggle(value)} isDisabled={!isAllowed} />