Logout user that do not have permissions (#3559)

This commit is contained in:
Krzysztof Żuraw 2023-05-18 10:54:30 +02:00 committed by GitHub
parent 55337b5998
commit 8a86a32c08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 340 additions and 1143 deletions

654
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,7 @@
"@material-ui/styles": "^4.11.4",
"@reach/auto-id": "^0.16.0",
"@saleor/macaw-ui": "0.8.0-pre.81",
"@saleor/sdk": "^0.5.0",
"@saleor/sdk": "0.6.0",
"@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6",
"@uiw/react-color-hue": "0.0.34",
@ -122,9 +122,6 @@
"@graphql-codegen/typescript-react-apollo": "^3.2.5",
"@percy/cli": "^1.21.0",
"@percy/cypress": "^3.1.2",
"@pollyjs/adapter-node-http": "~5.0.0",
"@pollyjs/core": "~5.0.0",
"@pollyjs/persister-fs": "~5.0.0",
"@release-it/bumper": "^2.0.0",
"@saleor/app-sdk": "0.37.3",
"@types/apollo-upload-client": "^17.0.2",

View file

@ -1,133 +0,0 @@
{
"log": {
"_recordingName": "User/will be logged in if has valid credentials",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "5.1.0"
},
"entries": [
{
"_id": "916a39b70c19064326e4caf3e7a38f9d",
"_order": 0,
"cache": {},
"request": {
"bodySize": 620,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "620"
},
{
"_fromType": "array",
"name": "user-agent",
"value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
},
{
"_fromType": "array",
"name": "accept-encoding",
"value": "gzip,deflate"
},
{
"_fromType": "array",
"name": "connection",
"value": "close"
},
{
"name": "host",
"value": "localhost:8000"
}
],
"headersSize": 254,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\"operationName\":\"loginWithoutDetails\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"admin\"},\"query\":\"fragment AccountErrorFragment on AccountError {\\n code\\n field\\n message\\n __typename\\n}\\n\\nfragment UserBaseFragment on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n __typename\\n}\\n\\nmutation loginWithoutDetails($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n csrfToken\\n token\\n errors {\\n ...AccountErrorFragment\\n __typename\\n }\\n user {\\n ...UserBaseFragment\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}"
},
"queryString": [],
"url": "http://localhost:8000/graphql/"
},
"response": {
"bodySize": 984,
"content": {
"mimeType": "application/json",
"size": 984,
"text": "{\"data\": {\"tokenCreate\": {\"csrfToken\": \"0GedAMt3cXdGCfB9rqe7Tqc8ogIRYwvhWgpN2l4pXFOKcQDtXpczhD4CPDO4zV2d\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJpYXQiOjE2NDY4MjgwNDgsIm93bmVyIjoic2FsZW9yIiwiZXhwIjoxNjQ2ODI4MzQ4LCJ0b2tlbiI6IlBHSXN0dmFUS2xQcCIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ0eXBlIjoiYWNjZXNzIiwidXNlcl9pZCI6IlZYTmxjam95TVE9PSIsImlzX3N0YWZmIjp0cnVlfQ.NQPcXACGUynrPfhYA_aoP_afZonMMQbmy_DBGM57HArB1BPQZSJXMJ4ye27buRN1CnwuWhSBHJP6J7XjOdH7F2WzXTldaIbud5TCms00ANde9AdJnJ92zL1vTuB99-oHC8PqJRUTMMn4hetYosKOb9FEAS6tD4u529Do8YiVnxcsdGxPmfEaN5HiwJ4HE76lfZH2rU6uin9Vdk14dmJKF-Rr_P_efCckgkvNwr3GAyX7L9zeVeJeDR4W1jeUY-f05OQKx9_qZazpWBVk1558JX1kBu6wQ2kFCiCLuUcv0L13GanLesP-FCDwgntTCwNmFXdqTdADAXzYc8KpoScpfg\", \"errors\": [], \"user\": {\"id\": \"VXNlcjoyMQ==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": true, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}, \"extensions\": {\"cost\": {\"requestedQueryCost\": 0, \"maximumAvailable\": 50000}}}"
},
"cookies": [
{
"httpOnly": true,
"name": "refreshToken",
"path": "/",
"sameSite": "Lax",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJpYXQiOjE2NDY4MjgwNDgsIm93bmVyIjoic2FsZW9yIiwiZXhwIjoxNjQ5NDIwMDQ4LCJ0b2tlbiI6IlBHSXN0dmFUS2xQcCIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ0eXBlIjoicmVmcmVzaCIsInVzZXJfaWQiOiJWWE5sY2pveU1RPT0iLCJpc19zdGFmZiI6dHJ1ZSwiY3NyZlRva2VuIjoiMEdlZEFNdDNjWGRHQ2ZCOXJxZTdUcWM4b2dJUll3dmhXZ3BOMmw0cFhGT0tjUUR0WHBjemhENENQRE80elYyZCJ9.eKJ4g02HziNjuKjxi_zEl5iz3EnrjzqWJjNYgVXxHMcXcJozOk5CrBpVFmM1ZUzdbXyPoHxW_UQeK0mVsMI9_owiYMfZcCyrOh1sDZB4oZVGA7Q_oYMLqc0Z81o_W-ZIN6Z3UNK4UKl7-1IRltJxVr4qmVtC7R5B_3Hnjs8xrEkcN0Q6sS0NyXYL9xVnazhDIRgrRQu4lU4co3eBCu0svyo7z5xESZmCRxsJZ1tfnoC-Gjx-6zO2hOFP4OrsbNWZcQhtoTDu5IbG8qthWI0eGvAkuK6jje0pnGrswuW_LlG1moBCy01C8K9WIYtYoT6BFnNMDc00W9MBNzGiN9rCXQ"
}
],
"headers": [
{
"name": "date",
"value": "Wed, 09 Mar 2022 12:14:08 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.9.10"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "984"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
},
{
"_fromType": "array",
"name": "set-cookie",
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJpYXQiOjE2NDY4MjgwNDgsIm93bmVyIjoic2FsZW9yIiwiZXhwIjoxNjQ5NDIwMDQ4LCJ0b2tlbiI6IlBHSXN0dmFUS2xQcCIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ0eXBlIjoicmVmcmVzaCIsInVzZXJfaWQiOiJWWE5sY2pveU1RPT0iLCJpc19zdGFmZiI6dHJ1ZSwiY3NyZlRva2VuIjoiMEdlZEFNdDNjWGRHQ2ZCOXJxZTdUcWM4b2dJUll3dmhXZ3BOMmw0cFhGT0tjUUR0WHBjemhENENQRE80elYyZCJ9.eKJ4g02HziNjuKjxi_zEl5iz3EnrjzqWJjNYgVXxHMcXcJozOk5CrBpVFmM1ZUzdbXyPoHxW_UQeK0mVsMI9_owiYMfZcCyrOh1sDZB4oZVGA7Q_oYMLqc0Z81o_W-ZIN6Z3UNK4UKl7-1IRltJxVr4qmVtC7R5B_3Hnjs8xrEkcN0Q6sS0NyXYL9xVnazhDIRgrRQu4lU4co3eBCu0svyo7z5xESZmCRxsJZ1tfnoC-Gjx-6zO2hOFP4OrsbNWZcQhtoTDu5IbG8qthWI0eGvAkuK6jje0pnGrswuW_LlG1moBCy01C8K9WIYtYoT6BFnNMDc00W9MBNzGiN9rCXQ; HttpOnly; Path=/; SameSite=Lax"
}
],
"headersSize": 967,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2022-03-09T12:14:07.734Z",
"time": 539,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 539
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -1,226 +0,0 @@
{
"log": {
"_recordingName": "User/will not be logged in if doesn't have valid credentials",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "5.1.0"
},
"entries": [
{
"_id": "916a39b70c19064326e4caf3e7a38f9d",
"_order": 0,
"cache": {},
"request": {
"bodySize": 636,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "636"
},
{
"_fromType": "array",
"name": "user-agent",
"value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
},
{
"_fromType": "array",
"name": "accept-encoding",
"value": "gzip,deflate"
},
{
"_fromType": "array",
"name": "connection",
"value": "close"
},
{
"name": "host",
"value": "localhost:8000"
}
],
"headersSize": 254,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\"operationName\":\"loginWithoutDetails\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"NotAValidPassword123!\"},\"query\":\"fragment AccountErrorFragment on AccountError {\\n code\\n field\\n message\\n __typename\\n}\\n\\nfragment UserBaseFragment on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n __typename\\n}\\n\\nmutation loginWithoutDetails($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n csrfToken\\n token\\n errors {\\n ...AccountErrorFragment\\n __typename\\n }\\n user {\\n ...UserBaseFragment\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}"
},
"queryString": [],
"url": "http://localhost:8000/graphql/"
},
"response": {
"bodySize": 321,
"content": {
"mimeType": "application/json",
"size": 321,
"text": "{\"data\": {\"tokenCreate\": {\"csrfToken\": null, \"token\": null, \"errors\": [{\"code\": \"INVALID_CREDENTIALS\", \"field\": \"email\", \"message\": \"Please, enter valid credentials\", \"__typename\": \"AccountError\"}], \"user\": null, \"__typename\": \"CreateToken\"}}, \"extensions\": {\"cost\": {\"requestedQueryCost\": 0, \"maximumAvailable\": 50000}}}"
},
"cookies": [],
"headers": [
{
"name": "date",
"value": "Wed, 09 Mar 2022 12:14:08 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.9.10"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "321"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
}
],
"headersSize": 194,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2022-03-09T12:14:08.308Z",
"time": 365,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 365
}
},
{
"_id": "f36c6d9d965b62862363338cf17d6135",
"_order": 0,
"cache": {},
"request": {
"bodySize": 324,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "324"
},
{
"_fromType": "array",
"name": "user-agent",
"value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
},
{
"_fromType": "array",
"name": "accept-encoding",
"value": "gzip,deflate"
},
{
"_fromType": "array",
"name": "connection",
"value": "close"
},
{
"name": "host",
"value": "localhost:8000"
}
],
"headersSize": 254,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "application/json",
"params": [],
"text": "[{\"operationName\":\"UserDetails\",\"variables\":{},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nquery UserDetails {\\n me {\\n ...User\\n __typename\\n }\\n}\\n\"}]"
},
"queryString": [],
"url": "http://localhost:8000/graphql/"
},
"response": {
"bodySize": 102,
"content": {
"mimeType": "application/json",
"size": 102,
"text": "[{\"data\": {\"me\": null}, \"extensions\": {\"cost\": {\"requestedQueryCost\": 1, \"maximumAvailable\": 50000}}}]"
},
"cookies": [],
"headers": [
{
"name": "date",
"value": "Wed, 09 Mar 2022 12:14:08 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.9.10"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "102"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
}
],
"headersSize": 194,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2022-03-09T12:14:08.311Z",
"time": 70,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 70
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -1,120 +0,0 @@
{
"log": {
"_recordingName": "User/will not be logged in if is non-staff",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "5.1.0"
},
"entries": [
{
"_id": "916a39b70c19064326e4caf3e7a38f9d",
"_order": 0,
"cache": {},
"request": {
"bodySize": 624,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "624"
},
{
"_fromType": "array",
"name": "user-agent",
"value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
},
{
"_fromType": "array",
"name": "accept-encoding",
"value": "gzip,deflate"
},
{
"_fromType": "array",
"name": "connection",
"value": "close"
},
{
"name": "host",
"value": "localhost:8000"
}
],
"headersSize": 254,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\"operationName\":\"loginWithoutDetails\",\"variables\":{\"email\":\"client@example.com\",\"password\":\"password\"},\"query\":\"fragment AccountErrorFragment on AccountError {\\n code\\n field\\n message\\n __typename\\n}\\n\\nfragment UserBaseFragment on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n __typename\\n}\\n\\nmutation loginWithoutDetails($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n csrfToken\\n token\\n errors {\\n ...AccountErrorFragment\\n __typename\\n }\\n user {\\n ...UserBaseFragment\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}"
},
"queryString": [],
"url": "http://localhost:8000/graphql/"
},
"response": {
"bodySize": 321,
"content": {
"mimeType": "application/json",
"size": 321,
"text": "{\"data\": {\"tokenCreate\": {\"csrfToken\": null, \"token\": null, \"errors\": [{\"code\": \"INVALID_CREDENTIALS\", \"field\": \"email\", \"message\": \"Please, enter valid credentials\", \"__typename\": \"AccountError\"}], \"user\": null, \"__typename\": \"CreateToken\"}}, \"extensions\": {\"cost\": {\"requestedQueryCost\": 0, \"maximumAvailable\": 50000}}}"
},
"cookies": [],
"headers": [
{
"name": "date",
"value": "Wed, 09 Mar 2022 12:14:08 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.9.10"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "321"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
}
],
"headersSize": 194,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2022-03-09T12:14:08.691Z",
"time": 121,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 121
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -1,42 +1,12 @@
/** @jest-environment setup-polly-jest/jest-environment-jsdom */
import { getApiUrl } from "@dashboard/config";
import { createSaleorClient, SaleorProvider } from "@saleor/sdk";
import setupApi from "@test/api";
import { useApolloClient } from "@apollo/client";
import { useUserDetailsQuery } from "@dashboard/graphql";
import useNotifier from "@dashboard/hooks/useNotifier";
import { useAuth, useAuthState } from "@saleor/sdk";
import { act, renderHook } from "@testing-library/react-hooks";
import React from "react";
import { IntlProvider } from "react-intl";
import { MemoryRouter as Router } from "react-router-dom";
import { useIntl } from "react-intl";
import { useAuthProvider } from "./hooks/useAuthProvider";
const apolloClient = setupApi();
function renderAuthProvider() {
const intl = {
formatMessage: ({ defaultMessage }) => defaultMessage,
};
const notify = jest.fn();
const saleorClient = createSaleorClient({
apiUrl: getApiUrl(),
channel: "",
});
const wrapper = ({ children }) => (
<IntlProvider defaultLocale="en" locale="en">
<Router>
<SaleorProvider client={saleorClient}>{children}</SaleorProvider>
</Router>
</IntlProvider>
);
const { result } = renderHook(
() => useAuthProvider({ intl: intl as any, notify, apolloClient }),
{ wrapper },
);
return result;
}
const adminCredentials = {
email: "admin@example.com",
password: "admin",
@ -53,43 +23,202 @@ beforeEach(() => {
sessionStorage.clear();
});
describe("User", () => {
it("will be logged in if has valid credentials", async () => {
const hook = renderAuthProvider();
jest.mock("react-intl", () => ({
useIntl: jest.fn(() => ({
formatMessage: jest.fn(x => x.defaultMessage),
})),
defineMessages: jest.fn(x => x),
}));
await act(async () => {
const result = await hook.current.login(
jest.mock("@saleor/sdk", () => ({
useAuth: jest.fn(() => ({
login: jest.fn(() => ({
data: {
tokenCreate: {
errors: [],
user: {
userPermissions: [
{
code: "MANAGE_USERS",
name: "Handle checkouts",
},
],
},
},
},
})),
logout: jest.fn(),
})),
useAuthState: jest.fn(() => undefined),
}));
jest.mock("@apollo/client", () => ({
useApolloClient: jest.fn(() => ({
clearStore: jest.fn(),
})),
ApolloError: jest.fn(),
}));
jest.mock("@dashboard/graphql", () => ({
useUserDetailsQuery: jest.fn(() => ({
data: undefined,
})),
}));
jest.mock("@dashboard/hooks/useNotifier", () => ({
__esModule: true,
default: jest.fn(() => () => undefined),
}));
jest.mock("@dashboard/hooks/useNavigator", () => ({
__esModule: true,
default: jest.fn(() => () => undefined),
}));
jest.mock("@dashboard/hooks/useLocalStorage", () => ({
__esModule: true,
default: jest.fn(() => []),
}));
jest.mock("@dashboard/auth", () => ({
useUser: jest.fn(),
}));
jest.mock("use-react-router", () => ({
__esModule: true,
default: jest.fn(() => ({
location: {},
})),
}));
describe("AuthProvider", () => {
it("Staff user will be logged in if has valid credentials", async () => {
// Arrange
const intl = useIntl();
const notify = useNotifier();
const apolloClient = useApolloClient();
(useAuthState as jest.Mock).mockImplementation(() => ({
authenticated: true,
authenticating: false,
user: {
isStaff: true,
},
}));
(useUserDetailsQuery as jest.Mock).mockImplementation(() => ({
data: {
me: {
email: adminCredentials.email,
isStaff: true,
},
},
}));
// Act
const hook = renderHook(() =>
useAuthProvider({ intl, notify, apolloClient }),
);
await act(async () =>
hook.result.current.login(
adminCredentials.email,
adminCredentials.password,
),
);
expect(result.user?.email).toBe(adminCredentials.email);
});
expect(hook.current.authenticated).toBe(true);
// Assert
expect(hook.result.current.user?.email).toBe(adminCredentials.email);
expect(hook.result.current.authenticated).toBe(true);
});
it("will not be logged in if doesn't have valid credentials", async () => {
const hook = renderAuthProvider();
it("User will not be logged in if doesn't have valid credentials", async () => {
// Arrange
const intl = useIntl();
const notify = useNotifier();
const apolloClient = useApolloClient();
await act(async () => {
const result = await hook.current.login(
adminCredentials.email,
"NotAValidPassword123!",
(useAuthState as jest.Mock).mockImplementation(() => ({
authenticated: false,
authenticating: false,
}));
(useUserDetailsQuery as jest.Mock).mockImplementation(() => ({
data: {
me: null,
},
}));
// Act
const hook = renderHook(() =>
useAuthProvider({ intl, notify, apolloClient }),
);
expect(result.user).toBe(null);
});
expect(hook.current.authenticated).toBe(false);
// Assert
expect(hook.result.current.user).toBe(null);
expect(hook.result.current.authenticated).toBe(false);
});
it("will not be logged in if is non-staff", async () => {
const hook = renderAuthProvider();
it("Non-staff user will not be logged in", async () => {
// Arrange
const intl = useIntl();
const notify = useNotifier();
const apolloClient = useApolloClient();
await act(async () => {
const result = await hook.current.login(
(useAuthState as jest.Mock).mockImplementation(() => ({
authenticated: false,
authenticating: false,
}));
(useUserDetailsQuery as jest.Mock).mockImplementation(() => ({
data: {
me: {
email: nonStaffUserCredentials.email,
isStaff: false,
},
},
}));
// Act
const hook = renderHook(() =>
useAuthProvider({ intl, notify, apolloClient }),
);
await act(async () =>
hook.result.current.login(
nonStaffUserCredentials.email,
nonStaffUserCredentials.password,
),
);
expect(result.user).toBe(null);
// Assert
expect(hook.result.current.errors).toEqual([]);
expect(hook.result.current.authenticated).toBe(false);
});
expect(hook.current.authenticated).toBe(false);
it("Should logout user without userPermissions", async () => {
const intl = useIntl();
const notify = useNotifier();
const apolloClient = useApolloClient();
(useAuth as jest.Mock).mockImplementation(() => ({
login: jest.fn(() => ({
data: {
tokenCreate: {
errors: [],
user: {
userPermissions: [],
},
},
},
})),
logout: jest.fn(),
}));
// Act
const hook = renderHook(() =>
useAuthProvider({ intl, notify, apolloClient }),
);
await act(async () =>
hook.result.current.login(
nonStaffUserCredentials.email,
nonStaffUserCredentials.password,
),
);
// Assert
expect(hook.result.current.errors).toEqual(["noPermissionsError"]);
expect(hook.result.current.authenticated).toBe(false);
});
});

View file

@ -17,6 +17,7 @@ import {
useAuth,
useAuthState,
} from "@saleor/sdk";
import isEmpty from "lodash/isEmpty";
import { useEffect, useRef, useState } from "react";
import { IntlShape } from "react-intl";
import urlJoin from "url-join";
@ -140,10 +141,16 @@ export function useAuthProvider({
includeDetails: false,
});
if (isEmpty(result.data?.tokenCreate.user.userPermissions)) {
setErrors(["noPermissionsError"]);
await handleLogout();
}
if (result && !result.data.tokenCreate.errors.length) {
if (DEMO_MODE) {
displayDemoMessage(intl, notify);
}
saveCredentials(result.data.tokenCreate.user, password);
} else {
setErrors(["loginError"]);
@ -183,6 +190,13 @@ export function useAuthProvider({
input: JSON.stringify(input),
});
if (
isEmpty(result.data?.externalObtainAccessTokens.user.userPermissions)
) {
setErrors(["noPermissionsError"]);
await handleLogout();
}
if (result && !result.data?.externalObtainAccessTokens.errors.length) {
if (DEMO_MODE) {
displayDemoMessage(intl, notify);
@ -223,7 +237,7 @@ export function useAuthProvider({
loginByExternalPlugin: handleExternalLogin,
logout: handleLogout,
authenticating: authenticating && !errors.length,
authenticated: authenticated && user?.isStaff,
authenticated: authenticated && !!user?.isStaff && !errors.length,
user: userDetails.data?.me,
errors,
};

View file

@ -82,6 +82,11 @@ const LoginView: React.FC<LoginViewProps> = ({ params }) => {
if (externalAuthParamsExist && externalAuthNotPerformed) {
handleExternalAuthentication(code, state);
}
return () => {
setRequestedExternalPluginId(null);
setFallbackUri(null);
};
}, []);
return (

View file

@ -1,71 +0,0 @@
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { BatchHttpLink } from "@apollo/client/link/batch-http";
import { getApiUrl } from "@dashboard/config";
import { createFetch } from "@saleor/sdk";
import isCI from "is-ci";
import path from "path";
import { setupPolly } from "setup-polly-jest";
const POLLY_MODES = ["replay", "record", "passthrough", "stopped"] as const;
function getPollyMode() {
const env = process.env.POLLY_MODE as (typeof POLLY_MODES)[number];
if (!env) {
return POLLY_MODES[0]; // replay
}
if (POLLY_MODES.includes(env)) {
return env;
}
console.warn(`Unrecognised POLLY_MODE env variable value: ${env}`);
return POLLY_MODES[0]; // replay
}
function getPollyRecordIfMissing() {
const env = process.env.POLLY_RECORD_IF_MISSING;
if (!env) {
return !isCI;
}
return env === "true";
}
function setupApi() {
setupPolly({
adapters: [require("@pollyjs/adapter-node-http")],
matchRequestsBy: {
headers: false,
url: {
hash: false,
hostname: true,
password: false,
pathname: true,
port: false,
protocol: false,
query: false,
username: false,
},
body: false,
},
mode: getPollyMode(),
recordIfMissing: getPollyRecordIfMissing(),
persister: require("@pollyjs/persister-fs"),
persisterOptions: {
keepUnusedRequests: false,
fs: {
recordingsDir: path.resolve(__dirname, "../recordings"),
},
},
});
const cache = new InMemoryCache();
const link = new BatchHttpLink({
fetch: createFetch(),
uri: getApiUrl(),
});
const apolloClient = new ApolloClient({
cache,
link,
});
return apolloClient;
}
export default setupApi;