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
+6 -2
View File
@@ -106,8 +106,9 @@ const initializePassport = async () => {
}).save(); }).save();
} }
let isLinkingRequired = false;
if (!user.authMethods.includes(AuthMethod.GOOGLE)) { if (!user.authMethods.includes(AuthMethod.GOOGLE)) {
done(InternalServerError()); isLinkingRequired = true;
} }
const isUserCompleted = !!user.publicKey; const isUserCompleted = !!user.publicKey;
@@ -119,6 +120,7 @@ const initializePassport = async () => {
lastName: user.lastName, lastName: user.lastName,
authMethod: AuthMethod.GOOGLE, authMethod: AuthMethod.GOOGLE,
isUserCompleted, isUserCompleted,
isLinkingRequired,
...(req.query.state ? { ...(req.query.state ? {
callbackPort: req.query.state as string callbackPort: req.query.state as string
} : {}) } : {})
@@ -159,8 +161,9 @@ const initializePassport = async () => {
}).save(); }).save();
} }
let isLinkingRequired = false;
if (!user.authMethods.includes(AuthMethod.GITHUB)) { if (!user.authMethods.includes(AuthMethod.GITHUB)) {
done(InternalServerError()); isLinkingRequired = true;
} }
const isUserCompleted = !!user.publicKey; const isUserCompleted = !!user.publicKey;
@@ -172,6 +175,7 @@ const initializePassport = async () => {
lastName: user.lastName, lastName: user.lastName,
authMethod: AuthMethod.GITHUB, authMethod: AuthMethod.GITHUB,
isUserCompleted, isUserCompleted,
isLinkingRequired,
...(req.query.state ? { ...(req.query.state ? {
callbackPort: req.query.state as string callbackPort: req.query.state as string
} : {}) } : {})
+2 -1
View File
@@ -20,7 +20,8 @@ import {
RenameUserDTO, RenameUserDTO,
TokenVersion, TokenVersion,
UpdateOrgUserRoleDTO, UpdateOrgUserRoleDTO,
User} from "./types"; User
} from "./types";
const userKeys = { const userKeys = {
getUser: ["user"] as const, getUser: ["user"] as const,
+1 -4
View File
@@ -16,8 +16,7 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
const { const {
email, email,
isUserCompleted, isUserCompleted
callbackPort
} = jwt_decode(providerAuthToken) as any; } = jwt_decode(providerAuthToken) as any;
useEffect(() => { useEffect(() => {
@@ -36,7 +35,6 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
return ( return (
<PasswordStep <PasswordStep
providerAuthToken={providerAuthToken} providerAuthToken={providerAuthToken}
callbackPort={callbackPort}
email={email} email={email}
password={password} password={password}
setPassword={setPassword} setPassword={setPassword}
@@ -47,7 +45,6 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
return ( return (
<MFAStep <MFAStep
providerAuthToken={providerAuthToken} providerAuthToken={providerAuthToken}
callbackPort={callbackPort}
email={email} email={email}
password={password} password={password}
/> />
@@ -3,14 +3,19 @@ import ReactCodeInput from "react-code-input";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import axios from "axios" import axios from "axios"
import jwt_decode from "jwt-decode";
import Error from "@app/components/basic/Error"; // which to notification import Error from "@app/components/basic/Error"; // which to notification
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa" import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"
import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
import { Button } from "@app/components/v2"; import { Button } from "@app/components/v2";
import { useUpdateUserAuthMethods } from "@app/hooks/api";
import { useSendMfaToken } from "@app/hooks/api/auth"; import { useSendMfaToken } from "@app/hooks/api/auth";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; 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 // The style for the verification code input
const props = { const props = {
@@ -54,8 +59,7 @@ interface VerifyMfaTokenError {
export const MFAStep = ({ export const MFAStep = ({
email, email,
password, password,
providerAuthToken, providerAuthToken
callbackPort
}: Props) => { }: Props) => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const router = useRouter(); const router = useRouter();
@@ -67,9 +71,22 @@ export const MFAStep = ({
const { t } = useTranslation(); const { t } = useTranslation();
const sendMfaToken = useSendMfaToken(); const sendMfaToken = useSendMfaToken();
const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods();
const handleLoginMfa = async () => { const handleLoginMfa = async () => {
try { 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) { if (mfaCode.length !== 6) {
createNotification({ createNotification({
text: "Please enter a 6-digit MFA code and try again", text: "Please enter a 6-digit MFA code and try again",
@@ -79,7 +96,7 @@ export const MFAStep = ({
} }
setIsLoading(true); setIsLoading(true);
if (callbackPort){ if (callbackPort) {
// attemptCliLogin // attemptCliLogin
const isCliLoginSuccessful = await attemptCliLoginMfa({ const isCliLoginSuccessful = await attemptCliLoginMfa({
@@ -118,6 +135,15 @@ export const MFAStep = ({
text: "Successfully logged in", text: "Successfully logged in",
type: "success" type: "success"
}); });
if (isLinkingRequired && authMethod) {
const user = await fetchUserDetails();
const newAuthMethods = [...user.authMethods, authMethod]
await updateUserAuthMethodsMutateAsync({
authMethods: newAuthMethods
});
}
router.push(`/org/${userOrg}/overview`); router.push(`/org/${userOrg}/overview`);
} else { } else {
createNotification({ createNotification({
@@ -3,16 +3,18 @@ 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 axios from "axios" import axios from "axios"
import jwt_decode from "jwt-decode";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
import attemptLogin from "@app/components/utilities/attemptLogin"; import attemptLogin from "@app/components/utilities/attemptLogin";
import { Button, Input } from "@app/components/v2"; import { Button, Input } from "@app/components/v2";
import { useUpdateUserAuthMethods } from "@app/hooks/api";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { fetchUserDetails } from "@app/hooks/api/users/queries";
type Props = { type Props = {
providerAuthToken: string; providerAuthToken: string;
callbackPort?: string;
email: string; email: string;
password: string; password: string;
setPassword: (password: string) => void; setPassword: (password: string) => void;
@@ -21,16 +23,22 @@ type Props = {
export const PasswordStep = ({ export const PasswordStep = ({
providerAuthToken, providerAuthToken,
callbackPort,
email, email,
password, password,
setPassword, setPassword,
setStep setStep,
}: Props) => { }: Props) => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const { mutateAsync } = useUpdateUserAuthMethods();
const {
callbackPort,
isLinkingRequired,
authMethod
} = jwt_decode(providerAuthToken) as any;
const handleLogin = async () => { const handleLogin = async () => {
try { try {
@@ -90,6 +98,15 @@ export const PasswordStep = ({
text: "Successfully logged in", text: "Successfully logged in",
type: "success" type: "success"
}); });
if (isLinkingRequired) {
const user = await fetchUserDetails();
const newAuthMethods = [...user.authMethods, authMethod]
await mutateAsync({
authMethods: newAuthMethods
});
}
router.push(`/org/${userOrg}/overview`); router.push(`/org/${userOrg}/overview`);
} }
} }
@@ -108,9 +125,18 @@ export const PasswordStep = ({
onSubmit={(e) => e.preventDefault()} onSubmit={(e) => e.preventDefault()}
className="h-full mx-auto w-full max-w-md px-6 pt-8" 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"> <div className="mb-8">
What’s your Infisical Password? <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">
</p> {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="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"> <div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
<Input <Input