mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 03:27:15 +00:00
Add Google SSO
This commit is contained in:
Generated
+1866
-1797
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,30 @@ import {
|
|||||||
getSiteURL
|
getSiteURL
|
||||||
} from "../../../config";
|
} from "../../../config";
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/redirect/google",
|
||||||
|
authLimiter,
|
||||||
|
passport.authenticate("google", {
|
||||||
|
scope: ["profile", "email"],
|
||||||
|
session: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/google",
|
||||||
|
passport.authenticate("google", {
|
||||||
|
failureRedirect: "/login/provider/error",
|
||||||
|
session: false
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
if (req.isUserCompleted) {
|
||||||
|
res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||||
|
} else {
|
||||||
|
res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/redirect/saml2/:ssoIdentifier",
|
"/redirect/saml2/:ssoIdentifier",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
@@ -32,7 +56,7 @@ router.post("/saml2/:ssoIdentifier",
|
|||||||
failureFlash: true,
|
failureFlash: true,
|
||||||
session: false
|
session: false
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => { // TODO: move this into controller
|
||||||
if (req.isUserCompleted) {
|
if (req.isUserCompleted) {
|
||||||
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ interface FeatureSet {
|
|||||||
customRateLimits: boolean;
|
customRateLimits: boolean;
|
||||||
customAlerts: boolean;
|
customAlerts: boolean;
|
||||||
auditLogs: boolean;
|
auditLogs: boolean;
|
||||||
|
samlSSO: boolean;
|
||||||
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
|
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
|
||||||
trial_end: number | null;
|
trial_end: number | null;
|
||||||
has_used_trial: boolean;
|
has_used_trial: boolean;
|
||||||
@@ -63,6 +64,7 @@ class EELicenseService {
|
|||||||
customRateLimits: true,
|
customRateLimits: true,
|
||||||
customAlerts: true,
|
customAlerts: true,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
|
samlSSO: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true
|
has_used_trial: true
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from "express-validator";
|
import { body } from "express-validator";
|
||||||
import passport from "passport";
|
|
||||||
import { requireAuth, validateRequest } from "../../middleware";
|
import { requireAuth, validateRequest } from "../../middleware";
|
||||||
import { authController } from "../../controllers/v1";
|
import { authController } from "../../controllers/v1";
|
||||||
import { authLimiter } from "../../helpers/rateLimiter";
|
import { authLimiter } from "../../helpers/rateLimiter";
|
||||||
@@ -44,21 +43,6 @@ router.post(
|
|||||||
authController.checkAuth
|
authController.checkAuth
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/redirect/google",
|
|
||||||
authLimiter,
|
|
||||||
passport.authenticate("google", {
|
|
||||||
scope: ["profile", "email"],
|
|
||||||
session: false,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/callback/google",
|
|
||||||
passport.authenticate("google", { failureRedirect: "/login/provider/error", session: false }),
|
|
||||||
authController.handleAuthProviderCallback,
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/common-passwords",
|
"/common-passwords",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
|
|||||||
+33
-16
@@ -18,16 +18,15 @@ import {
|
|||||||
getJwtProviderAuthSecret,
|
getJwtProviderAuthSecret,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { getSSOConfigHelper } from "../ee/helpers/organizations";
|
import { getSSOConfigHelper } from "../ee/helpers/organizations";
|
||||||
import { OrganizationNotFoundError } from "./errors";
|
import { OrganizationNotFoundError, InternalServerError } from "./errors";
|
||||||
import { MEMBER, INVITED } from "../variables";
|
import { MEMBER, INVITED } from "../variables";
|
||||||
|
import { getSiteURL } from "../config";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const GoogleStrategy = require("passport-google-oauth20").Strategy;
|
const GoogleStrategy = require("passport-google-oauth20").Strategy;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { MultiSamlStrategy } = require("@node-saml/passport-saml");
|
const { MultiSamlStrategy } = require("@node-saml/passport-saml");
|
||||||
|
|
||||||
// TODO: find a more optimal folder structure to store these types of functions
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object containing the id of the authentication data payload
|
* Returns an object containing the id of the authentication data payload
|
||||||
* @param {AuthData} authData - authentication data object
|
* @param {AuthData} authData - authentication data object
|
||||||
@@ -75,45 +74,57 @@ const initializePassport = async () => {
|
|||||||
passReqToCallback: true,
|
passReqToCallback: true,
|
||||||
clientID: googleClientId,
|
clientID: googleClientId,
|
||||||
clientSecret: googleClientSecret,
|
clientSecret: googleClientSecret,
|
||||||
callbackURL: "/api/v1/auth/callback/google",
|
callbackURL: "/api/v1/sso/google",
|
||||||
scope: ["profile", " email"],
|
scope: ["profile", " email"],
|
||||||
}, async (
|
}, async (
|
||||||
req: express.Request,
|
req: express.Request,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
profile: any,
|
profile: any,
|
||||||
cb: any
|
done: any
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const email = profile.emails[0].value;
|
const email = profile.emails[0].value;
|
||||||
|
const firstName = profile.name.givenName;
|
||||||
|
const lastName = profile.name.familyName;
|
||||||
|
|
||||||
let user = await User.findOne({
|
let user = await User.findOne({
|
||||||
authProvider: AuthProvider.GOOGLE,
|
email
|
||||||
authId: profile.id,
|
}).select("+publicKey");
|
||||||
}).select("+publicKey")
|
|
||||||
|
if (user && user.authProvider !== AuthProvider.GOOGLE) {
|
||||||
|
done(InternalServerError());
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await new User({
|
user = await new User({
|
||||||
email,
|
email,
|
||||||
authProvider: AuthProvider.GOOGLE,
|
authProvider: AuthProvider.GOOGLE,
|
||||||
authId: profile.id,
|
authId: profile.id,
|
||||||
|
firstName,
|
||||||
|
lastName
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isUserCompleted = !!user.publicKey;
|
||||||
const providerAuthToken = createToken({
|
const providerAuthToken = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
userId: user._id.toString(),
|
userId: user._id.toString(),
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
authProvider: user.authProvider,
|
authProvider: user.authProvider,
|
||||||
isUserCompleted: !!user.publicKey,
|
isUserCompleted
|
||||||
},
|
},
|
||||||
expiresIn: await getJwtProviderAuthLifetime(),
|
expiresIn: await getJwtProviderAuthLifetime(),
|
||||||
secret: await getJwtProviderAuthSecret(),
|
secret: await getJwtProviderAuthSecret(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
req.isUserCompleted = isUserCompleted;
|
||||||
req.providerAuthToken = providerAuthToken;
|
req.providerAuthToken = providerAuthToken;
|
||||||
cb(null, profile);
|
done(null, profile);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
cb(null, false);
|
done(null, false);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -129,7 +140,7 @@ const initializePassport = async () => {
|
|||||||
|
|
||||||
const samlConfig = ({
|
const samlConfig = ({
|
||||||
path: "/api/v1/auth/callback/saml",
|
path: "/api/v1/auth/callback/saml",
|
||||||
callbackURL: "http://localhost:8080/api/v1/auth/callback/saml", // TODO: get rid of localhost:8080 here
|
callbackURL: `${await getSiteURL()}/api/v1/auth/callback/saml`,
|
||||||
entryPoint: ssoConfig.entryPoint,
|
entryPoint: ssoConfig.entryPoint,
|
||||||
issuer: ssoConfig.issuer,
|
issuer: ssoConfig.issuer,
|
||||||
cert: ssoConfig.cert,
|
cert: ssoConfig.cert,
|
||||||
@@ -143,23 +154,29 @@ const initializePassport = async () => {
|
|||||||
},
|
},
|
||||||
async (req: any, profile: any, done: any) => {
|
async (req: any, profile: any, done: any) => {
|
||||||
|
|
||||||
|
if (!req.ssoConfig.isActive) return done(InternalServerError());
|
||||||
|
|
||||||
const organization = await Organization.findById(req.ssoConfig.organization);
|
const organization = await Organization.findById(req.ssoConfig.organization);
|
||||||
|
|
||||||
if (!organization) done(OrganizationNotFoundError());
|
if (!organization) return done(OrganizationNotFoundError());
|
||||||
|
|
||||||
const email = profile.email;
|
const email = profile.email;
|
||||||
const firstName = profile.firstName;
|
const firstName = profile.firstName;
|
||||||
const lastName = profile.lastName;
|
const lastName = profile.lastName;
|
||||||
|
|
||||||
let user = await User.findOne({
|
let user = await User.findOne({
|
||||||
authProvider: AuthProvider.OKTA_SAML,
|
|
||||||
email
|
email
|
||||||
}).select("+publicKey");
|
}).select("+publicKey");
|
||||||
|
|
||||||
|
if (user && user.authProvider !== AuthProvider.OKTA_SAML) {
|
||||||
|
done(InternalServerError());
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await new User({
|
user = await new User({
|
||||||
email,
|
email,
|
||||||
authProvider: AuthProvider.OKTA_SAML,
|
authProvider: AuthProvider.OKTA_SAML,
|
||||||
|
authId: profile.id,
|
||||||
firstName,
|
firstName,
|
||||||
lastName
|
lastName
|
||||||
}).save();
|
}).save();
|
||||||
@@ -178,8 +195,8 @@ const initializePassport = async () => {
|
|||||||
payload: {
|
payload: {
|
||||||
userId: user._id.toString(),
|
userId: user._id.toString(),
|
||||||
email: user.email,
|
email: user.email,
|
||||||
firstName: user.firstName,
|
firstName,
|
||||||
lastName: user.lastName,
|
lastName,
|
||||||
organizationName: organization?.name,
|
organizationName: organization?.name,
|
||||||
authProvider: user.authProvider,
|
authProvider: user.authProvider,
|
||||||
isUserCompleted
|
isUserCompleted
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { faGoogle } from '@fortawesome/free-brands-svg-icons';
|
||||||
// import { faGoogle } from '@fortawesome/free-brands-svg-icons';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { Button } from "../v2";
|
import { Button } from "../v2";
|
||||||
|
|
||||||
export default function InitialSignupStep({
|
export default function InitialSignupStep({
|
||||||
@@ -16,19 +15,6 @@ export default function InitialSignupStep({
|
|||||||
|
|
||||||
return <div className='flex flex-col mx-auto w-full justify-center items-center'>
|
return <div className='flex flex-col mx-auto w-full justify-center items-center'>
|
||||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >{t("signup.initial-title")}</h1>
|
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >{t("signup.initial-title")}</h1>
|
||||||
{/* <div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
|
||||||
<Button
|
|
||||||
colorSchema="primary"
|
|
||||||
variant="solid"
|
|
||||||
onClick={() => {
|
|
||||||
window.open('/api/v1/auth/redirect/google')
|
|
||||||
}}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
|
||||||
className="h-14 w-full mx-0"
|
|
||||||
>
|
|
||||||
{t('signup.continue-with-google')}
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md'>
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md'>
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
@@ -42,6 +28,25 @@ export default function InitialSignupStep({
|
|||||||
Sign Up with email
|
Sign Up with email
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center my-4 py-2'>
|
||||||
|
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||||
|
<span className='px-4 text-sm text-bunker-400'>or</span>
|
||||||
|
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||||
|
</div>
|
||||||
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="solid"
|
||||||
|
onClick={() => {
|
||||||
|
window.open('/api/v1/sso/redirect/google');
|
||||||
|
window.close();
|
||||||
|
}}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
||||||
|
className="h-14 w-full mx-0"
|
||||||
|
>
|
||||||
|
{t('signup.continue-with-google')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export type SubscriptionPlan = {
|
|||||||
workspaceLimit: number;
|
workspaceLimit: number;
|
||||||
workspacesUsed: number;
|
workspacesUsed: number;
|
||||||
environmentLimit: number;
|
environmentLimit: number;
|
||||||
|
samlSSO: boolean;
|
||||||
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
|
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
|
||||||
trial_end: number | null;
|
trial_end: number | null;
|
||||||
has_used_trial: boolean;
|
has_used_trial: boolean;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { FormEvent, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
|
|
||||||
import Error from "@app/components/basic/Error";
|
import Error from "@app/components/basic/Error";
|
||||||
@@ -115,20 +115,6 @@ export const InitialStep = ({
|
|||||||
return (
|
return (
|
||||||
<form onSubmit={handleLogin} className='flex flex-col mx-auto w-full justify-center items-center'>
|
<form onSubmit={handleLogin} className='flex flex-col mx-auto w-full justify-center items-center'>
|
||||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
|
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
|
||||||
{/* <Button
|
|
||||||
colorSchema="primary"
|
|
||||||
variant="solid"
|
|
||||||
onClick={() => {
|
|
||||||
// window.open('/api/v1/auth/redirect/google')
|
|
||||||
window.open('/api/v1/auth/redirect/okta/64b4b9166e76604655b5373e');
|
|
||||||
}}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
|
||||||
className="h-14 w-full mx-0"
|
|
||||||
>
|
|
||||||
{t('login.continue-with-google')}
|
|
||||||
</Button> */}
|
|
||||||
</div>
|
|
||||||
<div className="relative md:px-1.5 flex items-center justify-center lg:w-1/6 w-1/4 min-w-[21.3rem] md:min-w-[22rem] mx-auto rounded-lg max-h-24 md:max-h-28">
|
<div className="relative md:px-1.5 flex items-center justify-center lg:w-1/6 w-1/4 min-w-[21.3rem] md:min-w-[22rem] mx-auto rounded-lg max-h-24 md:max-h-28">
|
||||||
<div className="flex items-center justify-center w-full md:px-2 md:py-1 rounded-lg max-h-24 md:max-h-28">
|
<div className="flex items-center justify-center w-full md:px-2 md:py-1 rounded-lg max-h-24 md:max-h-28">
|
||||||
<Input
|
<Input
|
||||||
@@ -168,11 +154,25 @@ export const InitialStep = ({
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
> Login </Button>
|
> Login </Button>
|
||||||
</div>
|
</div>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center mt-4 py-2'>
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center my-4 py-2'>
|
||||||
<div className='w-1/2 border-t border-mineshaft-500' />
|
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||||
<span className='px-4 text-sm text-bunker-400'>or</span>
|
<span className='px-4 text-sm text-bunker-400'>or</span>
|
||||||
<div className='w-1/2 border-t border-mineshaft-500' />
|
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||||
</div>
|
</div>
|
||||||
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="solid"
|
||||||
|
onClick={() => {
|
||||||
|
window.open('/api/v1/sso/redirect/google');
|
||||||
|
window.close();
|
||||||
|
}}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
||||||
|
className="h-14 w-full mx-0"
|
||||||
|
>
|
||||||
|
{t('login.continue-with-google')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
|
|||||||
+16
-5
@@ -1,15 +1,13 @@
|
|||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Button , Switch } from "@app/components/v2";
|
import { Button, Switch, UpgradePlanModal } from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { useOrganization, useSubscription } from "@app/context";
|
||||||
import {
|
import {
|
||||||
useGetSSOConfig,
|
useGetSSOConfig,
|
||||||
useUpdateSSOConfig
|
useUpdateSSOConfig
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { SSOModal } from "./SSOModal";
|
import { SSOModal } from "./SSOModal";
|
||||||
|
|
||||||
const ssoAuthProviderMap: { [key: string]: string } = {
|
const ssoAuthProviderMap: { [key: string]: string } = {
|
||||||
@@ -18,10 +16,12 @@ const ssoAuthProviderMap: { [key: string]: string } = {
|
|||||||
|
|
||||||
export const OrgSSOSection = (): JSX.Element => {
|
export const OrgSSOSection = (): JSX.Element => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
const { subscription } = useSubscription();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const { data, isLoading } = useGetSSOConfig(currentOrg?._id ?? "");
|
const { data, isLoading } = useGetSSOConfig(currentOrg?._id ?? "");
|
||||||
const { mutateAsync } = useUpdateSSOConfig();
|
const { mutateAsync } = useUpdateSSOConfig();
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"upgradePlan",
|
||||||
"addSSO"
|
"addSSO"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
@@ -55,7 +55,13 @@ export const OrgSSOSection = (): JSX.Element => {
|
|||||||
</h2>
|
</h2>
|
||||||
{!isLoading && (
|
{!isLoading && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handlePopUpOpen("addSSO")}
|
onClick={() => {
|
||||||
|
if (subscription?.samlSSO) {
|
||||||
|
handlePopUpOpen("addSSO");
|
||||||
|
} else {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
}
|
||||||
|
}}
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
>
|
>
|
||||||
@@ -101,6 +107,11 @@ export const OrgSSOSection = (): JSX.Element => {
|
|||||||
handlePopUpClose={handlePopUpClose}
|
handlePopUpClose={handlePopUpClose}
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text="You can use SAML SSO if you switch to Infisical's Pro plan."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,15 +1,31 @@
|
|||||||
import { Fragment } from "react"
|
import { Fragment } from "react"
|
||||||
import { Tab } from "@headlessui/react"
|
import { Tab } from "@headlessui/react"
|
||||||
|
|
||||||
import { OrgAuthTab } from "../OrgAuthTab";
|
import { OrgAuthTab } from "../OrgAuthTab";
|
||||||
import { OrgGeneralTab } from "../OrgGeneralTab";
|
import { OrgGeneralTab } from "../OrgGeneralTab";
|
||||||
|
import { useUser, useOrganization } from "@app/context";
|
||||||
const tabs = [
|
import {
|
||||||
{ name: "General", key: "tab-org-general" },
|
useGetOrgUsers
|
||||||
{ name: "SAML SSO", key: "tab-org-saml" }
|
} from "@app/hooks/api";
|
||||||
];
|
|
||||||
|
|
||||||
export const OrgTabGroup = () => {
|
export const OrgTabGroup = () => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { user } = useUser();
|
||||||
|
const { data } = useGetOrgUsers(currentOrg?._id ?? "");
|
||||||
|
|
||||||
|
const isRoleSufficient = data?.some((orgUser) => {
|
||||||
|
return orgUser.role !== 'member' && orgUser.user._id === user._id;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ name: "General", key: "tab-org-general" },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isRoleSufficient) {
|
||||||
|
tabs.push(
|
||||||
|
{ name: "SAML SSO", key: "tab-org-saml" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="mb-6 border-b-2 border-mineshaft-800 w-full">
|
<Tab.List className="mb-6 border-b-2 border-mineshaft-800 w-full">
|
||||||
@@ -30,9 +46,11 @@ export const OrgTabGroup = () => {
|
|||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<OrgGeneralTab />
|
<OrgGeneralTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
<Tab.Panel>
|
{isRoleSufficient && (
|
||||||
<OrgAuthTab />
|
<Tab.Panel>
|
||||||
</Tab.Panel>
|
<OrgAuthTab />
|
||||||
|
</Tab.Panel>
|
||||||
|
)}
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const SignupSSO = ({
|
|||||||
<UserInfoSSOStep
|
<UserInfoSSOStep
|
||||||
email={email}
|
email={email}
|
||||||
name={`${firstName} ${lastName}`}
|
name={`${firstName} ${lastName}`}
|
||||||
organizationName={organizationName}
|
providerOrganizationName={organizationName}
|
||||||
password={password}
|
password={password}
|
||||||
setPassword={setPassword}
|
setPassword={setPassword}
|
||||||
setStep={setStep}
|
setStep={setStep}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
@@ -30,7 +30,7 @@ type Props = {
|
|||||||
password: string;
|
password: string;
|
||||||
setPassword: (value: string) => void;
|
setPassword: (value: string) => void;
|
||||||
name: string;
|
name: string;
|
||||||
organizationName: string;
|
providerOrganizationName: string;
|
||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ type Errors = {
|
|||||||
export const UserInfoSSOStep = ({
|
export const UserInfoSSOStep = ({
|
||||||
email,
|
email,
|
||||||
name,
|
name,
|
||||||
organizationName,
|
providerOrganizationName,
|
||||||
password,
|
password,
|
||||||
setPassword,
|
setPassword,
|
||||||
setStep,
|
setStep,
|
||||||
@@ -67,11 +67,19 @@ export const UserInfoSSOStep = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { data: commonPasswords } = useGetCommonPasswords();
|
const { data: commonPasswords } = useGetCommonPasswords();
|
||||||
const [nameError, setNameError] = useState(false);
|
const [nameError, setNameError] = useState(false);
|
||||||
|
const [organizationName, setOrganizationName] = useState("");
|
||||||
const [organizationNameError, setOrganizationNameError] = useState(false);
|
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||||
const [errors, setErrors] = useState<Errors>({});
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("providerOrganizationName: ", providerOrganizationName);
|
||||||
|
if (providerOrganizationName) {
|
||||||
|
setOrganizationName(providerOrganizationName);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Verifies if the information that the users entered (name, workspace)
|
// Verifies if the information that the users entered (name, workspace)
|
||||||
// is there, and if the password matches the criteria.
|
// is there, and if the password matches the criteria.
|
||||||
const signupErrorCheck = async () => {
|
const signupErrorCheck = async () => {
|
||||||
@@ -105,6 +113,10 @@ export const UserInfoSSOStep = ({
|
|||||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||||
localStorage.setItem("PRIVATE_KEY", privateKey);
|
localStorage.setItem("PRIVATE_KEY", privateKey);
|
||||||
|
|
||||||
|
console.log("make");
|
||||||
|
console.log("email: ", email);
|
||||||
|
console.log("password: ", password);
|
||||||
|
|
||||||
client.init(
|
client.init(
|
||||||
{
|
{
|
||||||
username: email,
|
username: email,
|
||||||
@@ -225,6 +237,7 @@ export const UserInfoSSOStep = ({
|
|||||||
<Input
|
<Input
|
||||||
placeholder="Infisical"
|
placeholder="Infisical"
|
||||||
value={organizationName}
|
value={organizationName}
|
||||||
|
onChange={(e) => setOrganizationName(e.target.value)}
|
||||||
isRequired
|
isRequired
|
||||||
className="h-12"
|
className="h-12"
|
||||||
disabled
|
disabled
|
||||||
|
|||||||
Reference in New Issue
Block a user