"use client"; import { useEffect, useState } from "react"; import { ListRolesResponse } from "@server/routers/role"; import { toast } from "@app/hooks/useToast"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { useResourceContext } from "@app/hooks/useResourceContext"; import { AxiosResponse } from "axios"; import { formatAxiosError } from "@app/lib/api"; import { GetResourceWhitelistResponse, ListResourceRolesResponse, ListResourceUsersResponse } from "@server/routers/resource"; import { Button } from "@app/components/ui/button"; import { set, z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@app/components/ui/form"; import { ListUsersResponse } from "@server/routers/user"; import { Binary, Key } from "lucide-react"; import SetResourcePasswordForm from "./SetResourcePasswordForm"; import SetResourcePincodeForm from "./SetResourcePincodeForm"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { SettingsContainer, SettingsSection, SettingsSectionTitle, SettingsSectionHeader, SettingsSectionDescription, SettingsSectionBody, SettingsSectionFooter, SettingsSectionForm } from "@app/components/Settings"; import { SwitchInput } from "@app/components/SwitchInput"; import { InfoPopup } from "@app/components/ui/info-popup"; import { Tag, TagInput } from "@app/components/tags/tag-input"; import { useRouter } from "next/navigation"; import { UserType } from "@server/types/UserTypes"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { InfoIcon } from "lucide-react"; import { useTranslations } from "next-intl"; const UsersRolesFormSchema = z.object({ roles: z.array( z.object({ id: z.string(), text: z.string() }) ), users: z.array( z.object({ id: z.string(), text: z.string() }) ) }); const whitelistSchema = z.object({ emails: z.array( z.object({ id: z.string(), text: z.string() }) ) }); export default function ResourceAuthenticationPage() { const { org } = useOrgContext(); const { resource, updateResource, authInfo, updateAuthInfo } = useResourceContext(); const { env } = useEnvContext(); const api = createApiClient({ env }); const router = useRouter(); const t = useTranslations(); const [pageLoading, setPageLoading] = useState(true); const [allRoles, setAllRoles] = useState<{ id: string; text: string }[]>( [] ); const [allUsers, setAllUsers] = useState<{ id: string; text: string }[]>( [] ); const [activeRolesTagIndex, setActiveRolesTagIndex] = useState< number | null >(null); const [activeUsersTagIndex, setActiveUsersTagIndex] = useState< number | null >(null); const [activeEmailTagIndex, setActiveEmailTagIndex] = useState< number | null >(null); const [ssoEnabled, setSsoEnabled] = useState(resource.sso); // const [blockAccess, setBlockAccess] = useState(resource.blockAccess); const [whitelistEnabled, setWhitelistEnabled] = useState( resource.emailWhitelistEnabled ); const [loadingSaveUsersRoles, setLoadingSaveUsersRoles] = useState(false); const [loadingSaveWhitelist, setLoadingSaveWhitelist] = useState(false); const [loadingRemoveResourcePassword, setLoadingRemoveResourcePassword] = useState(false); const [loadingRemoveResourcePincode, setLoadingRemoveResourcePincode] = useState(false); const [isSetPasswordOpen, setIsSetPasswordOpen] = useState(false); const [isSetPincodeOpen, setIsSetPincodeOpen] = useState(false); const usersRolesForm = useForm>({ resolver: zodResolver(UsersRolesFormSchema), defaultValues: { roles: [], users: [] } }); const whitelistForm = useForm>({ resolver: zodResolver(whitelistSchema), defaultValues: { emails: [] } }); useEffect(() => { const fetchData = async () => { try { const [ rolesResponse, resourceRolesResponse, usersResponse, resourceUsersResponse, whitelist ] = await Promise.all([ api.get>( `/org/${org?.org.orgId}/roles` ), api.get>( `/resource/${resource.resourceId}/roles` ), api.get>( `/org/${org?.org.orgId}/users` ), api.get>( `/resource/${resource.resourceId}/users` ), api.get>( `/resource/${resource.resourceId}/whitelist` ) ]); setAllRoles( rolesResponse.data.data.roles .map((role) => ({ id: role.roleId.toString(), text: role.name })) .filter((role) => role.text !== "Admin") ); usersRolesForm.setValue( "roles", resourceRolesResponse.data.data.roles .map((i) => ({ id: i.roleId.toString(), text: i.name })) .filter((role) => role.text !== "Admin") ); setAllUsers( usersResponse.data.data.users.map((user) => ({ id: user.id.toString(), text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}` })) ); usersRolesForm.setValue( "users", resourceUsersResponse.data.data.users.map((i) => ({ id: i.userId.toString(), text: `${i.email || i.username}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}` })) ); whitelistForm.setValue( "emails", whitelist.data.data.whitelist.map((w) => ({ id: w.email, text: w.email })) ); setPageLoading(false); } catch (e) { console.error(e); toast({ variant: "destructive", title: t('resourceErrorAuthFetch'), description: formatAxiosError( e, t('resourceErrorAuthFetchDescription') ) }); } }; fetchData(); }, []); async function saveWhitelist() { setLoadingSaveWhitelist(true); try { await api.post(`/resource/${resource.resourceId}`, { emailWhitelistEnabled: whitelistEnabled }); if (whitelistEnabled) { await api.post(`/resource/${resource.resourceId}/whitelist`, { emails: whitelistForm.getValues().emails.map((i) => i.text) }); } updateResource({ emailWhitelistEnabled: whitelistEnabled }); toast({ title: t('resourceWhitelistSave'), description: t('resourceWhitelistSaveDescription') }); router.refresh(); } catch (e) { console.error(e); toast({ variant: "destructive", title: t('resourceErrorWhitelistSave'), description: formatAxiosError( e, t('resourceErrorWhitelistSaveDescription') ) }); } finally { setLoadingSaveWhitelist(false); } } async function onSubmitUsersRoles( data: z.infer ) { try { setLoadingSaveUsersRoles(true); const jobs = [ api.post(`/resource/${resource.resourceId}/roles`, { roleIds: data.roles.map((i) => parseInt(i.id)) }), api.post(`/resource/${resource.resourceId}/users`, { userIds: data.users.map((i) => i.id) }), api.post(`/resource/${resource.resourceId}`, { sso: ssoEnabled }) ]; await Promise.all(jobs); updateResource({ sso: ssoEnabled }); updateAuthInfo({ sso: ssoEnabled }); toast({ title: t('resourceAuthSettingsSave'), description: t('resourceAuthSettingsSaveDescription') }); router.refresh(); } catch (e) { console.error(e); toast({ variant: "destructive", title: t('resourceErrorUsersRolesSave'), description: formatAxiosError( e, t('resourceErrorUsersRolesSaveDescription') ) }); } finally { setLoadingSaveUsersRoles(false); } } function removeResourcePassword() { setLoadingRemoveResourcePassword(true); api.post(`/resource/${resource.resourceId}/password`, { password: null }) .then(() => { toast({ title: t('resourcePasswordRemove'), description: t('resourcePasswordRemoveDescription') }); updateAuthInfo({ password: false }); router.refresh(); }) .catch((e) => { toast({ variant: "destructive", title: t('resourceErrorPasswordRemove'), description: formatAxiosError( e, t('resourceErrorPasswordRemoveDescription') ) }); }) .finally(() => setLoadingRemoveResourcePassword(false)); } function removeResourcePincode() { setLoadingRemoveResourcePincode(true); api.post(`/resource/${resource.resourceId}/pincode`, { pincode: null }) .then(() => { toast({ title: t('resourcePincodeRemove'), description: t('resourcePincodeRemoveDescription') }); updateAuthInfo({ pincode: false }); router.refresh(); }) .catch((e) => { toast({ variant: "destructive", title: t('resourceErrorPincodeRemove'), description: formatAxiosError( e, t('resourceErrorPincodeRemoveDescription') ) }); }) .finally(() => setLoadingRemoveResourcePincode(false)); } if (pageLoading) { return <>; } return ( <> {isSetPasswordOpen && ( { setIsSetPasswordOpen(false); updateAuthInfo({ password: true }); }} /> )} {isSetPincodeOpen && ( { setIsSetPincodeOpen(false); updateAuthInfo({ pincode: true }); }} /> )} {t('resourceUsersRoles')} {t('resourceUsersRolesDescription')} setSsoEnabled(val)} />
{ssoEnabled && ( <> ( {t('roles')} { usersRolesForm.setValue( "roles", newRoles as [ Tag, ...Tag[] ] ); }} enableAutocomplete={ true } autocompleteOptions={ allRoles } allowDuplicates={ false } restrictTagsToAutocompleteOptions={ true } sortTags={true} /> {t('resourceRoleDescription')} )} /> ( {t('users')} { usersRolesForm.setValue( "users", newUsers as [ Tag, ...Tag[] ] ); }} enableAutocomplete={ true } autocompleteOptions={ allUsers } allowDuplicates={ false } restrictTagsToAutocompleteOptions={ true } sortTags={true} /> )} /> )}
{t('resourceAuthMethods')} {t('resourceAuthMethodsDescriptions')} {/* Password Protection */}
{t('resourcePasswordProtection', {status: authInfo.password? t('enabled') : t('disabled')})}
{/* PIN Code Protection */}
{t('resourcePincodeProtection', {status: authInfo.pincode ? t('enabled') : t('disabled')})}
{t('otpEmailTitle')} {t('otpEmailTitleDescription')} {!env.email.emailEnabled && ( {t('otpEmailSmtpRequired')} {t('otpEmailSmtpRequiredDescription')} )} {whitelistEnabled && env.email.emailEnabled && (
( {/* @ts-ignore */} { return z .string() .email() .or( z .string() .regex( /^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { message: t('otpEmailErrorInvalid') } ) ) .safeParse( tag ).success; }} setActiveTagIndex={ setActiveEmailTagIndex } placeholder={t('otpEmailEnter')} tags={ whitelistForm.getValues() .emails } setTags={( newRoles ) => { whitelistForm.setValue( "emails", newRoles as [ Tag, ...Tag[] ] ); }} allowDuplicates={ false } sortTags={true} /> {t('otpEmailEnterDescription')} )} /> )}
); }