saleor-dashboard/src/auth/AuthProvider.tsx

236 lines
5.9 KiB
TypeScript
Raw Normal View History

2020-07-20 10:12:14 +00:00
import { IMessageContext } from "@saleor/components/messages";
2020-05-25 23:38:52 +00:00
import { DEMO_MODE } from "@saleor/config";
import { User } from "@saleor/fragments/types/User";
2019-10-25 12:35:48 +00:00
import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl";
2020-07-20 09:46:59 +00:00
import { getMutationStatus } from "@saleor/misc";
2019-09-06 15:30:33 +00:00
import {
isSupported as isCredentialsManagementAPISupported,
login as loginWithCredentialsManagementAPI,
saveCredentials
} from "@saleor/utils/credentialsManagement";
2020-07-20 10:12:14 +00:00
import ApolloClient from "apollo-client";
2020-07-21 13:55:50 +00:00
import React, { useContext, useEffect, useRef, useState } from "react";
2020-07-20 10:12:14 +00:00
import { useApolloClient, useMutation } from "react-apollo";
import { IntlShape, useIntl } from "react-intl";
import { UserContext } from "./";
2020-05-07 11:04:15 +00:00
import {
tokenAuthMutation,
tokenRefreshMutation,
tokenVerifyMutation
2020-05-07 11:04:15 +00:00
} from "./mutations";
import { RefreshToken, RefreshTokenVariables } from "./types/RefreshToken";
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
2020-05-25 23:38:52 +00:00
import {
displayDemoMessage,
2020-07-23 13:37:39 +00:00
getTokens,
removeTokens,
setAuthToken,
setTokens
2020-05-25 23:38:52 +00:00
} from "./utils";
2019-06-19 14:40:52 +00:00
2020-07-20 09:46:59 +00:00
const persistToken = false;
2020-07-21 13:55:50 +00:00
export function useAuthProvider(
2020-07-20 10:12:14 +00:00
intl: IntlShape,
notify: IMessageContext,
apolloClient: ApolloClient<any>
) {
2020-07-20 09:46:59 +00:00
const [userContext, setUserContext] = useState<undefined | User>(undefined);
2020-07-21 13:55:50 +00:00
const autologinPromise = useRef<Promise<any>>();
2020-07-23 13:37:39 +00:00
const refreshPromise = useRef<Promise<boolean>>();
2020-07-20 09:46:59 +00:00
useEffect(() => {
const token = getTokens().auth;
if (!!token && !userContext) {
autologinPromise.current = tokenVerify({ variables: { token } });
} else {
autologinPromise.current = loginWithCredentialsManagementAPI(login);
}
}, []);
useEffect(() => {
if (userContext && !userContext.isStaff) {
logout();
notify({
status: "error",
text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess),
title: intl.formatMessage(commonMessages.insufficientPermissions)
});
}
}, [userContext]);
2020-07-20 09:46:59 +00:00
const logout = () => {
setUserContext(undefined);
if (isCredentialsManagementAPISupported) {
navigator.credentials.preventSilentAccess();
}
2020-07-23 13:37:39 +00:00
removeTokens();
2020-07-20 09:46:59 +00:00
};
const [tokenAuth, tokenAuthResult] = useMutation<
TokenAuth,
TokenAuthVariables
2020-07-20 09:46:59 +00:00
>(tokenAuthMutation, {
2020-07-20 10:12:14 +00:00
client: apolloClient,
onCompleted: ({ tokenCreate }) => {
if (tokenCreate.errors.length > 0) {
2020-07-20 09:58:39 +00:00
logout();
}
const user = tokenCreate.user;
2020-07-20 09:46:59 +00:00
// FIXME: Now we set state also when auth fails and returned user is
// `null`, because the LoginView uses this `null` to display error.
setUserContext(user);
if (user) {
setTokens(tokenCreate.token, tokenCreate.csrfToken, persistToken);
2020-07-20 09:46:59 +00:00
}
},
onError: logout
});
const [tokenRefresh] = useMutation<RefreshToken, RefreshTokenVariables>(
2020-07-20 09:46:59 +00:00
tokenRefreshMutation,
{
2020-07-20 10:12:14 +00:00
client: apolloClient,
2020-07-20 09:46:59 +00:00
onError: logout
}
);
const [tokenVerify, tokenVerifyResult] = useMutation<
VerifyToken,
VerifyTokenVariables
2020-07-20 09:46:59 +00:00
>(tokenVerifyMutation, {
2020-07-20 10:12:14 +00:00
client: apolloClient,
2020-07-20 09:46:59 +00:00
onCompleted: result => {
if (result.tokenVerify === null) {
logout();
} else {
const user = result.tokenVerify?.user;
if (!!user) {
setUserContext(user);
}
}
},
onError: logout
});
const tokenAuthOpts = {
...tokenAuthResult,
status: getMutationStatus(tokenAuthResult)
};
const tokenVerifyOpts = {
...tokenVerifyResult,
status: getMutationStatus(tokenVerifyResult)
};
2020-07-13 15:44:02 +00:00
const onLogin = () => {
2020-05-25 23:38:52 +00:00
if (DEMO_MODE) {
displayDemoMessage(intl, notify);
}
};
2019-10-25 12:35:48 +00:00
2020-07-13 15:44:02 +00:00
const login = async (email: string, password: string) => {
2020-07-21 13:55:50 +00:00
const result = await tokenAuth({ variables: { email, password } });
if (result && !result.data.tokenCreate.errors.length) {
if (!!onLogin) {
onLogin();
}
2020-07-21 13:55:50 +00:00
saveCredentials(result.data.tokenCreate.user, password);
return result.data.tokenCreate.user;
}
return null;
2019-09-03 13:42:15 +00:00
};
2020-07-23 13:37:39 +00:00
const loginByToken = (auth: string, refresh: string, user: User) => {
2020-07-13 15:44:02 +00:00
setUserContext(user);
2020-07-23 13:37:39 +00:00
setTokens(auth, refresh, persistToken);
2019-06-19 14:40:52 +00:00
};
2020-07-23 13:37:39 +00:00
const refreshToken = (): Promise<boolean> => {
if (!!refreshPromise.current) {
return refreshPromise.current;
}
return new Promise(resolve => {
const token = getTokens().refresh;
2020-05-07 11:04:15 +00:00
2020-07-23 13:37:39 +00:00
return tokenRefresh({ variables: { token } }).then(refreshData => {
if (!!refreshData.data.tokenRefresh?.token) {
setAuthToken(refreshData.data.tokenRefresh.token, persistToken);
return resolve(true);
}
2020-05-07 11:04:15 +00:00
2020-07-23 13:37:39 +00:00
return resolve(false);
});
});
2020-05-07 11:04:15 +00:00
};
2020-07-20 10:12:14 +00:00
return {
2020-07-21 13:55:50 +00:00
autologinPromise,
2020-07-20 10:12:14 +00:00
login,
loginByToken,
logout,
refreshToken,
tokenAuthOpts,
tokenVerifyOpts,
userContext
};
}
interface AuthProviderProps {
children: React.ReactNode;
}
const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const apolloClient = useApolloClient();
const intl = useIntl();
const notify = useNotifier();
const {
login,
loginByToken,
logout,
tokenAuthOpts,
refreshToken,
tokenVerifyOpts,
userContext
} = useAuthProvider(intl, notify, apolloClient);
2020-07-13 15:44:02 +00:00
return (
<UserContext.Provider
value={{
login,
loginByToken,
logout,
tokenAuthLoading: tokenAuthOpts.loading,
tokenRefresh: refreshToken,
tokenVerifyLoading: tokenVerifyOpts.loading,
user: userContext
}}
>
{children}
</UserContext.Provider>
);
};
export const useAuth = () => {
const user = useContext(UserContext);
const isAuthenticated = !!user.user;
2020-07-13 15:44:02 +00:00
return {
2020-07-23 13:37:39 +00:00
hasToken: !!getTokens(),
2020-07-13 15:44:02 +00:00
isAuthenticated,
tokenAuthLoading: user.tokenAuthLoading,
tokenVerifyLoading: user.tokenVerifyLoading,
2020-07-14 08:11:43 +00:00
user: user.user
2020-07-13 15:44:02 +00:00
};
};
2019-06-19 14:40:52 +00:00
2020-07-13 15:44:02 +00:00
export default AuthProvider;