mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #853 from Infisical/linking-sso
Add linking for existing users without SSO enabled logging in via SSO
This commit is contained in:
@@ -106,8 +106,9 @@ const initializePassport = async () => {
|
||||
}).save();
|
||||
}
|
||||
|
||||
let isLinkingRequired = false;
|
||||
if (!user.authMethods.includes(AuthMethod.GOOGLE)) {
|
||||
done(InternalServerError());
|
||||
isLinkingRequired = true;
|
||||
}
|
||||
|
||||
const isUserCompleted = !!user.publicKey;
|
||||
@@ -119,6 +120,7 @@ const initializePassport = async () => {
|
||||
lastName: user.lastName,
|
||||
authMethod: AuthMethod.GOOGLE,
|
||||
isUserCompleted,
|
||||
isLinkingRequired,
|
||||
...(req.query.state ? {
|
||||
callbackPort: req.query.state as string
|
||||
} : {})
|
||||
@@ -159,8 +161,9 @@ const initializePassport = async () => {
|
||||
}).save();
|
||||
}
|
||||
|
||||
let isLinkingRequired = false;
|
||||
if (!user.authMethods.includes(AuthMethod.GITHUB)) {
|
||||
done(InternalServerError());
|
||||
isLinkingRequired = true;
|
||||
}
|
||||
|
||||
const isUserCompleted = !!user.publicKey;
|
||||
@@ -172,6 +175,7 @@ const initializePassport = async () => {
|
||||
lastName: user.lastName,
|
||||
authMethod: AuthMethod.GITHUB,
|
||||
isUserCompleted,
|
||||
isLinkingRequired,
|
||||
...(req.query.state ? {
|
||||
callbackPort: req.query.state as string
|
||||
} : {})
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
RenameUserDTO,
|
||||
TokenVersion,
|
||||
UpdateOrgUserRoleDTO,
|
||||
User} from "./types";
|
||||
User
|
||||
} from "./types";
|
||||
|
||||
const userKeys = {
|
||||
getUser: ["user"] as const,
|
||||
|
||||
@@ -16,8 +16,7 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
|
||||
|
||||
const {
|
||||
email,
|
||||
isUserCompleted,
|
||||
callbackPort
|
||||
isUserCompleted
|
||||
} = jwt_decode(providerAuthToken) as any;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -36,7 +35,6 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
|
||||
return (
|
||||
<PasswordStep
|
||||
providerAuthToken={providerAuthToken}
|
||||
callbackPort={callbackPort}
|
||||
email={email}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
@@ -47,7 +45,6 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
|
||||
return (
|
||||
<MFAStep
|
||||
providerAuthToken={providerAuthToken}
|
||||
callbackPort={callbackPort}
|
||||
email={email}
|
||||
password={password}
|
||||
/>
|
||||
|
||||
@@ -3,14 +3,19 @@ import ReactCodeInput from "react-code-input";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import axios from "axios"
|
||||
import jwt_decode from "jwt-decode";
|
||||
|
||||
import Error from "@app/components/basic/Error"; // which to notification
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"
|
||||
import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
||||
import { useSendMfaToken } from "@app/hooks/api/auth";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||
|
||||
|
||||
// The style for the verification code input
|
||||
const props = {
|
||||
@@ -54,8 +59,7 @@ interface VerifyMfaTokenError {
|
||||
export const MFAStep = ({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
callbackPort
|
||||
providerAuthToken
|
||||
}: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const router = useRouter();
|
||||
@@ -67,9 +71,22 @@ export const MFAStep = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const sendMfaToken = useSendMfaToken();
|
||||
const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods();
|
||||
|
||||
const handleLoginMfa = async () => {
|
||||
try {
|
||||
let isLinkingRequired: undefined | boolean;
|
||||
let callbackPort: undefined | string;
|
||||
let authMethod: undefined | AuthMethod;
|
||||
|
||||
if (providerAuthToken) {
|
||||
const decodedToken = jwt_decode(providerAuthToken) as any;
|
||||
|
||||
isLinkingRequired = decodedToken.isLinkingRequired;
|
||||
callbackPort = decodedToken.callbackPort;
|
||||
authMethod = decodedToken.authMethod;
|
||||
}
|
||||
|
||||
if (mfaCode.length !== 6) {
|
||||
createNotification({
|
||||
text: "Please enter a 6-digit MFA code and try again",
|
||||
@@ -79,7 +96,7 @@ export const MFAStep = ({
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
if (callbackPort){
|
||||
if (callbackPort) {
|
||||
|
||||
// attemptCliLogin
|
||||
const isCliLoginSuccessful = await attemptCliLoginMfa({
|
||||
@@ -118,6 +135,15 @@ export const MFAStep = ({
|
||||
text: "Successfully logged in",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
if (isLinkingRequired && authMethod) {
|
||||
const user = await fetchUserDetails();
|
||||
const newAuthMethods = [...user.authMethods, authMethod]
|
||||
await updateUserAuthMethodsMutateAsync({
|
||||
authMethods: newAuthMethods
|
||||
});
|
||||
}
|
||||
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
} else {
|
||||
createNotification({
|
||||
|
||||
@@ -3,16 +3,18 @@ import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router"
|
||||
import axios from "axios"
|
||||
import jwt_decode from "jwt-decode";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
||||
import attemptLogin from "@app/components/utilities/attemptLogin";
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||
|
||||
type Props = {
|
||||
providerAuthToken: string;
|
||||
callbackPort?: string;
|
||||
email: string;
|
||||
password: string;
|
||||
setPassword: (password: string) => void;
|
||||
@@ -21,16 +23,22 @@ type Props = {
|
||||
|
||||
export const PasswordStep = ({
|
||||
providerAuthToken,
|
||||
callbackPort,
|
||||
email,
|
||||
password,
|
||||
setPassword,
|
||||
setStep
|
||||
setStep,
|
||||
}: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useUpdateUserAuthMethods();
|
||||
|
||||
const {
|
||||
callbackPort,
|
||||
isLinkingRequired,
|
||||
authMethod
|
||||
} = jwt_decode(providerAuthToken) as any;
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
@@ -90,6 +98,15 @@ export const PasswordStep = ({
|
||||
text: "Successfully logged in",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
if (isLinkingRequired) {
|
||||
const user = await fetchUserDetails();
|
||||
const newAuthMethods = [...user.authMethods, authMethod]
|
||||
await mutateAsync({
|
||||
authMethods: newAuthMethods
|
||||
});
|
||||
}
|
||||
|
||||
router.push(`/org/${userOrg}/overview`);
|
||||
}
|
||||
}
|
||||
@@ -108,9 +125,18 @@ export const PasswordStep = ({
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
className="h-full mx-auto w-full max-w-md px-6 pt-8"
|
||||
>
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8">
|
||||
What’s your Infisical Password?
|
||||
</p>
|
||||
<div className="mb-8">
|
||||
<p className="mx-auto flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-4">
|
||||
{isLinkingRequired ? "Link your account" : "What's your Infisical password?"}
|
||||
</p>
|
||||
{isLinkingRequired && (
|
||||
<div className="text-bunker-400 text-xs flex flex-col items-center w-max mx-auto">
|
||||
<span className='duration-200 max-w-sm text-center px-4'>
|
||||
An existing account without this SSO authentication method enabled was found under the same email. Login with your password to link the account.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
|
||||
Reference in New Issue
Block a user