diff --git a/backend/src/ee/controllers/v1/ssoController.ts b/backend/src/ee/controllers/v1/ssoController.ts index 4837dfd15..31ad59894 100644 --- a/backend/src/ee/controllers/v1/ssoController.ts +++ b/backend/src/ee/controllers/v1/ssoController.ts @@ -57,7 +57,6 @@ export const updateSSOConfig = async (req: Request, res: Response) => { entryPoint, issuer, cert, - audience } = req.body; const plan = await EELicenseService.getPlan(organizationId); @@ -78,9 +77,6 @@ export const updateSSOConfig = async (req: Request, res: Response) => { encryptedCert?: string; certIV?: string; certTag?: string; - encryptedAudience?: string; - audienceIV?: string; - audienceTag?: string; } const update: PatchUpdate = {}; @@ -132,18 +128,6 @@ export const updateSSOConfig = async (req: Request, res: Response) => { update.certIV = certIV; update.certTag = certTag; } - - if (audience) { - const { - ciphertext: encryptedAudience, - iv: audienceIV, - tag: audienceTag - } = client.encryptSymmetric(audience, key); - - update.encryptedAudience = encryptedAudience; - update.audienceIV = audienceIV; - update.audienceTag = audienceTag; - } const ssoConfig = await SSOConfig.findOneAndUpdate( { @@ -207,8 +191,7 @@ export const createSSOConfig = async (req: Request, res: Response) => { isActive, entryPoint, issuer, - cert, - audience + cert } = req.body; const plan = await EELicenseService.getPlan(organizationId); @@ -238,12 +221,6 @@ export const createSSOConfig = async (req: Request, res: Response) => { iv: certIV, tag: certTag } = client.encryptSymmetric(cert, key); - - const { - ciphertext: encryptedAudience, - iv: audienceIV, - tag: audienceTag - } = client.encryptSymmetric(audience, key); const ssoConfig = await new SSOConfig({ organization: new Types.ObjectId(organizationId), @@ -257,10 +234,7 @@ export const createSSOConfig = async (req: Request, res: Response) => { issuerTag, encryptedCert, certIV, - certTag, - encryptedAudience, - audienceIV, - audienceTag + certTag }).save(); return res.status(200).send(ssoConfig); diff --git a/backend/src/ee/helpers/organizations.ts b/backend/src/ee/helpers/organizations.ts index 477b05ccd..f9f125b39 100644 --- a/backend/src/ee/helpers/organizations.ts +++ b/backend/src/ee/helpers/organizations.ts @@ -51,13 +51,6 @@ export const getSSOConfigHelper = async ({ ssoConfig.certIV, ssoConfig.certTag ); - - const audience = client.decryptSymmetric( - ssoConfig.encryptedAudience, - key, - ssoConfig.audienceIV, - ssoConfig.audienceTag - ); return ({ _id: ssoConfig._id, @@ -66,7 +59,6 @@ export const getSSOConfigHelper = async ({ isActive: ssoConfig.isActive, entryPoint, issuer, - cert, - audience + cert }); } \ No newline at end of file diff --git a/backend/src/ee/models/ssoConfig.ts b/backend/src/ee/models/ssoConfig.ts index ab870afd7..12f695b79 100644 --- a/backend/src/ee/models/ssoConfig.ts +++ b/backend/src/ee/models/ssoConfig.ts @@ -1,8 +1,13 @@ import { Schema, Types, model } from "mongoose"; +export enum AuthProvider { + OKTA_SAML = "okta-saml", + AZURE_SAML = "azure-saml" +} + export interface ISSOConfig { organization: Types.ObjectId; - authProvider: "okta-saml" + authProvider: AuthProvider; isActive: boolean; encryptedEntryPoint: string; entryPointIV: string; @@ -13,9 +18,6 @@ export interface ISSOConfig { encryptedCert: string; certIV: string; certTag: string; - encryptedAudience: string; - audienceIV: string; - audienceTag: string; } const ssoConfigSchema = new Schema( @@ -26,9 +28,7 @@ const ssoConfigSchema = new Schema( }, authProvider: { type: String, - enum: [ - "okta-saml" - ], + enum: AuthProvider, required: true }, isActive: { @@ -61,15 +61,6 @@ const ssoConfigSchema = new Schema( }, certTag: { type: String - }, - encryptedAudience: { - type: String - }, - audienceIV: { - type: String - }, - audienceTag: { - type: String } }, { diff --git a/backend/src/ee/routes/v1/sso.ts b/backend/src/ee/routes/v1/sso.ts index 4771b5144..b54aa5942 100644 --- a/backend/src/ee/routes/v1/sso.ts +++ b/backend/src/ee/routes/v1/sso.ts @@ -1,6 +1,9 @@ import express from "express"; const router = express.Router(); import passport from "passport"; +import { + AuthProvider +} from "../../models"; import { requireAuth, requireOrganizationAuth, @@ -87,12 +90,11 @@ router.post( locationOrganizationId: "body" }), body("organizationId").exists().trim(), - body("authProvider").exists().isString(), + body("authProvider").exists().isString().isIn([AuthProvider.OKTA_SAML]), body("isActive").exists().isBoolean(), body("entryPoint").exists().isString(), body("issuer").exists().isString(), body("cert").exists().isString(), - body("audience").exists().isString(), validateRequest, ssoController.createSSOConfig ); @@ -113,7 +115,6 @@ router.patch( body("entryPoint").optional().isString(), body("issuer").optional().isString(), body("cert").optional().isString(), - body("audience").optional().isString(), validateRequest, ssoController.updateSSOConfig ); diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index ded2f3eb3..4ca60a622 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -135,24 +135,24 @@ const initializePassport = async () => { { passReqToCallback: true, getSamlOptions: async (req: any, done: any) => { - const { ssoIdentifier } = req.params; - - const ssoConfig = await getSSOConfigHelper({ - ssoConfigId: new Types.ObjectId(ssoIdentifier) - }); - - const samlConfig = ({ - path: "/api/v1/auth/callback/saml", - callbackURL: `${await getSiteURL()}/api/v1/auth/callback/saml`, - entryPoint: ssoConfig.entryPoint, - issuer: ssoConfig.issuer, - cert: ssoConfig.cert, - audience: ssoConfig.audience - }); - - req.ssoConfig = ssoConfig; + const { ssoIdentifier } = req.params; + + const ssoConfig = await getSSOConfigHelper({ + ssoConfigId: new Types.ObjectId(ssoIdentifier) + }); + + const samlConfig = ({ + path: `/api/v1/sso/saml2/${ssoIdentifier}`, + callbackURL: `${await getSiteURL()}/api/v1/sso/saml2${ssoIdentifier}`, + entryPoint: ssoConfig.entryPoint, + issuer: ssoConfig.issuer, + cert: ssoConfig.cert, + audience: await getSiteURL() + }); + + req.ssoConfig = ssoConfig; - done(null, samlConfig); + done(null, samlConfig); }, }, async (req: any, profile: any, done: any) => { @@ -161,7 +161,7 @@ const initializePassport = async () => { const organization = await Organization.findById(req.ssoConfig.organization); if (!organization) return done(OrganizationNotFoundError()); - + const email = profile.email; const firstName = profile.firstName; const lastName = profile.lastName; diff --git a/docs/documentation/platform/saml.mdx b/docs/documentation/platform/saml.mdx deleted file mode 100644 index 6f58f7900..000000000 --- a/docs/documentation/platform/saml.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: "SSO" -description: "Log in to Infisical via SSO protocols" ---- - - - Infisical currently only supports SAML SSO authentication with [Okta as the - identity provider (IDP)](https://www.okta.com/). We're expanding support for - other IDPs in the coming months, so stay tuned with this issue - [here](https://github.com/Infisical/infisical/issues/442). - - -You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0). - -To note, configuring SSO retains the end-to-end encrypted architecture of Infisical because we decouple the **authentication** and **decryption** steps. In all login with SSO implementations, -your IDP cannot and will not have access to the decryption key needed to decrypt your secrets. - -## Configuration - -Head over to your organization Settings > Authentication > SAML SSO Configuration. - -Next, press "Set up SAML SSO" in the SAML SSO and follow the instructions -below to configure SSO for your identity provider: - - - Note that only members with the `owner` or `admin` roles in an organization - can configure SSO for it. - - - - - 1. In the Okta Admin Portal, select Applications > Applications from the - navigation. On the Applications screen, select the Create App Integration - button. - - ![SAML Okta create app integration](../../images/saml-okta-1.png) - - 2. In the Create a New Application Integration dialog, select the SAML 2.0 radio button: - - ![SAML Okta create SAML 2.0 integration](../../images/saml-okta-2.png) - - 3. On the General Settings screen, give the application a unique, Infisical-specific name and select Next. - - 4. On the Configure SAML screen, configure the following fields: - - - Single sign on URL: `https://app.infisical.com/api/v1/sso/saml2/:identifier`; we'll update the `:identifier` part later in step 6. - - Audience URI (SP Entity ID): `https://app.infisical.com` - - ![SAML Okta configure IDP fields](../../images/saml-okta-3.png) - - - If you're self-hosting Infisical, then you will want to replace `https://app.infisical.com` with your own domain. - - - 4. Also on the Configure SAML screen, configure the Attribute Statements to map: - - - `id -> user.id`, - - `email -> user.email`, - - `firstName -> user.firstName` - - `lastName -> user.lastName` - - ![SAML Okta attribute statements](../../images/saml-okta-4.png) - - Once configured, select the Next button to proceed to the Feedback screen and select Finish. - - 5. Get IDP values - - Once your application is created, select the Sign On tab for the app and select the View Setup Instructions button located on the right side of the screen: - - Copy the Identity Provider Single Sign-On URL, the Identity Provider Issuer, and the X.509 Certificate to be pasted into your Infisical SAML SSO configuration details with the following map: - - - `Audience -> Okta Audience URI (SP Entity ID)` - - `Entrypoint -> Okta Identity Provider Single Sign-On URL` - - `Issuer -> Identity Provider Issuer` - - `Certificate -> X.509 Certificate`. - - ![SAML Okta IDP values](../../images/saml-okta-5.png) - - ![SAML Okta paste values into Infisical](../../images/saml-okta-6.png) - - 6. Create the SSO configuration and copy your SSO identifier in Infisical; update `:identifier` from step 4 earlier to be this value. - - ![SAML Okta assignments](../../images/saml-okta-7.png) - - 7. Assignments - - Finally, Navigate to the Assignments tab and select the Assign button: - - You can assign access to the application on a user-by-user basis using the Assign to People option, or in-bulk using the Assign to Groups option. - - ![SAML Okta assignment](../../images/saml-okta-8.png) - - At this point, you have configured everything you need within the context of the Okta Admin Portal. - - 8. Return to Infisical and enable SAML SSO. - - Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta. - - - diff --git a/docs/documentation/platform/sso/azure.mdx b/docs/documentation/platform/sso/azure.mdx new file mode 100644 index 000000000..9d7e38b96 --- /dev/null +++ b/docs/documentation/platform/sso/azure.mdx @@ -0,0 +1,84 @@ +--- +title: "Azure SAML" +description: "Configure Azure SAML for Infisical SSO" +--- + +1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**. + Next, copy the **Reply URL (Assertion Consumer Service URL)** and **Identifier (Entity ID)** to use when configuring the Azure SAML application. + +![Azure SAML initial configuration](../../../images/sso/azure/init-config.png) + +2. In the Azure Portal, navigate to the Azure Active Directory and select **Enterprise applications**. On this screen, select + **+ New application**. + +![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png) + +![Azure SAML new application](../../../images/sso/azure/new-application.png) + +2. On the next screen, press the **+ Create your own application** button. + Give the application a unique, Infisical-specific name; choose the "Integrate any other application you don't find in the gallery (Non-gallery)" + option and hit the **Create** button. + +![Azure SAML create own application](../../../images/sso/azure/create-own-application.png) + +3. On the application overview screen, select **Single sign-on** from the left sidebar. From there, + select the **SAML** single sign-on method. + +![Azure SAML sign on method](../../../images/sso/azure/sso-method.png) + +4. Next, select **Edit** in the **Basic SAML Configuration** section and add/set the **Identifier (Entity ID)** + to **Entity ID** and add/set the **Reply URL (Assertion Consumer Service URL)** to **ACS URL** from step 1. + +![Azure SAML edit basic configuration](../../../images/sso/azure/edit-basic-config.png) + +![Azure SAML edit basic configuration 2](../../../images/sso/azure/edit-basic-config-2.png) + + + If you're self-hosting Infisical, then you will want to replace + `https://app.infisical.com` with your own domain. + + +5. Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **Attributes & Claims** section and configure the following map: + +- `email -> user.userprinciplename` +- `firstName -> user.firstName` +- `lastName -> user.lastName` + +![Azure SAML edit attributes and claims](../../../images/sso/azure/edit-attributes-claims.png) + +![Azure SAML edit attributes and claims 2](../../../images/sso/azure/edit-attributes-claims-2.png) + +6. Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **SAML Certificates** section and set the **Signing Option** field to **Sign SAML response and assertion**. + +![Azure SAML edit certificate](../../../images/sso/azure/edit-saml-certificate.png) + +![Azure SAML edit certificate signing option](../../../images/sso/azure/edit-saml-certificate-2.png) + +7. Get IdP values: + +Back in the **Set up Single Sign-On with SAML** screen, copy the **Login URL**, **Azure AD Identifier** and **SAML Certificate** to use when finishing configuring Azure SAML in Infisical. + +Back in Infisical, set **Login URL** and **Azure AD Identifier** from above. Once you've done that, press **Update** to complete the required configuration. + +![Azure SAML identity provider values](../../../images/sso/azure/idp-values.png) +![Azure SAML paste identity provider values](../../../images/sso/azure/idp-values-2.png) + + +When pasting the certificate into Infisical, you'll want to retain `-----BEGIN + CERTIFICATE-----` and `-----END CERTIFICATE-----` at the first and last line + of the text area respectively. + +Having trouble?, try copying the X509 certificate information from the Federation Metadata XML file in Azure. + + + +7. Assignments + +Finally, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level. +![Azure SAML assignment](../../../images/sso/azure/assignment.png) + +8. Return to Infisical and enable SAML SSO. + +Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure. + +![SAML Okta assignment](../../../images/sso/azure/enable-saml.png) diff --git a/docs/documentation/platform/sso/jumpcloud.mdx b/docs/documentation/platform/sso/jumpcloud.mdx new file mode 100644 index 000000000..574489200 --- /dev/null +++ b/docs/documentation/platform/sso/jumpcloud.mdx @@ -0,0 +1,6 @@ +--- +title: "JumpCloud SAML" +description: "Configure JumpCloud SAML for Infisical SSO" +--- + +Coming soon. diff --git a/docs/documentation/platform/sso/okta.mdx b/docs/documentation/platform/sso/okta.mdx new file mode 100644 index 000000000..51eb561ed --- /dev/null +++ b/docs/documentation/platform/sso/okta.mdx @@ -0,0 +1,76 @@ +--- +title: "Okta SAML" +description: "Configure Okta SAML 2.0 for Infisical SSO" +--- + +Prerequisites: + +- Okta Developer Account with access to create custom application integrations. + +1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**. + Next, copy the **Single sign-on URL** and **Audience URI (SP Entity ID)** to use when configuring the Okta SAML 2.0 application. + +![Okta SAML initial configuration](../../../images/sso/okta/init-config.png) + +2. In the Okta Admin Portal, select Applications > Applications from the + navigation. On the Applications screen, select the **Create App Integration** + button. + +![SAML Okta create app integration](../../../images/sso/okta/create-app-integration.png) + +3. In the Create a New Application Integration dialog, select the **SAML 2.0** radio button: + +![SAML Okta create SAML 2.0 integration](../../../images/sso/okta/create-saml-app.png) + +4. On the General Settings screen, give the application a unique name like Infisical and select **Next**. + +![SAML Okta create SAML 2.0 integration](../../../images/sso/okta/general-settings.png) + +5. On the Configure SAML screen, set the **Single sign-on URL** and **Audience URI (SP Entity ID)** from step 1. + +![SAML Okta configure IdP fields](../../../images/sso/okta/configure-saml.png) + + + If you're self-hosting Infisical, then you will want to replace + `https://app.infisical.com` with your own domain. + + +6. Also on the Configure SAML screen, configure the **Attribute Statements** to map: + +- `id -> user.id`, +- `email -> user.email`, +- `firstName -> user.firstName` +- `lastName -> user.lastName` + +![SAML Okta attribute statements](../../../images/sso/okta/attribute-statements.png) + +Once configured, select **Next** to proceed to the Feedback screen and select **Finish**. + +7. Get IdP values + +Once your application is created, select the **Sign On** tab for the app and select the **View Setup Instructions** button located on the right side of the screen: + +![SAML Okta view setup instructions](../../../images/sso/okta/view-setup-instructions.png) + +Copy the **Identity Provider Single Sign-On URL**, the **Identity Provider Issuer**, and the **X.509 Certificate** to use when finishing configuring Okta SAML in Infisical. + +![SAML Okta IdP values](../../../images/sso/okta/idp-values.png) + +Back in Infisical, set **Identity Provider Single Sign-On URL**, **Identity Provider Issuer**, +and **Certificate** to **X.509 Certificate** from above. Once you've done that, press **Update** to complete the required configuration. + +![SAML Okta paste values into Infisical](../../../images/sso/okta/idp-values-2.png) + +8. Finally, navigate to the **Assignments** tab and select **Assign** + +You can assign access to the application on a user-by-user basis using the Assign to People option, or in-bulk using the Assign to Groups option. + +![SAML Okta assignment](../../../images/sso/okta/assignment.png) + +At this point, you have configured everything you need within the context of the Okta Admin Portal. + +9. Return to Infisical and enable SAML SSO. + +Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta. + +![SAML Okta assignment](../../../images/sso/okta/enable-saml.png) diff --git a/docs/documentation/platform/sso/overview.mdx b/docs/documentation/platform/sso/overview.mdx new file mode 100644 index 000000000..76f44ba41 --- /dev/null +++ b/docs/documentation/platform/sso/overview.mdx @@ -0,0 +1,18 @@ +--- +title: "SSO Overview" +description: "Log in to Infisical via SSO protocols" +--- + + + Infisical currently only supports SAML SSO authentication with Okta and Azure + AD. We're expanding support for other IdPs in the coming months, so stay tuned + with this issue [here](https://github.com/Infisical/infisical/issues/442). + + +You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0). + +To note, configuring SSO retains the end-to-end encrypted architecture of Infisical because we decouple the **authentication** and **decryption** steps. In all login with SSO implementations, +your IdP cannot and will not have access to the decryption key needed to decrypt your secrets. + +- [Okta SAML](/documentation/platform/sso/okta) +- [Azure SAML](/documentation/platform/sso/azure) diff --git a/docs/images/saml-okta-3.png b/docs/images/saml-okta-3.png deleted file mode 100644 index eccc8d277..000000000 Binary files a/docs/images/saml-okta-3.png and /dev/null differ diff --git a/docs/images/saml-okta-6.png b/docs/images/saml-okta-6.png deleted file mode 100644 index 82300afdb..000000000 Binary files a/docs/images/saml-okta-6.png and /dev/null differ diff --git a/docs/images/saml-okta-7.png b/docs/images/saml-okta-7.png deleted file mode 100644 index 2bd4a84e9..000000000 Binary files a/docs/images/saml-okta-7.png and /dev/null differ diff --git a/docs/images/sso/azure/assignment.png b/docs/images/sso/azure/assignment.png new file mode 100644 index 000000000..bf03db3e0 Binary files /dev/null and b/docs/images/sso/azure/assignment.png differ diff --git a/docs/images/sso/azure/create-own-application.png b/docs/images/sso/azure/create-own-application.png new file mode 100644 index 000000000..511073ee1 Binary files /dev/null and b/docs/images/sso/azure/create-own-application.png differ diff --git a/docs/images/sso/azure/edit-attributes-claims-2.png b/docs/images/sso/azure/edit-attributes-claims-2.png new file mode 100644 index 000000000..49939a03c Binary files /dev/null and b/docs/images/sso/azure/edit-attributes-claims-2.png differ diff --git a/docs/images/sso/azure/edit-attributes-claims.png b/docs/images/sso/azure/edit-attributes-claims.png new file mode 100644 index 000000000..50c38087c Binary files /dev/null and b/docs/images/sso/azure/edit-attributes-claims.png differ diff --git a/docs/images/sso/azure/edit-basic-config-2.png b/docs/images/sso/azure/edit-basic-config-2.png new file mode 100644 index 000000000..f18da50d2 Binary files /dev/null and b/docs/images/sso/azure/edit-basic-config-2.png differ diff --git a/docs/images/sso/azure/edit-basic-config.png b/docs/images/sso/azure/edit-basic-config.png new file mode 100644 index 000000000..0a293da7a Binary files /dev/null and b/docs/images/sso/azure/edit-basic-config.png differ diff --git a/docs/images/sso/azure/edit-saml-certificate-2.png b/docs/images/sso/azure/edit-saml-certificate-2.png new file mode 100644 index 000000000..335bf963f Binary files /dev/null and b/docs/images/sso/azure/edit-saml-certificate-2.png differ diff --git a/docs/images/sso/azure/edit-saml-certificate.png b/docs/images/sso/azure/edit-saml-certificate.png new file mode 100644 index 000000000..255d6a9c7 Binary files /dev/null and b/docs/images/sso/azure/edit-saml-certificate.png differ diff --git a/docs/images/sso/azure/enable-saml.png b/docs/images/sso/azure/enable-saml.png new file mode 100644 index 000000000..a5a09b6ea Binary files /dev/null and b/docs/images/sso/azure/enable-saml.png differ diff --git a/docs/images/sso/azure/enterprise-applications.png b/docs/images/sso/azure/enterprise-applications.png new file mode 100644 index 000000000..a400a8a27 Binary files /dev/null and b/docs/images/sso/azure/enterprise-applications.png differ diff --git a/docs/images/sso/azure/idp-values-2.png b/docs/images/sso/azure/idp-values-2.png new file mode 100644 index 000000000..e95b1781c Binary files /dev/null and b/docs/images/sso/azure/idp-values-2.png differ diff --git a/docs/images/sso/azure/idp-values.png b/docs/images/sso/azure/idp-values.png new file mode 100644 index 000000000..40bdfa194 Binary files /dev/null and b/docs/images/sso/azure/idp-values.png differ diff --git a/docs/images/sso/azure/init-config.png b/docs/images/sso/azure/init-config.png new file mode 100644 index 000000000..eb4cdad21 Binary files /dev/null and b/docs/images/sso/azure/init-config.png differ diff --git a/docs/images/sso/azure/new-application.png b/docs/images/sso/azure/new-application.png new file mode 100644 index 000000000..5f2f3342f Binary files /dev/null and b/docs/images/sso/azure/new-application.png differ diff --git a/docs/images/sso/azure/sso-method.png b/docs/images/sso/azure/sso-method.png new file mode 100644 index 000000000..629d3ac5a Binary files /dev/null and b/docs/images/sso/azure/sso-method.png differ diff --git a/docs/images/saml-okta-8.png b/docs/images/sso/okta/assignment.png similarity index 100% rename from docs/images/saml-okta-8.png rename to docs/images/sso/okta/assignment.png diff --git a/docs/images/saml-okta-4.png b/docs/images/sso/okta/attribute-statements.png similarity index 100% rename from docs/images/saml-okta-4.png rename to docs/images/sso/okta/attribute-statements.png diff --git a/docs/images/sso/okta/configure-saml.png b/docs/images/sso/okta/configure-saml.png new file mode 100644 index 000000000..c4f977fb8 Binary files /dev/null and b/docs/images/sso/okta/configure-saml.png differ diff --git a/docs/images/saml-okta-1.png b/docs/images/sso/okta/create-app-integration.png similarity index 100% rename from docs/images/saml-okta-1.png rename to docs/images/sso/okta/create-app-integration.png diff --git a/docs/images/saml-okta-2.png b/docs/images/sso/okta/create-saml-app.png similarity index 100% rename from docs/images/saml-okta-2.png rename to docs/images/sso/okta/create-saml-app.png diff --git a/docs/images/sso/okta/enable-saml.png b/docs/images/sso/okta/enable-saml.png new file mode 100644 index 000000000..2d615707d Binary files /dev/null and b/docs/images/sso/okta/enable-saml.png differ diff --git a/docs/images/sso/okta/general-settings.png b/docs/images/sso/okta/general-settings.png new file mode 100644 index 000000000..245f48df0 Binary files /dev/null and b/docs/images/sso/okta/general-settings.png differ diff --git a/docs/images/sso/okta/idp-values-2.png b/docs/images/sso/okta/idp-values-2.png new file mode 100644 index 000000000..6703f9965 Binary files /dev/null and b/docs/images/sso/okta/idp-values-2.png differ diff --git a/docs/images/saml-okta-5.png b/docs/images/sso/okta/idp-values.png similarity index 100% rename from docs/images/saml-okta-5.png rename to docs/images/sso/okta/idp-values.png diff --git a/docs/images/sso/okta/init-config.png b/docs/images/sso/okta/init-config.png new file mode 100644 index 000000000..2d65b4c58 Binary files /dev/null and b/docs/images/sso/okta/init-config.png differ diff --git a/docs/images/sso/okta/view-setup-instructions.png b/docs/images/sso/okta/view-setup-instructions.png new file mode 100644 index 000000000..59b4da358 Binary files /dev/null and b/docs/images/sso/okta/view-setup-instructions.png differ diff --git a/docs/mint.json b/docs/mint.json index 590fcd102..c229faed4 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -121,7 +121,15 @@ "documentation/platform/token", "documentation/platform/ip-allowlisting", "documentation/platform/mfa", - "documentation/platform/saml" + { + "group": "SSO", + "pages": [ + "documentation/platform/sso/overview", + "documentation/platform/sso/okta", + "documentation/platform/sso/azure", + "documentation/platform/sso/jumpcloud" + ] + } ] }, { diff --git a/frontend/src/config/request.ts b/frontend/src/config/request.ts index 2e91be462..8f8669010 100644 --- a/frontend/src/config/request.ts +++ b/frontend/src/config/request.ts @@ -4,7 +4,8 @@ import SecurityClient from "@app/components/utilities/SecurityClient"; import { getAuthToken, getMfaTempToken, - getSignupTempToken} from "@app/reactQuery"; + getSignupTempToken +} from "@app/reactQuery"; export const apiRequest = axios.create({ baseURL: "/", diff --git a/frontend/src/hooks/api/ssoConfig/queries.tsx b/frontend/src/hooks/api/ssoConfig/queries.tsx index c17c2531a..f3451060f 100644 --- a/frontend/src/hooks/api/ssoConfig/queries.tsx +++ b/frontend/src/hooks/api/ssoConfig/queries.tsx @@ -29,8 +29,7 @@ export const useCreateSSOConfig = () => { isActive, entryPoint, issuer, - cert, - audience + cert }: { organizationId: string; authProvider: string; @@ -38,7 +37,6 @@ export const useCreateSSOConfig = () => { entryPoint: string; issuer: string; cert: string; - audience: string; }) => { const { data } = await apiRequest.post( "/api/v1/sso/config", @@ -48,8 +46,7 @@ export const useCreateSSOConfig = () => { isActive, entryPoint, issuer, - cert, - audience + cert } ); @@ -70,8 +67,7 @@ export const useUpdateSSOConfig = () => { isActive, entryPoint, issuer, - cert, - audience + cert }: { organizationId: string; authProvider?: string; @@ -79,7 +75,6 @@ export const useUpdateSSOConfig = () => { entryPoint?: string; issuer?: string; cert?: string; - audience?: string; }) => { const { data } = await apiRequest.patch( "/api/v1/sso/config", @@ -89,8 +84,7 @@ export const useUpdateSSOConfig = () => { ...(isActive !== undefined ? { isActive } : {}), ...(entryPoint !== undefined ? { entryPoint } : {}), ...(issuer !== undefined ? { issuer } : {}), - ...(cert !== undefined ? { cert } : {}), - ...(audience !== undefined ? { audience } : {}) + ...(cert !== undefined ? { cert } : {}) } ); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx index 73213db31..7c694052e 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx @@ -5,6 +5,7 @@ import { useNotificationContext } from "@app/components/context/Notifications/No import { Button, Switch, UpgradePlanModal } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; import { + useCreateSSOConfig, useGetSSOConfig, useUpdateSSOConfig } from "@app/hooks/api"; @@ -13,7 +14,8 @@ import { usePopUp } from "@app/hooks/usePopUp"; import { SSOModal } from "./SSOModal"; const ssoAuthProviderMap: { [key: string]: string } = { - "okta-saml": "Okta SAML 2.0" + "okta-saml": "Okta SAML", + "azure-saml": "Azure SAML" } export const OrgSSOSection = (): JSX.Element => { @@ -27,6 +29,8 @@ export const OrgSSOSection = (): JSX.Element => { "addSSO" ] as const); + const { mutateAsync: createMutateAsync } = useCreateSSOConfig(); + const handleSamlSSOToggle = async (value: boolean) => { try { if (!currentOrg?._id) return; @@ -49,6 +53,31 @@ export const OrgSSOSection = (): JSX.Element => { } } + const addSSOBtnClick = async () => { + try { + if (subscription?.samlSSO && currentOrg) { + if (!data) { + // case: SAML SSO is not configured + // -> initialize empty SAML SSO configuration + await createMutateAsync({ + organizationId: currentOrg._id, + authProvider: "okta-saml", + isActive: false, + entryPoint: "", + issuer: "", + cert: "" + }); + } + + handlePopUpOpen("addSSO"); + } else { + handlePopUpOpen("upgradePlan"); + } + } catch (err) { + console.error(err); + } + } + return (
@@ -57,13 +86,7 @@ export const OrgSSOSection = (): JSX.Element => { {!isLoading && ( )}
- {!isLoading && data && ( - <> -
- handleSamlSSOToggle(value)} - isChecked={data.isActive} - > - Enable SAML SSO - -
-
-

SSO identifier

-

{data._id}

-
-
-

Type

-

{ssoAuthProviderMap[data.authProvider]}

-
-
-

Audience

-

{data.audience}

-
-
-

Entrypoint

-

{data.entryPoint}

-
-
-

Issuer

-

{data.issuer}

-
- + {data && ( +
+ handleSamlSSOToggle(value)} + isChecked={data ? data.isActive : false} + > + Enable SAML SSO + +
)} +
+

SSO identifier

+

{(data && data._id !== "") ? data._id : "-"}

+
+
+

Type

+

{(data && data.authProvider !== "") ? ssoAuthProviderMap[data.authProvider] : "-"}

+
+
+

Entrypoint

+

{(data && data.entryPoint !== "") ? data.entryPoint : "-"}

+
+
+

Issuer

+

{(data && data.issuer !== "") ? data.issuer : "-"}

+
; @@ -59,7 +64,7 @@ export const SSOModal = ({ watch, } = useForm({ defaultValues: { - authProvider: "okta-saml" + authProvider: AuthProvider.OKTA_SAML }, resolver: yupResolver(schema) }); @@ -70,8 +75,7 @@ export const SSOModal = ({ authProvider: data?.authProvider ?? "", entryPoint: data?.entryPoint ?? "", issuer: data?.issuer ?? "", - cert: data?.cert ?? "", - audience: data?.audience ?? "" + cert: data?.cert ?? "" }); } }, [data]); @@ -80,8 +84,7 @@ export const SSOModal = ({ authProvider, entryPoint, issuer, - cert, - audience + cert }: AddSSOFormData) => { try { if (!currentOrg) return; @@ -93,8 +96,7 @@ export const SSOModal = ({ isActive: false, entryPoint, issuer, - cert, - audience + cert }); } else { await updateMutateAsync({ @@ -103,8 +105,7 @@ export const SSOModal = ({ isActive: false, entryPoint, issuer, - cert, - audience + cert }); } @@ -123,6 +124,38 @@ export const SSOModal = ({ } } + const renderLabels = (authProvider: string) => { + switch (authProvider){ + case AuthProvider.OKTA_SAML: + return ({ + acsUrl: "Single sign-on URL", + entityId: "Audience URI (SP Entity ID)", + entryPoint: "Identity Provider Single Sign-On URL", + entryPointPlaceholder: "https://your-domain.okta.com/app/app-name/xxx/sso/saml", + issuer: "Identity Provider Issuer", + issuerPlaceholder: "http://www.okta.com/xxx" + }); + case AuthProvider.AZURE_SAML: + return ({ + acsUrl: "Reply URL (Assertion Consumer Service URL)", + entityId: "Identifier (Entity ID)", + entryPoint: "Login URL", + entryPointPlaceholder: "https://login.microsoftonline.com/xxx/saml2", + issuer: "Azure AD Identifier", + issuerPlaceholder: "https://sts.windows.net/xxx/" + }); + default: + return ({ + acsUrl: "ACS URL", + entityId: "Entity ID", + entryPoint: "Entrypoint", + entryPointPlaceholder: "Enter entrypoint...", + issuer: "Issuer", + issuerPlaceholder: "Enter placeholder..." + }); + } + } + const authProvider = watch("authProvider"); return ( @@ -160,36 +193,28 @@ export const SSOModal = ({ )} /> - {authProvider && authProvider === "okta-saml" && ( + {authProvider && data && ( <> - ( - - - - )} - /> +
+

{renderLabels(authProvider).acsUrl}

+

{`${window.origin}/api/v1/sso/saml2/${data._id}`}

+
+
+

{renderLabels(authProvider).entityId}

+

{window.origin}

+
( )} @@ -199,13 +224,13 @@ export const SSOModal = ({ name="issuer" render={({ field, fieldState: { error } }) => ( )}