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:
BlackMagiq
2023-08-13 22:57:13 +07:00
committed by GitHub
5 changed files with 70 additions and 16 deletions

View File

@@ -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
} : {})

View File

@@ -20,7 +20,8 @@ import {
RenameUserDTO,
TokenVersion,
UpdateOrgUserRoleDTO,
User} from "./types";
User
} from "./types";
const userKeys = {
getUser: ["user"] as const,

View File

@@ -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}
/>

View File

@@ -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({

View File

@@ -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