added user controller and modified auth method page

This commit is contained in:
Sheen Capadngan
2023-08-06 19:09:38 +08:00
parent 49bcd8839f
commit 5604232aea
8 changed files with 123 additions and 47 deletions

View File

@@ -148,6 +148,43 @@ export const updateAuthProvider = async (req: Request, res: Response) => {
});
}
/**
* Update auth provider of the current user to [authProvider]
* @param req
* @param res
* @returns
*/
export const updateAuthProviders = async (req: Request, res: Response) => {
const {
authProviders
} = req.body;
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(),
{
authProviders
},
{
new: true
}
);
return res.status(200).send({
user
});
}
/**
* Return organizations that the current user is part of.
* @param req

View File

@@ -13,6 +13,7 @@ export interface IUser extends Document {
_id: Types.ObjectId;
authId?: string;
authProvider?: AuthProvider;
authProviders?: AuthProvider[];
email: string;
firstName?: string;
lastName?: string;

View File

@@ -57,6 +57,24 @@ router.patch(
usersController.updateAuthProvider
);
router.put(
"/me/auth-providers",
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
}),
body("authProviders").exists().isArray({
min: 1,
}).custom((authProviders: AuthProvider[]) => {
return authProviders.every(provider => [
AuthProvider.EMAIL,
AuthProvider.GOOGLE,
AuthProvider.GITHUB
].includes(provider))
}),
validateRequest,
usersController.updateAuthProviders,
);
router.get(
"/me/organizations",
requireAuth({

View File

@@ -16,6 +16,7 @@ type Props = {
position?: "item-aligned" | "popper";
isDisabled?: boolean;
icon?: IconProp;
isMulti?: boolean;
};
export type SelectProps = Omit<SelectPrimitive.SelectProps, "disabled"> & Props;

View File

@@ -15,5 +15,6 @@ export {
useRegisterUserAction,
useRevokeMySessions,
useUpdateOrgUserRole,
useUpdateUserAuthProvider
useUpdateUserAuthProvider,
useUpdateUserAuthProviders,
} from "./queries";

View File

@@ -80,6 +80,28 @@ export const useUpdateUserAuthProvider = () => {
});
};
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
});
return user;
},
onSuccess: () => {
queryClient.invalidateQueries(userKeys.getUser);
}
});
};
export const useGetUserAction = (action: string) =>
useQuery({
queryKey: userKeys.userAction,

View File

@@ -13,6 +13,7 @@ export type User = {
firstName?: string;
lastName?: string;
authProvider?: AuthProvider;
authProviders?: AuthProvider[];
encryptionVersion?: number;
protectedKey?: string;
protectedKeyIV?: string;

View File

@@ -1,17 +1,16 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import {
Button,
FormControl,
Select,
SelectItem} from "@app/components/v2";
Checkbox
} from "@app/components/v2";
import { useUser } from "@app/context";
import {
useUpdateUserAuthProvider
useUpdateUserAuthProviders
} from "@app/hooks/api";
const authMethods = [
@@ -24,7 +23,7 @@ const authMethods = [
];
const schema = yup.object({
authMethod: yup.string().required("Auth method is required")
authMethods: yup.array().required("Auth method is required")
});
export type FormData = yup.InferType<typeof schema>;
@@ -32,35 +31,38 @@ export type FormData = yup.InferType<typeof schema>;
export const AuthMethodSection = () => {
const { createNotification } = useNotificationContext();
const { user } = useUser();
const { mutateAsync, isLoading } = useUpdateUserAuthProvider();
const { mutateAsync, isLoading } = useUpdateUserAuthProviders();
const {
reset,
control,
handleSubmit
handleSubmit,
setValue,
watch,
} = useForm<FormData>({
defaultValues: {
authMethod: user?.authProvider ?? "email"
authMethods: [user?.authProvider ?? "email"]
},
resolver: yupResolver(schema)
});
const selectedAuthMethods = watch("authMethods");
useEffect(() => {
if (user) {
reset({
authMethod: user?.authProvider ?? "email"
authMethods: [user?.authProvider ?? "email"]
});
}
}, [user]);
const onFormSubmit = async ({
authMethod
authMethods
}: FormData) => {
try {
if (
authMethod === "okta-saml"
|| authMethod === "azure-saml"
|| authMethod === "jumpcloud-saml"
authMethods.includes("okta-saml")
|| authMethods.includes("azure-saml")
|| authMethods.includes("jumpcloud-saml")
) {
createNotification({
text: "SAML authentication can only be configured in your organization settings",
@@ -71,7 +73,7 @@ export const AuthMethodSection = () => {
}
await mutateAsync({
authProvider: authMethod
authProviders: authMethods
});
createNotification({
@@ -96,36 +98,29 @@ export const AuthMethodSection = () => {
Authentication Method
</h2>
<div className="max-w-md mb-4">
<Controller
control={control}
name="authMethod"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
className="mb-0"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full bg-mineshaft-800 border border-mineshaft-600"
>
{authMethods.map((authMethod) => {
return (
<SelectItem
value={authMethod.value}
key={`auth-method-${authMethod.value}`}
>
{authMethod.label}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
{
authMethods.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>
))
}
</div>
<Button
type="submit"
colorSchema="secondary"
@@ -136,4 +131,4 @@ export const AuthMethodSection = () => {
</Button>
</form>
);
}
}