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
@@ -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. * Return organizations that the current user is part of.
* @param req * @param req
+1
View File
@@ -13,6 +13,7 @@ export interface IUser extends Document {
_id: Types.ObjectId; _id: Types.ObjectId;
authId?: string; authId?: string;
authProvider?: AuthProvider; authProvider?: AuthProvider;
authProviders?: AuthProvider[];
email: string; email: string;
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
+18
View File
@@ -57,6 +57,24 @@ router.patch(
usersController.updateAuthProvider 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( router.get(
"/me/organizations", "/me/organizations",
requireAuth({ requireAuth({
@@ -16,6 +16,7 @@ type Props = {
position?: "item-aligned" | "popper"; position?: "item-aligned" | "popper";
isDisabled?: boolean; isDisabled?: boolean;
icon?: IconProp; icon?: IconProp;
isMulti?: boolean;
}; };
export type SelectProps = Omit<SelectPrimitive.SelectProps, "disabled"> & Props; export type SelectProps = Omit<SelectPrimitive.SelectProps, "disabled"> & Props;
+2 -1
View File
@@ -15,5 +15,6 @@ export {
useRegisterUserAction, useRegisterUserAction,
useRevokeMySessions, useRevokeMySessions,
useUpdateOrgUserRole, useUpdateOrgUserRole,
useUpdateUserAuthProvider useUpdateUserAuthProvider,
useUpdateUserAuthProviders,
} from "./queries"; } from "./queries";
+22
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) => export const useGetUserAction = (action: string) =>
useQuery({ useQuery({
queryKey: userKeys.userAction, queryKey: userKeys.userAction,
+1
View File
@@ -13,6 +13,7 @@ export type User = {
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
authProvider?: AuthProvider; authProvider?: AuthProvider;
authProviders?: AuthProvider[];
encryptionVersion?: number; encryptionVersion?: number;
protectedKey?: string; protectedKey?: string;
protectedKeyIV?: string; protectedKeyIV?: string;
@@ -1,17 +1,16 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup"; import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { import {
Button, Button,
FormControl, Checkbox
Select, } from "@app/components/v2";
SelectItem} from "@app/components/v2";
import { useUser } from "@app/context"; import { useUser } from "@app/context";
import { import {
useUpdateUserAuthProvider useUpdateUserAuthProviders
} from "@app/hooks/api"; } from "@app/hooks/api";
const authMethods = [ const authMethods = [
@@ -24,7 +23,7 @@ const authMethods = [
]; ];
const schema = yup.object({ 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>; export type FormData = yup.InferType<typeof schema>;
@@ -32,35 +31,38 @@ export type FormData = yup.InferType<typeof schema>;
export const AuthMethodSection = () => { export const AuthMethodSection = () => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { user } = useUser(); const { user } = useUser();
const { mutateAsync, isLoading } = useUpdateUserAuthProvider(); const { mutateAsync, isLoading } = useUpdateUserAuthProviders();
const { const {
reset, reset,
control, handleSubmit,
handleSubmit setValue,
watch,
} = useForm<FormData>({ } = useForm<FormData>({
defaultValues: { defaultValues: {
authMethod: user?.authProvider ?? "email" authMethods: [user?.authProvider ?? "email"]
}, },
resolver: yupResolver(schema) resolver: yupResolver(schema)
}); });
const selectedAuthMethods = watch("authMethods");
useEffect(() => { useEffect(() => {
if (user) { if (user) {
reset({ reset({
authMethod: user?.authProvider ?? "email" authMethods: [user?.authProvider ?? "email"]
}); });
} }
}, [user]); }, [user]);
const onFormSubmit = async ({ const onFormSubmit = async ({
authMethod authMethods
}: FormData) => { }: FormData) => {
try { try {
if ( if (
authMethod === "okta-saml" authMethods.includes("okta-saml")
|| authMethod === "azure-saml" || authMethods.includes("azure-saml")
|| authMethod === "jumpcloud-saml" || authMethods.includes("jumpcloud-saml")
) { ) {
createNotification({ createNotification({
text: "SAML authentication can only be configured in your organization settings", text: "SAML authentication can only be configured in your organization settings",
@@ -71,7 +73,7 @@ export const AuthMethodSection = () => {
} }
await mutateAsync({ await mutateAsync({
authProvider: authMethod authProviders: authMethods
}); });
createNotification({ createNotification({
@@ -96,36 +98,29 @@ export const AuthMethodSection = () => {
Authentication Method Authentication Method
</h2> </h2>
<div className="max-w-md mb-4"> <div className="max-w-md mb-4">
<Controller {
control={control} authMethods.map(authMethod => (
name="authMethod" <Checkbox
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( className="data-[state=checked]:bg-primary"
<FormControl id={`auth-method-id-${authMethod.label}`}
className="mb-0" key={`auth-method-${authMethod.label}`}
errorText={error?.message} isChecked={selectedAuthMethods.includes(authMethod.value)}
isError={Boolean(error)} onCheckedChange={(checked) => {
> if (checked) {
<Select setValue("authMethods", [
defaultValue={field.value} ...selectedAuthMethods,
{...field} authMethod.value
onValueChange={(e) => onChange(e)} ])
className="w-full bg-mineshaft-800 border border-mineshaft-600" } else {
> setValue("authMethods", selectedAuthMethods.filter(auth => auth !== authMethod.value))
{authMethods.map((authMethod) => { }
return ( }}>
<SelectItem {authMethod.label}
value={authMethod.value} </Checkbox>
key={`auth-method-${authMethod.value}`} ))
> }
{authMethod.label}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
</div> </div>
<Button <Button
type="submit" type="submit"
colorSchema="secondary" colorSchema="secondary"