387 lines
13 KiB
TypeScript
387 lines
13 KiB
TypeScript
import { useApolloClient } from '@apollo/client';
|
|
import { useCallback } from 'react';
|
|
import {
|
|
snapshot_UNSTABLE,
|
|
useGotoRecoilSnapshot,
|
|
useRecoilCallback,
|
|
useSetRecoilState,
|
|
} from 'recoil';
|
|
import { iconsState } from 'twenty-ui';
|
|
|
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
|
|
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
|
|
import { workspacesState } from '@/auth/states/workspaces';
|
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
|
import { billingState } from '@/client-config/states/billingState';
|
|
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
|
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
|
import { supportChatState } from '@/client-config/states/supportChatState';
|
|
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
|
import {
|
|
useChallengeMutation,
|
|
useCheckUserExistsLazyQuery,
|
|
useSignUpMutation,
|
|
useVerifyMutation,
|
|
} from '~/generated/graphql';
|
|
import { isDefined } from '~/utils/isDefined';
|
|
|
|
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
|
|
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
|
import { DateFormat } from '@/localization/constants/DateFormat';
|
|
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
|
import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState';
|
|
import { detectDateFormat } from '@/localization/utils/detectDateFormat';
|
|
import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';
|
|
import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
|
import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDateFormatFromWorkspaceDateFormat';
|
|
import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat';
|
|
import { currentUserState } from '../states/currentUserState';
|
|
import { tokenPairState } from '../states/tokenPairState';
|
|
|
|
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
|
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
|
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
|
|
|
export const useAuth = () => {
|
|
const setTokenPair = useSetRecoilState(tokenPairState);
|
|
const setCurrentUser = useSetRecoilState(currentUserState);
|
|
const setCurrentWorkspaceMember = useSetRecoilState(
|
|
currentWorkspaceMemberState,
|
|
);
|
|
const setCurrentWorkspaceMembers = useSetRecoilState(
|
|
currentWorkspaceMembersState,
|
|
);
|
|
|
|
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
|
|
const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState);
|
|
const setWorkspaces = useSetRecoilState(workspacesState);
|
|
|
|
const [challenge] = useChallengeMutation();
|
|
const [signUp] = useSignUpMutation();
|
|
const [verify] = useVerifyMutation();
|
|
const { isOnAWorkspaceSubdomain } =
|
|
useIsCurrentLocationOnAWorkspaceSubdomain();
|
|
const { workspaceSubdomain } = useReadWorkspaceSubdomainFromCurrentLocation();
|
|
|
|
const { setLastAuthenticateWorkspaceDomain } =
|
|
useLastAuthenticatedWorkspaceDomain();
|
|
const [checkUserExistsQuery, { data: checkUserExistsData }] =
|
|
useCheckUserExistsLazyQuery();
|
|
|
|
const client = useApolloClient();
|
|
|
|
const goToRecoilSnapshot = useGotoRecoilSnapshot();
|
|
|
|
const setDateTimeFormat = useSetRecoilState(dateTimeFormatState);
|
|
|
|
const clearSession = useRecoilCallback(
|
|
({ snapshot }) =>
|
|
async () => {
|
|
const emptySnapshot = snapshot_UNSTABLE();
|
|
const iconsValue = snapshot.getLoadable(iconsState).getValue();
|
|
const authProvidersValue = snapshot
|
|
.getLoadable(authProvidersState)
|
|
.getValue();
|
|
const billing = snapshot.getLoadable(billingState).getValue();
|
|
const isDeveloperDefaultSignInPrefilled = snapshot
|
|
.getLoadable(isDeveloperDefaultSignInPrefilledState)
|
|
.getValue();
|
|
const supportChat = snapshot.getLoadable(supportChatState).getValue();
|
|
const isDebugMode = snapshot.getLoadable(isDebugModeState).getValue();
|
|
const captchaProvider = snapshot
|
|
.getLoadable(captchaProviderState)
|
|
.getValue();
|
|
const clientConfigApiStatus = snapshot
|
|
.getLoadable(clientConfigApiStatusState)
|
|
.getValue();
|
|
const isCurrentUserLoaded = snapshot
|
|
.getLoadable(isCurrentUserLoadedState)
|
|
.getValue();
|
|
const isMultiWorkspaceEnabled = snapshot
|
|
.getLoadable(isMultiWorkspaceEnabledState)
|
|
.getValue();
|
|
const initialSnapshot = emptySnapshot.map(({ set }) => {
|
|
set(iconsState, iconsValue);
|
|
set(authProvidersState, authProvidersValue);
|
|
set(billingState, billing);
|
|
set(
|
|
isDeveloperDefaultSignInPrefilledState,
|
|
isDeveloperDefaultSignInPrefilled,
|
|
);
|
|
set(supportChatState, supportChat);
|
|
set(isDebugModeState, isDebugMode);
|
|
set(captchaProviderState, captchaProvider);
|
|
set(clientConfigApiStatusState, clientConfigApiStatus);
|
|
set(isCurrentUserLoadedState, isCurrentUserLoaded);
|
|
set(isMultiWorkspaceEnabledState, isMultiWorkspaceEnabled);
|
|
return undefined;
|
|
});
|
|
goToRecoilSnapshot(initialSnapshot);
|
|
await client.clearStore();
|
|
sessionStorage.clear();
|
|
localStorage.clear();
|
|
},
|
|
[client, goToRecoilSnapshot],
|
|
);
|
|
|
|
const handleChallenge = useCallback(
|
|
async (email: string, password: string, captchaToken?: string) => {
|
|
const challengeResult = await challenge({
|
|
variables: {
|
|
email,
|
|
password,
|
|
captchaToken,
|
|
},
|
|
});
|
|
|
|
if (isDefined(challengeResult.errors)) {
|
|
throw challengeResult.errors;
|
|
}
|
|
|
|
if (!challengeResult.data?.challenge) {
|
|
throw new Error('No login token');
|
|
}
|
|
|
|
return challengeResult.data.challenge;
|
|
},
|
|
[challenge],
|
|
);
|
|
|
|
const handleVerify = useCallback(
|
|
async (loginToken: string) => {
|
|
const verifyResult = await verify({
|
|
variables: { loginToken },
|
|
});
|
|
|
|
if (isDefined(verifyResult.errors)) {
|
|
throw verifyResult.errors;
|
|
}
|
|
|
|
if (!verifyResult.data?.verify) {
|
|
throw new Error('No verify result');
|
|
}
|
|
|
|
setTokenPair(verifyResult.data?.verify.tokens);
|
|
|
|
const user = verifyResult.data?.verify.user;
|
|
|
|
let workspaceMember = null;
|
|
|
|
setCurrentUser(user);
|
|
|
|
if (isDefined(user.workspaceMembers)) {
|
|
const workspaceMembers = user.workspaceMembers.map(
|
|
(workspaceMember) => ({
|
|
...workspaceMember,
|
|
colorScheme: workspaceMember.colorScheme as ColorScheme,
|
|
locale: workspaceMember.locale ?? 'en',
|
|
}),
|
|
);
|
|
|
|
setCurrentWorkspaceMembers(workspaceMembers);
|
|
}
|
|
|
|
if (isDefined(user.workspaceMember)) {
|
|
workspaceMember = {
|
|
...user.workspaceMember,
|
|
colorScheme: user.workspaceMember?.colorScheme as ColorScheme,
|
|
locale: user.workspaceMember?.locale ?? 'en',
|
|
};
|
|
|
|
setCurrentWorkspaceMember(workspaceMember);
|
|
|
|
// TODO: factorize with UserProviderEffect
|
|
setDateTimeFormat({
|
|
timeZone:
|
|
workspaceMember.timeZone && workspaceMember.timeZone !== 'system'
|
|
? workspaceMember.timeZone
|
|
: detectTimeZone(),
|
|
dateFormat: isDefined(user.workspaceMember.dateFormat)
|
|
? getDateFormatFromWorkspaceDateFormat(
|
|
user.workspaceMember.dateFormat,
|
|
)
|
|
: DateFormat[detectDateFormat()],
|
|
timeFormat: isDefined(user.workspaceMember.timeFormat)
|
|
? getTimeFormatFromWorkspaceTimeFormat(
|
|
user.workspaceMember.timeFormat,
|
|
)
|
|
: TimeFormat[detectTimeFormat()],
|
|
});
|
|
}
|
|
|
|
const workspace = user.defaultWorkspace ?? null;
|
|
|
|
setCurrentWorkspace(workspace);
|
|
|
|
if (isDefined(workspace) && isOnAWorkspaceSubdomain) {
|
|
setLastAuthenticateWorkspaceDomain({
|
|
workspaceId: workspace.id,
|
|
subdomain: workspace.subdomain,
|
|
});
|
|
}
|
|
|
|
if (isDefined(verifyResult.data?.verify.user.workspaces)) {
|
|
const validWorkspaces = verifyResult.data?.verify.user.workspaces
|
|
.filter(
|
|
({ workspace }) => workspace !== null && workspace !== undefined,
|
|
)
|
|
.map((validWorkspace) => validWorkspace.workspace)
|
|
.filter(isDefined);
|
|
|
|
setWorkspaces(validWorkspaces);
|
|
}
|
|
|
|
return {
|
|
user,
|
|
workspaceMember,
|
|
workspace,
|
|
tokens: verifyResult.data?.verify.tokens,
|
|
};
|
|
},
|
|
[
|
|
verify,
|
|
setTokenPair,
|
|
setCurrentUser,
|
|
setCurrentWorkspace,
|
|
isOnAWorkspaceSubdomain,
|
|
setCurrentWorkspaceMembers,
|
|
setCurrentWorkspaceMember,
|
|
setDateTimeFormat,
|
|
setLastAuthenticateWorkspaceDomain,
|
|
setWorkspaces,
|
|
],
|
|
);
|
|
|
|
const handleCrendentialsSignIn = useCallback(
|
|
async (email: string, password: string, captchaToken?: string) => {
|
|
const { loginToken } = await handleChallenge(
|
|
email,
|
|
password,
|
|
captchaToken,
|
|
);
|
|
setIsVerifyPendingState(true);
|
|
|
|
const { user, workspaceMember, workspace } = await handleVerify(
|
|
loginToken.token,
|
|
);
|
|
|
|
setIsVerifyPendingState(false);
|
|
|
|
return {
|
|
user,
|
|
workspaceMember,
|
|
workspace,
|
|
};
|
|
},
|
|
[handleChallenge, handleVerify, setIsVerifyPendingState],
|
|
);
|
|
|
|
const handleSignOut = useCallback(async () => {
|
|
await clearSession();
|
|
}, [clearSession]);
|
|
|
|
const handleCredentialsSignUp = useCallback(
|
|
async (
|
|
email: string,
|
|
password: string,
|
|
workspaceInviteHash?: string,
|
|
workspacePersonalInviteToken?: string,
|
|
captchaToken?: string,
|
|
) => {
|
|
setIsVerifyPendingState(true);
|
|
|
|
const signUpResult = await signUp({
|
|
variables: {
|
|
email,
|
|
password,
|
|
workspaceInviteHash,
|
|
workspacePersonalInviteToken,
|
|
captchaToken,
|
|
},
|
|
});
|
|
|
|
if (isDefined(signUpResult.errors)) {
|
|
throw signUpResult.errors;
|
|
}
|
|
|
|
if (!signUpResult.data?.signUp) {
|
|
throw new Error('No login token');
|
|
}
|
|
|
|
const { user, workspace, workspaceMember } = await handleVerify(
|
|
signUpResult.data?.signUp.loginToken.token,
|
|
);
|
|
|
|
setIsVerifyPendingState(false);
|
|
|
|
return { user, workspaceMember, workspace };
|
|
},
|
|
[setIsVerifyPendingState, signUp, handleVerify],
|
|
);
|
|
|
|
const buildRedirectUrl = useCallback(
|
|
(
|
|
path: string,
|
|
params: {
|
|
workspacePersonalInviteToken?: string;
|
|
workspaceInviteHash?: string;
|
|
},
|
|
) => {
|
|
const url = new URL(`${REACT_APP_SERVER_BASE_URL}${path}`);
|
|
if (isDefined(params.workspaceInviteHash)) {
|
|
url.searchParams.set('inviteHash', params.workspaceInviteHash);
|
|
}
|
|
if (isDefined(params.workspacePersonalInviteToken)) {
|
|
url.searchParams.set(
|
|
'inviteToken',
|
|
params.workspacePersonalInviteToken,
|
|
);
|
|
}
|
|
if (isDefined(workspaceSubdomain)) {
|
|
url.searchParams.set('workspaceSubdomain', workspaceSubdomain);
|
|
}
|
|
|
|
return url.toString();
|
|
},
|
|
[workspaceSubdomain],
|
|
);
|
|
|
|
const handleGoogleLogin = useCallback(
|
|
(params: {
|
|
workspacePersonalInviteToken?: string;
|
|
workspaceInviteHash?: string;
|
|
}) => {
|
|
window.location.href = buildRedirectUrl('/auth/google', params);
|
|
},
|
|
[buildRedirectUrl],
|
|
);
|
|
|
|
const handleMicrosoftLogin = useCallback(
|
|
(params: {
|
|
workspacePersonalInviteToken?: string;
|
|
workspaceInviteHash?: string;
|
|
}) => {
|
|
window.location.href = buildRedirectUrl('/auth/microsoft', params);
|
|
},
|
|
[buildRedirectUrl],
|
|
);
|
|
|
|
return {
|
|
challenge: handleChallenge,
|
|
verify: handleVerify,
|
|
|
|
checkUserExists: { checkUserExistsData, checkUserExistsQuery },
|
|
clearSession,
|
|
signOut: handleSignOut,
|
|
signUpWithCredentials: handleCredentialsSignUp,
|
|
signInWithCredentials: handleCrendentialsSignIn,
|
|
signInWithGoogle: handleGoogleLogin,
|
|
signInWithMicrosoft: handleMicrosoftLogin,
|
|
};
|
|
};
|