diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index b12a37964..8220bc41e 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1162,6 +1162,10 @@ "context": "product field", "string": "Export Product Weight" }, + "7Jg9ec": { + "context": "error message", + "string": "You don't have permission to login." + }, "7KRGqz": { "context": "Payment card title", "string": "Payment balance" @@ -2215,6 +2219,10 @@ "Fn3bE0": { "string": "Order line updated" }, + "FopBSj": { + "context": "error message", + "string": "Your username and/or password are incorrect. Please try again." + }, "FpIcp9": { "string": "No customers found" }, @@ -3051,10 +3059,6 @@ "context": "collection", "string": "Not Published" }, - "M4q0Ye": { - "context": "error message", - "string": "Sorry, login went wrong. Please try again." - }, "M6s/9e": { "context": "unassign country, dialog header", "string": "Remove from Shipping Zone" @@ -4515,6 +4519,10 @@ "context": "title", "string": "There’s a problem with app." }, + "Wr5UOV": { + "context": "error message", + "string": "Login went wrong. Please try again." + }, "Ww69SE": { "context": "search input placeholder", "string": "Search tax classes" @@ -7197,10 +7205,6 @@ "tR+UuE": { "string": "User doesn't exist. Please check your email in URL" }, - "tTtoKd": { - "context": "error message", - "string": "Sorry, your username and/or password are incorrect. Please try again." - }, "tTuCYj": { "context": "all gift cards label", "string": "All Gift Cards" diff --git a/src/auth/components/LoginPage/LoginPage.stories.tsx b/src/auth/components/LoginPage/LoginPage.stories.tsx index 5fb5f187f..e2233f88b 100644 --- a/src/auth/components/LoginPage/LoginPage.stories.tsx +++ b/src/auth/components/LoginPage/LoginPage.stories.tsx @@ -15,6 +15,7 @@ const props: Omit = { }, ], loading: false, + errors: [], onExternalAuthentication: () => undefined, onSubmit: () => undefined, }; @@ -23,9 +24,9 @@ storiesOf("Views / Authentication / Log in", module) .addDecorator(CardDecorator) .addDecorator(Decorator) .add("default", () => ) - .add("error login", () => ) + .add("error login", () => ) .add("error external login", () => ( - + )) .add("disabled", () => ) .add("loading", () => ); diff --git a/src/auth/components/LoginPage/LoginPage.tsx b/src/auth/components/LoginPage/LoginPage.tsx index a202cd00e..1de603d17 100644 --- a/src/auth/components/LoginPage/LoginPage.tsx +++ b/src/auth/components/LoginPage/LoginPage.tsx @@ -21,7 +21,7 @@ import LoginForm, { LoginFormData } from "./form"; import { getErrorMessage } from "./messages"; export interface LoginCardProps { - error?: UserContextError; + errors: UserContextError[]; disabled: boolean; loading: boolean; externalAuthentications?: AvailableExternalAuthenticationsQuery["shop"]["availableExternalAuthentications"]; @@ -31,7 +31,7 @@ export interface LoginCardProps { const LoginCard: React.FC = props => { const { - error, + errors, disabled, loading, externalAuthentications = [], @@ -62,11 +62,15 @@ const LoginCard: React.FC = props => { description="card header" /> - {error && ( -
+ {errors.map(error => ( +
{getErrorMessage(error, intl)}
- )} + ))} + getAuthErrorType(graphQLError), + ) || [] + ); +} diff --git a/src/auth/hooks/useAuthProvider.ts b/src/auth/hooks/useAuthProvider.ts index 81188136b..e7db7bac0 100644 --- a/src/auth/hooks/useAuthProvider.ts +++ b/src/auth/hooks/useAuthProvider.ts @@ -1,4 +1,4 @@ -import { ApolloClient } from "@apollo/client"; +import { ApolloClient, ApolloError } from "@apollo/client"; import { IMessageContext } from "@saleor/components/messages"; import { DEMO_MODE } from "@saleor/config"; import { useUserDetailsQuery } from "@saleor/graphql"; @@ -21,6 +21,7 @@ import { useEffect, useRef, useState } from "react"; import { IntlShape } from "react-intl"; import urlJoin from "url-join"; +import { parseAuthError } from "../errors"; import { ExternalLoginInput, RequestExternalLoginInput, @@ -53,12 +54,12 @@ export function useAuthProvider({ "requestedExternalPluginId", null, ); - const [error, setError] = useState(); + const [errors, setErrors] = useState([]); const permitCredentialsAPI = useRef(true); useEffect(() => { - if (authenticating && error) { - setError(undefined); + if (authenticating && errors.length) { + setErrors([]); } }, [authenticating]); @@ -88,6 +89,16 @@ export function useAuthProvider({ fetchPolicy: "cache-and-network", }); + const handleLoginError = (error: ApolloError) => { + const parsedErrors = parseAuthError(error); + + if (parsedErrors.length) { + setErrors(parsedErrors); + } else { + setErrors(["unknownLoginError"]); + } + }; + const handleLogout = async () => { const returnTo = urlJoin( window.location.origin, @@ -139,14 +150,14 @@ export function useAuthProvider({ } saveCredentials(result.data.tokenCreate.user, password); } else { - setError("loginError"); + setErrors(["loginError"]); } await logoutNonStaffUser(result.data.tokenCreate); return result.data.tokenCreate; } catch (error) { - setError("serverError"); + handleLoginError(error); } }; @@ -177,14 +188,14 @@ export function useAuthProvider({ displayDemoMessage(intl, notify); } } else { - setError("externalLoginError"); + setErrors(["externalLoginError"]); } await logoutNonStaffUser(result.data.externalObtainAccessTokens); return result?.data?.externalObtainAccessTokens; } catch (error) { - setError("serverError"); + handleLoginError(error); } }; @@ -206,9 +217,9 @@ export function useAuthProvider({ requestLoginByExternalPlugin: handleRequestExternalLogin, loginByExternalPlugin: handleExternalLogin, logout: handleLogout, - authenticating: authenticating && !error, + authenticating: authenticating && !errors.length, authenticated: authenticated && user?.isStaff, user: userDetails.data?.me, - error, + errors, }; } diff --git a/src/auth/index.tsx b/src/auth/index.tsx index c0930f998..39571fbe7 100644 --- a/src/auth/index.tsx +++ b/src/auth/index.tsx @@ -29,6 +29,7 @@ export const UserContext = React.createContext({ requestLoginByExternalPlugin: undefined, authenticating: false, authenticated: false, + errors: [], }); const AuthRouter: React.FC = () => ( diff --git a/src/auth/types.ts b/src/auth/types.ts index a81450159..ef13eae28 100644 --- a/src/auth/types.ts +++ b/src/auth/types.ts @@ -18,10 +18,15 @@ export interface RequestExternalLogoutInput { returnTo: string; } -export type UserContextError = - | "loginError" - | "externalLoginError" - | "serverError"; +export const UserContextError = { + loginError: "loginError", + serverError: "serverError", + noPermissionsError: "noPermissionsError", + externalLoginError: "externalLoginError", + unknownLoginError: "unknownLoginError", +} as const; + +export type UserContextError = typeof UserContextError[keyof typeof UserContextError]; export interface UserContext { login: (username: string, password: string) => Promise; @@ -37,5 +42,5 @@ export interface UserContext { user?: UserFragment; authenticating: boolean; authenticated: boolean; - error?: UserContextError; + errors: UserContextError[]; } diff --git a/src/auth/views/Login.tsx b/src/auth/views/Login.tsx index 0579446c6..159cf8fbc 100644 --- a/src/auth/views/Login.tsx +++ b/src/auth/views/Login.tsx @@ -23,7 +23,7 @@ const LoginView: React.FC = ({ params }) => { requestLoginByExternalPlugin, loginByExternalPlugin, authenticating, - error, + errors, } = useUser(); const { data: externalAuthentications, @@ -79,14 +79,17 @@ const LoginView: React.FC = ({ params }) => { const { code, state } = params; const isCallbackPath = location.pathname.includes(loginCallbackPath); - if (code && state && isCallbackPath) { + const externalAuthParamsExist = code && state && isCallbackPath; + const externalAuthNotPerformed = !authenticating && !errors.length; + + if (externalAuthParamsExist && externalAuthNotPerformed) { handleExternalAuthentication(code, state); } }, []); return ( storyFn => ( user, authenticated: false, authenticating: false, + errors: [], }} > {storyFn()} diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 9351644f7..d774e3ca3 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -37127,7 +37127,7 @@ exports[`Storyshots Views / Authentication / Log in error external login 1`] = ` class="Login-panel-id" data-test-id="login-error-message" > - Sorry, login went wrong. Please try again. + Login went wrong. Please try again.
- Sorry, your username and/or password are incorrect. Please try again. + Your username and/or password are incorrect. Please try again.
{children}