mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 23:29:05 +00:00
Update login with multiple auth methods to toggle button and logic
This commit is contained in:
@@ -17,6 +17,5 @@ export {
|
||||
useRevokeMySessions,
|
||||
useUpdateMfaEnabled,
|
||||
useUpdateOrgUserRole,
|
||||
useUpdateUserAuthProvider,
|
||||
useUpdateUserAuthProviders,
|
||||
useUpdateUserAuthMethods
|
||||
} from "./queries";
|
||||
@@ -13,14 +13,14 @@ import {
|
||||
AddUserToWsDTO,
|
||||
AddUserToWsRes,
|
||||
APIKeyData,
|
||||
AuthMethod,
|
||||
CreateAPIKeyRes,
|
||||
DeletOrgMembershipDTO,
|
||||
OrgUser,
|
||||
RenameUserDTO,
|
||||
TokenVersion,
|
||||
UpdateOrgUserRoleDTO,
|
||||
User
|
||||
} from "./types";
|
||||
User} from "./types";
|
||||
|
||||
const userKeys = {
|
||||
getUser: ["user"] as const,
|
||||
@@ -61,39 +61,18 @@ export const useRenameUser = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateUserAuthProvider = () => {
|
||||
|
||||
export const useUpdateUserAuthMethods = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
authProvider
|
||||
authMethods
|
||||
}: {
|
||||
authProvider: string;
|
||||
authMethods: AuthMethod[];
|
||||
}) => {
|
||||
const { data: { user } } = await apiRequest.patch("/api/v2/users/me/auth-provider", {
|
||||
authProvider
|
||||
});
|
||||
|
||||
return user;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(userKeys.getUser);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const useUpdateUserAuthProviders = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
authProviders
|
||||
}: {
|
||||
authProviders: string[];
|
||||
}) => {
|
||||
const { data: { user } } = await apiRequest.put("/api/v2/users/me/auth-providers", {
|
||||
authProviders
|
||||
const { data: { user } } = await apiRequest.put("/api/v2/users/me/auth-methods", {
|
||||
authMethods
|
||||
});
|
||||
|
||||
return user;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { UserWsKeyPair } from "../keys/types";
|
||||
|
||||
export enum AuthProvider {
|
||||
export enum AuthMethod {
|
||||
EMAIL = "email",
|
||||
GOOGLE = "google",
|
||||
OKTA_SAML = "okta-saml"
|
||||
GITHUB = "github",
|
||||
OKTA_SAML = "okta-saml",
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml"
|
||||
}
|
||||
|
||||
export type User = {
|
||||
@@ -12,8 +15,8 @@ export type User = {
|
||||
email: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
authProvider?: AuthProvider;
|
||||
authProviders?: AuthProvider[];
|
||||
authProvider?: AuthMethod;
|
||||
authMethods: AuthMethod[];
|
||||
encryptionVersion?: number;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
|
||||
+87
-88
@@ -1,27 +1,33 @@
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { faGithub, faGoogle, IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faEnvelope } from "@fortawesome/free-regular-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
Checkbox
|
||||
} from "@app/components/v2";
|
||||
import { Switch } from "@app/components/v2";
|
||||
import { useUser } from "@app/context";
|
||||
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
||||
import {
|
||||
useUpdateUserAuthProviders
|
||||
} from "@app/hooks/api";
|
||||
AuthMethod
|
||||
} from "@app/hooks/api/users/types";
|
||||
|
||||
const authMethodList = [
|
||||
{ label: "Email", value: "email" },
|
||||
{ label: "Google SSO", value: "google" },
|
||||
{ label: "GitHub SSO", value: "github" },
|
||||
{ label: "Okta SAML", value: "okta-saml" },
|
||||
{ label: "Azure SAML", value: "azure-saml" },
|
||||
{ label: "JumpCloud SAML", value: "jumpcloud-saml" }
|
||||
interface AuthMethodOption {
|
||||
label: string,
|
||||
value: AuthMethod,
|
||||
icon: IconDefinition;
|
||||
}
|
||||
|
||||
const authMethodOpts: AuthMethodOption[] = [
|
||||
{ label: "Email", value: AuthMethod.EMAIL, icon: faEnvelope },
|
||||
{ label: "Google", value: AuthMethod.GOOGLE, icon: faGoogle },
|
||||
{ label: "GitHub", value: AuthMethod.GITHUB, icon: faGithub }
|
||||
];
|
||||
|
||||
const samlProviders = [AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.AZURE_SAML];
|
||||
|
||||
const schema = yup.object({
|
||||
authMethods: yup.array().required("Auth method is required")
|
||||
});
|
||||
@@ -31,108 +37,101 @@ export type FormData = yup.InferType<typeof schema>;
|
||||
export const AuthMethodSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { user } = useUser();
|
||||
const { mutateAsync, isLoading } = useUpdateUserAuthProviders();
|
||||
|
||||
const defaultAuthMethods = user.authProviders?.length ?
|
||||
user.authProviders :
|
||||
[user?.authProvider ?? "email"];
|
||||
const { mutateAsync } = useUpdateUserAuthMethods();
|
||||
|
||||
const {
|
||||
reset,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
authMethods: defaultAuthMethods,
|
||||
authMethods: user.authMethods,
|
||||
},
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const selectedAuthMethods = watch("authMethods");
|
||||
const authMethods = watch("authMethods");
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
reset({
|
||||
authMethods: defaultAuthMethods,
|
||||
authMethods: user.authMethods,
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const onAuthMethodToggle = async (value: boolean, authMethodOpt: AuthMethodOption) => {
|
||||
const hasSamlEnabled = user.authMethods
|
||||
.some((authMethod: AuthMethod) => samlProviders.includes(authMethod));
|
||||
|
||||
const onFormSubmit = async ({
|
||||
authMethods
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (
|
||||
authMethods.includes("okta-saml")
|
||||
|| authMethods.includes("azure-saml")
|
||||
|| authMethods.includes("jumpcloud-saml")
|
||||
) {
|
||||
createNotification({
|
||||
text: "SAML authentication can only be configured in your organization settings",
|
||||
type: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
authProviders: authMethods
|
||||
});
|
||||
|
||||
if (hasSamlEnabled) {
|
||||
createNotification({
|
||||
text: "Successfully updated authentication method",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to update authentication method",
|
||||
text: "SAML authentication can only be configured in your organization settings",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
const newAuthMethods = value
|
||||
? [...authMethods, authMethodOpt.value]
|
||||
: authMethods.filter(auth => auth !== authMethodOpt.value);
|
||||
|
||||
if (value) {
|
||||
const newUser = await mutateAsync({
|
||||
authMethods: newAuthMethods
|
||||
});
|
||||
|
||||
setValue("authMethods", newUser.authMethods);
|
||||
createNotification({
|
||||
text: "Successfully enabled authentication method",
|
||||
type: "success"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (newAuthMethods.length === 0) {
|
||||
createNotification({
|
||||
text: "You must keep at least 1 authentication method enabled",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newUser = await mutateAsync({
|
||||
authMethods: newAuthMethods
|
||||
});
|
||||
|
||||
setValue("authMethods", newUser.authMethods);
|
||||
createNotification({
|
||||
text: "Successfully disabled authentication method",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
>
|
||||
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">
|
||||
Authentication Method
|
||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">
|
||||
Authentication methods
|
||||
</h2>
|
||||
<div className="max-w-md mb-4">
|
||||
{
|
||||
authMethodList.map(authMethod => (
|
||||
<Checkbox
|
||||
className="data-[state=checked]:bg-primary"
|
||||
id={`auth-method-id-${authMethod.label}`}
|
||||
key={`auth-method-${authMethod.label}`}
|
||||
isChecked={selectedAuthMethods.includes(authMethod.value)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setValue("authMethods", [
|
||||
...selectedAuthMethods,
|
||||
authMethod.value
|
||||
])
|
||||
} else {
|
||||
setValue("authMethods", selectedAuthMethods.filter(auth => auth !== authMethod.value))
|
||||
}
|
||||
}}>
|
||||
{authMethod.label}
|
||||
</Checkbox>
|
||||
))
|
||||
}
|
||||
<p className="text-gray-400 mb-4">
|
||||
By enabling a SSO provider, you are allowing an account with that provider which uses the same email address as your existing Infisical account to be able to log in to Infisical.
|
||||
</p>
|
||||
<div className="mb-4">
|
||||
{user && authMethodOpts.map((authMethodOpt) => {
|
||||
return (
|
||||
<div className="flex p-4 items-center" key={`auth-method-${authMethodOpt.value}`}>
|
||||
<div className="flex items-center w-20 mr-4">
|
||||
<FontAwesomeIcon icon={authMethodOpt.icon} className="mr-4" />
|
||||
<p>{authMethodOpt.label}</p>
|
||||
</div>
|
||||
<Switch
|
||||
id={`enable-${authMethodOpt.value}-auth`}
|
||||
onCheckedChange={(value) => onAuthMethodToggle(value, authMethodOpt)}
|
||||
isChecked={authMethods?.includes(authMethodOpt.value) ?? false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user