diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 655081f84..2e686c260 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -36,7 +36,6 @@ export const getClientIdVercel = async () => (await client.getSecret("CLIENT_ID_ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; -export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue; export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue; export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue; export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; @@ -44,9 +43,14 @@ export const getClientSecretVercel = async () => (await client.getSecret("CLIENT export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; -export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue; export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue; export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue; + +export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; +export const getClientSecretGoogleLogin = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue; +export const getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue; +export const getClientSecretGitHubLogin = async () => (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue; + export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com"; export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE"; export const getSentryDSN = async () => (await client.getSecret("SENTRY_DSN")).secretValue; diff --git a/backend/src/controllers/v2/usersController.ts b/backend/src/controllers/v2/usersController.ts index 237d32671..66a48b550 100644 --- a/backend/src/controllers/v2/usersController.ts +++ b/backend/src/controllers/v2/usersController.ts @@ -123,9 +123,15 @@ export const updateAuthProvider = async (req: Request, res: Response) => { authProvider } = req.body; - if (req.user?.authProvider === AuthProvider.OKTA_SAML) return res.status(400).send({ - message: "Failed to update user authentication method because SAML SSO is enforced" - }); + if ( + req.user?.authProvider === AuthProvider.OKTA_SAML + || req.user?.authProvider === AuthProvider.AZURE_SAML + || req.user?.authProvider === AuthProvider.JUMPCLOUD_SAML + ) { + return res.status(400).send({ + message: "Failed to update user authentication method because SAML SSO is enforced" + }); + } const user = await User.findByIdAndUpdate( req.user._id.toString(), diff --git a/backend/src/ee/routes/v1/sso.ts b/backend/src/ee/routes/v1/sso.ts index b54aa5942..f76f19e7d 100644 --- a/backend/src/ee/routes/v1/sso.ts +++ b/backend/src/ee/routes/v1/sso.ts @@ -41,6 +41,29 @@ router.get( ssoController.redirectSSO ); +router.get( + "/redirect/github", + authLimiter, + (req, res, next) => { + passport.authenticate("github", { + session: false, + ...(req.query.callback_port ? { + state: req.query.callback_port as string + } : {}) + })(req, res, next); + } +); + +router.get( + "/github", + authLimiter, + passport.authenticate("github", { + failureRedirect: "/login/provider/error", + session: false + }), + ssoController.redirectSSO +); + router.get( "/redirect/saml2/:ssoIdentifier", authLimiter, diff --git a/backend/src/routes/v1/auth.ts b/backend/src/routes/v1/auth.ts index f6d90c804..ae85aa36c 100644 --- a/backend/src/routes/v1/auth.ts +++ b/backend/src/routes/v1/auth.ts @@ -1,66 +1,10 @@ import express from "express"; const router = express.Router(); import { body } from "express-validator"; -import passport from "passport"; import { requireAuth, validateRequest } from "../../middleware"; import { authController } from "../../controllers/v1"; import { authLimiter } from "../../helpers/rateLimiter"; import { AUTH_MODE_JWT } from "../../variables"; -import { User, AuthProvider } from '../../models'; -import { createToken } from '../../helpers/auth'; -import { getJwtProviderAuthLifetime, getJwtProviderAuthSecret } from '../../config'; -import { ssoController } from "../../ee/controllers/v1"; - -var GitHubStrategy = require('passport-github').Strategy; -passport.use(new GitHubStrategy({ - passReqToCallback: true, - clientID: process.env['CLIENT_ID_GITHUB_LOGIN'], - clientSecret: process.env['CLIENT_SECRET_GITHUB'], - callbackURL: "/api/v1/auth/github/callback" -}, -async (req : express.Request, accessToken : any, refreshToken : any, profile : any, cb : any) => { - const email = profile.emails[0].value; - let user = await User.findOne({ - email - }); - if (!user) { - user = await new User({ - email: email, - authProvider: AuthProvider.GITHUB, - authId: profile.id, - firstName: profile.displayName, - }).save(); - } - const isUserCompleted = true; - const providerAuthToken = createToken({ - payload: { - userId: profile.id.toString(), - email: email, - isUserCompleted, - ...(req.query.state ? { - callbackPort: req.query.state as string - } : {}) - }, - expiresIn: await getJwtProviderAuthLifetime(), - secret: await getJwtProviderAuthSecret(), - }); - req.isUserCompleted = isUserCompleted; - req.providerAuthToken = providerAuthToken; - return cb(null, profile); -} -)); - -router.get('/github', passport.authenticate('github', { failureRedirect: '/login/provider/error', session: false }), ssoController.redirectSSO); - -router.get('/github/callback', -passport.authenticate('github', { failureRedirect: '/login/provider/error', session: false } -), -function(req, res, next) { - // Successful authentication, redirect home. - console.log("github success"); - res.redirect(`/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`); - next(); -}); router.post("/token", validateRequest, authController.getNewToken); diff --git a/backend/src/routes/v2/users.ts b/backend/src/routes/v2/users.ts index 94d912b48..334ef523b 100644 --- a/backend/src/routes/v2/users.ts +++ b/backend/src/routes/v2/users.ts @@ -50,7 +50,8 @@ router.patch( }), body("authProvider").exists().isString().isIn([ AuthProvider.EMAIL, - AuthProvider.GOOGLE + AuthProvider.GOOGLE, + AuthProvider.GITHUB ]), validateRequest, usersController.updateAuthProvider diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 2dc652460..8faba80bf 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -12,8 +12,10 @@ import { } from "../models"; import { createToken } from "../helpers/auth"; import { - getClientIdGoogle, - getClientSecretGoogle, + getClientIdGitHubLogin, + getClientIdGoogleLogin, + getClientSecretGitHubLogin, + getClientSecretGoogleLogin, getJwtProviderAuthLifetime, getJwtProviderAuthSecret, } from "../config"; @@ -25,6 +27,8 @@ import { getSiteURL } from "../config"; // eslint-disable-next-line @typescript-eslint/no-var-requires const GoogleStrategy = require("passport-google-oauth20").Strategy; // eslint-disable-next-line @typescript-eslint/no-var-requires +const GitHubStrategy = require("passport-github").Strategy; +// eslint-disable-next-line @typescript-eslint/no-var-requires const { MultiSamlStrategy } = require("@node-saml/passport-saml"); /** @@ -67,42 +71,97 @@ const getAuthDataPayloadUserObj = (authData: AuthData) => { } const initializePassport = async () => { - const googleClientSecret = await getClientSecretGoogle(); - const googleClientId = await getClientIdGoogle(); + const clientIdGoogleLogin = await getClientIdGoogleLogin(); + const clientSecretGoogleLogin = await getClientSecretGoogleLogin(); + const clientIdGitHubLogin = await getClientIdGitHubLogin(); + const clientSecretGitHubLogin = await getClientSecretGitHubLogin(); - passport.use(new GoogleStrategy({ - passReqToCallback: true, - clientID: googleClientId, - clientSecret: googleClientSecret, - callbackURL: "/api/v1/sso/google", - scope: ["profile", " email"], - }, async ( - req: express.Request, - accessToken: string, - refreshToken: string, - profile: any, - done: any - ) => { - try { + if (clientIdGoogleLogin && clientSecretGoogleLogin) { + passport.use(new GoogleStrategy({ + passReqToCallback: true, + clientID: clientIdGoogleLogin, + clientSecret: clientSecretGoogleLogin, + callbackURL: "/api/v1/sso/google", + scope: ["profile", " email"], + }, async ( + req: express.Request, + accessToken: string, + refreshToken: string, + profile: any, + done: any + ) => { + try { + const email = profile.emails[0].value; + + let user = await User.findOne({ + email + }).select("+publicKey"); + + if (user && user.authProvider !== AuthProvider.GOOGLE) { + done(InternalServerError()); + } + + if (!user) { + user = await new User({ + email, + authProvider: AuthProvider.GOOGLE, + authId: profile.id, + firstName: profile.name.givenName, + lastName: profile.name.familyName + }).save(); + } + + const isUserCompleted = !!user.publicKey; + const providerAuthToken = createToken({ + payload: { + userId: user._id.toString(), + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + authProvider: user.authProvider, + isUserCompleted, + ...(req.query.state ? { + callbackPort: req.query.state as string + } : {}) + }, + expiresIn: await getJwtProviderAuthLifetime(), + secret: await getJwtProviderAuthSecret(), + }); + + req.isUserCompleted = isUserCompleted; + req.providerAuthToken = providerAuthToken; + done(null, profile); + } catch (err) { + done(null, false); + } + })); + } + + if (clientIdGitHubLogin && clientSecretGitHubLogin) { + passport.use(new GitHubStrategy({ + passReqToCallback: true, + clientID: clientIdGitHubLogin, + clientSecret: clientSecretGitHubLogin, + callbackURL: "/api/v1/sso/github" + }, + async (req : express.Request, accessToken : any, refreshToken : any, profile : any, done : any) => { const email = profile.emails[0].value; - const firstName = profile.name.givenName; - const lastName = profile.name.familyName; let user = await User.findOne({ email }).select("+publicKey"); - if (user && user.authProvider !== AuthProvider.GOOGLE) { + if (user && user.authProvider !== AuthProvider.GITHUB) { done(InternalServerError()); } - + if (!user) { user = await new User({ - email, - authProvider: AuthProvider.GOOGLE, + email: email, + authProvider: AuthProvider.GITHUB, authId: profile.id, - firstName, - lastName + firstName: profile.displayName, + lastName: "" }).save(); } @@ -111,8 +170,8 @@ const initializePassport = async () => { payload: { userId: user._id.toString(), email: user.email, - firstName, - lastName, + firstName: user.firstName, + lastName: user.lastName, authProvider: user.authProvider, isUserCompleted, ...(req.query.state ? { @@ -125,11 +184,10 @@ const initializePassport = async () => { req.isUserCompleted = isUserCompleted; req.providerAuthToken = providerAuthToken; - done(null, profile); - } catch (err) { - done(null, false); + return done(null, profile); } - })); + )); + } passport.use("saml", new MultiSamlStrategy( { diff --git a/backend/src/utils/setup/index.ts b/backend/src/utils/setup/index.ts index 2f88b2913..aa1e22f90 100644 --- a/backend/src/utils/setup/index.ts +++ b/backend/src/utils/setup/index.ts @@ -24,8 +24,6 @@ import { reencryptSecretBlindIndexDataSalts } from "./reencryptData"; import { - getClientIdGoogle, - getClientSecretGoogle, getMongoURL, getNodeEnv, getSentryDSN @@ -55,12 +53,7 @@ export const setup = async () => { // initializing the database connection await DatabaseService.initDatabase(await getMongoURL()); - const googleClientSecret: string = await getClientSecretGoogle(); - const googleClientId: string = await getClientIdGoogle(); - - if (googleClientId && googleClientSecret) { - await initializePassport(); - } + await initializePassport(); // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // to base64 256-bit ROOT_ENCRYPTION_KEY diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 1db882260..d355140b6 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -5,7 +5,7 @@ description: "Configure your environment variables when self-hosting Infisical." ## Backend environment variables -Depending on your choosen self hosted deployment method, you may need to configured at least the required environment variable listed below. +Depending on your choosen self hosted deployment method, you may need to configured at least the required environment variable listed below. Other environment variables are listed below to increase the functionality of your self hosted instance based on your use case. @@ -14,25 +14,40 @@ Other environment variables are listed below to increase the functionality of yo Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` - - Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` - +{" "} - - Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` - + + Must be a random 16 byte hex string. Can be generated with `openssl rand -hex + 16` + - - Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` - +{" "} - - Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` - + + Must be a random 16 byte hex string. Can be generated with `openssl rand -hex + 16` + - - Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` - +{" "} + + + Must be a random 16 byte hex string. Can be generated with `openssl rand -hex + 16` + + +{" "} + + + Must be a random 16 byte hex string. Can be generated with `openssl rand -hex + 16` + + +{" "} + + + Must be a random 16 byte hex string. Can be generated with `openssl rand -hex + 16` + *TLS based connection string is not yet supported @@ -58,7 +73,7 @@ Other environment variables are listed below to increase the functionality of yo - If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported + If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported @@ -68,9 +83,10 @@ Other environment variables are listed below to increase the functionality of yo Name label to be used in From field (e.g. Team) + - To sync secret to third party services, provide value for the related services + To sync secret to third party services, provide value for the related services OAuth2 client ID for Heroku integration @@ -81,7 +97,7 @@ Other environment variables are listed below to increase the functionality of yo - OAuth2 client ID for Vercel integration + OAuth2 client ID for Vercel integration @@ -89,7 +105,7 @@ Other environment variables are listed below to increase the functionality of yo - OAuth2 client ID for Netlify integration + OAuth2 client ID for Netlify integration @@ -97,7 +113,7 @@ Other environment variables are listed below to increase the functionality of yo - OAuth2 client ID for GitHub integration + OAuth2 client ID for GitHub integration @@ -109,23 +125,30 @@ Other environment variables are listed below to increase the functionality of yo - OAuth2 client ID for BitBucket integration + OAuth2 client ID for BitBucket integration OAuth2 client secret for BitBucket integration + To integrate with external auth providers, provide value for the related keys Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` - - OAuth2 client ID for Google auth integration + + OAuth2 client ID for Google login - - OAuth2 client secret for Google auth integration + + OAuth2 client secret for Google login + + + OAuth2 client ID for GitHub login + + + OAuth2 client secret for GitHub login @@ -150,18 +173,44 @@ Other environment variables are listed below to increase the functionality of yo JWT token lifetime expressed in seconds or a string describing a time span - +{" "} - + - #### Error logging - Infisical uses Sentry to report error logs - +{" "} - #### Settings - - Only allow users who are invited to sign up - + + +#### Error logging + +Infisical uses Sentry to report error logs + +{" "} + + + +#### Settings + +{" "} + + + Only allow users who are invited to sign up + Site URL - should be an absolute URL including the protocol (e.g. https://app.infisical.com) @@ -170,6 +219,11 @@ Other environment variables are listed below to increase the functionality of yo - ## Frontend environment variables - + + diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 1cc485aa4..93d8d41de 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -293,12 +293,10 @@ export const useRevokeMySessions = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => { - console.log("useRevokeAllSessions 1"); const { data } = await apiRequest.delete( "/api/v2/users/me/sessions" ); - console.log("useRevokeAllSessions 2: ", data); return data; }, onSuccess() { diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index afd82abd0..c814cd510 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -2,7 +2,7 @@ import { FormEvent, useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; -import { faGoogle } from "@fortawesome/free-brands-svg-icons"; +import { faGithub,faGoogle } from "@fortawesome/free-brands-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import axios from "axios" @@ -34,7 +34,6 @@ export const InitialStep = ({ const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const [loginError, setLoginError] = useState(false); - const [loginEmailChosen, setLoginEmailChosen] = useState(false); const { data: serverDetails } = useFetchServerStatus(); const queryParams = new URLSearchParams(window.location.search); @@ -68,8 +67,7 @@ export const InitialStep = ({ // send request to server endpoint const instance = axios.create() - const cliResp = await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse }) - console.log(cliResp) + await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse }) // cli page router.push("/cli-redirect"); @@ -118,23 +116,6 @@ export const InitialStep = ({ return (

Login to Infisical

-
- -
- {loginEmailChosen && <>
@@ -175,19 +156,40 @@ export const InitialStep = ({ {!isLoading && loginError && }
-
} - {!loginEmailChosen &&
+
+
-
} + onClick={() => { + const callbackPort = queryParams.get("callback_port"); + + window.open(`/api/v1/sso/redirect/google${callbackPort ? `?callback_port=${callbackPort}` : ""}`); + window.close(); + }} + leftIcon={} + className="h-12 w-full mx-0" + > + {t("login.continue-with-google")} + +
+
+ +