commit
5062d35270
24 changed files with 3843 additions and 474 deletions
|
@ -13,6 +13,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Add order invoices management - #570 by @orzechdev
|
- Add order invoices management - #570 by @orzechdev
|
||||||
- Add Cypress e2e runner - #584 by @krzysztofwolski
|
- Add Cypress e2e runner - #584 by @krzysztofwolski
|
||||||
- create Apps - #599 by @AlicjaSzu
|
- create Apps - #599 by @AlicjaSzu
|
||||||
|
- Refactor authorization - #624 by @dominik-zeglen
|
||||||
|
|
||||||
## 2.10.1
|
## 2.10.1
|
||||||
|
|
||||||
|
|
2545
package-lock.json
generated
2545
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -83,6 +83,9 @@
|
||||||
"@babel/preset-react": "^7.7.4",
|
"@babel/preset-react": "^7.7.4",
|
||||||
"@babel/preset-typescript": "^7.7.4",
|
"@babel/preset-typescript": "^7.7.4",
|
||||||
"@babel/runtime": "^7.7.6",
|
"@babel/runtime": "^7.7.6",
|
||||||
|
"@pollyjs/adapter-node-http": "^4.3.0",
|
||||||
|
"@pollyjs/core": "^4.3.0",
|
||||||
|
"@pollyjs/persister-fs": "^4.3.0",
|
||||||
"@storybook/addon-storyshots": "^5.2.8",
|
"@storybook/addon-storyshots": "^5.2.8",
|
||||||
"@storybook/react": "^5.1.9",
|
"@storybook/react": "^5.1.9",
|
||||||
"@testing-library/react-hooks": "^1.1.0",
|
"@testing-library/react-hooks": "^1.1.0",
|
||||||
|
@ -93,6 +96,9 @@
|
||||||
"@types/jest": "^24.0.24",
|
"@types/jest": "^24.0.24",
|
||||||
"@types/lodash-es": "^4.17.3",
|
"@types/lodash-es": "^4.17.3",
|
||||||
"@types/moment-timezone": "^0.5.12",
|
"@types/moment-timezone": "^0.5.12",
|
||||||
|
"@types/node-fetch": "^2.5.7",
|
||||||
|
"@types/pollyjs__adapter-node-http": "^2.0.0",
|
||||||
|
"@types/pollyjs__persister-fs": "^2.0.0",
|
||||||
"@types/react": "^16.9.16",
|
"@types/react": "^16.9.16",
|
||||||
"@types/react-dom": "^16.8.5",
|
"@types/react-dom": "^16.8.5",
|
||||||
"@types/react-dropzone": "^4.2.2",
|
"@types/react-dropzone": "^4.2.2",
|
||||||
|
@ -103,6 +109,7 @@
|
||||||
"@types/react-sortable-tree": "^0.3.6",
|
"@types/react-sortable-tree": "^0.3.6",
|
||||||
"@types/react-test-renderer": "^16.8.2",
|
"@types/react-test-renderer": "^16.8.2",
|
||||||
"@types/semver-compare": "^1.0.1",
|
"@types/semver-compare": "^1.0.1",
|
||||||
|
"@types/setup-polly-jest": "^0.5.0",
|
||||||
"@types/storybook__addon-storyshots": "^3.4.9",
|
"@types/storybook__addon-storyshots": "^3.4.9",
|
||||||
"@types/storybook__react": "^4.0.2",
|
"@types/storybook__react": "^4.0.2",
|
||||||
"@types/url-join": "^4.0.0",
|
"@types/url-join": "^4.0.0",
|
||||||
|
@ -133,8 +140,10 @@
|
||||||
"husky": "^3.0.8",
|
"husky": "^3.0.8",
|
||||||
"jest": "^24.8.0",
|
"jest": "^24.8.0",
|
||||||
"jest-file": "^1.0.0",
|
"jest-file": "^1.0.0",
|
||||||
|
"jest-localstorage-mock": "^2.4.3",
|
||||||
"lint-staged": "^9.4.2",
|
"lint-staged": "^9.4.2",
|
||||||
"mock-apollo-client": "^0.4.0",
|
"mock-apollo-client": "^0.4.0",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"react-intl-translations-manager": "^5.0.3",
|
"react-intl-translations-manager": "^5.0.3",
|
||||||
"react-test-renderer": "^16.12.0",
|
"react-test-renderer": "^16.12.0",
|
||||||
|
@ -142,6 +151,8 @@
|
||||||
"require-context.macro": "^1.1.1",
|
"require-context.macro": "^1.1.1",
|
||||||
"rimraf": "^3.0.0",
|
"rimraf": "^3.0.0",
|
||||||
"start-server-and-test": "^1.11.0",
|
"start-server-and-test": "^1.11.0",
|
||||||
|
"setup-polly-jest": "^0.8.0",
|
||||||
|
"testcafe": "^1.3.3",
|
||||||
"ts-jest": "^24.2.0",
|
"ts-jest": "^24.2.0",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.2.0",
|
"tsconfig-paths-webpack-plugin": "^3.2.0",
|
||||||
"webpack": "^4.35.3",
|
"webpack": "^4.35.3",
|
||||||
|
@ -152,6 +163,9 @@
|
||||||
"fsevents": "^1.2.9"
|
"fsevents": "^1.2.9"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
"setupFiles": [
|
||||||
|
"jest-localstorage-mock"
|
||||||
|
],
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(jsx?|tsx?)$": "babel-jest",
|
"^.+\\.(jsx?|tsx?)$": "babel-jest",
|
||||||
"^.+\\.(png|svg|jpe?g)$": "jest-file"
|
"^.+\\.(png|svg|jpe?g)$": "jest-file"
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"_recordingName": "User/will be logged if has valid token",
|
||||||
|
"creator": {
|
||||||
|
"comment": "persister:fs",
|
||||||
|
"name": "Polly.JS",
|
||||||
|
"version": "4.3.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_id": "f515e15cbc83df73e5bd41437971c2e6",
|
||||||
|
"_order": 0,
|
||||||
|
"cache": {},
|
||||||
|
"request": {
|
||||||
|
"bodySize": 691,
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept",
|
||||||
|
"value": "*/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "691"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_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\":\"VerifyToken\",\"variables\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5NjAyODUxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU1RPT0iLCJpc19zdGFmZiI6dHJ1ZX0.eo8_Ew98HICB4cFQN2U7mCJ8ydGVOvQLGRT4CnkufMc\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation VerifyToken($token: String!) {\\n tokenVerify(token: $token) {\\n payload\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
|
},
|
||||||
|
"queryString": [],
|
||||||
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"bodySize": 1619,
|
||||||
|
"content": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"size": 1619,
|
||||||
|
"text": "[{\"data\": {\"tokenVerify\": {\"payload\": {\"iat\": 1596028218, \"exp\": 1596028518, \"token\": \"C3Sk2kLRVuPA\", \"email\": \"admin@example.com\", \"type\": \"access\", \"user_id\": \"VXNlcjoyMQ==\", \"is_staff\": true}, \"user\": {\"id\": \"VXNlcjoyMQ==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"userPermissions\": [{\"code\": \"MANAGE_APPS\", \"name\": \"Manage apps\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_CHECKOUTS\", \"name\": \"Manage checkouts\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_DISCOUNTS\", \"name\": \"Manage sales and vouchers.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_GIFT_CARD\", \"name\": \"Manage gift cards.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_MENUS\", \"name\": \"Manage navigation.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_ORDERS\", \"name\": \"Manage orders.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PAGES\", \"name\": \"Manage pages.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PLUGINS\", \"name\": \"Manage plugins\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCTS\", \"name\": \"Manage products.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SETTINGS\", \"name\": \"Manage settings.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SHIPPING\", \"name\": \"Manage shipping.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_STAFF\", \"name\": \"Manage staff.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_TRANSLATIONS\", \"name\": \"Manage translations.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_USERS\", \"name\": \"Manage customers.\", \"__typename\": \"UserPermission\"}], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"VerifyToken\"}}}]"
|
||||||
|
},
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "WSGIServer/0.2 CPython/3.8.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-origin",
|
||||||
|
"value": "http://localhost:9000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-methods",
|
||||||
|
"value": "POST, OPTIONS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-headers",
|
||||||
|
"value": "Origin, Content-Type, Accept, Authorization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "1619"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 336,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"redirectURL": "",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"startedDateTime": "2020-07-29T13:10:18.327Z",
|
||||||
|
"time": 23,
|
||||||
|
"timings": {
|
||||||
|
"blocked": -1,
|
||||||
|
"connect": -1,
|
||||||
|
"dns": -1,
|
||||||
|
"receive": 0,
|
||||||
|
"send": 0,
|
||||||
|
"ssl": -1,
|
||||||
|
"wait": 23
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [],
|
||||||
|
"version": "1.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"_recordingName": "User/will be logged in if has valid credentials",
|
||||||
|
"creator": {
|
||||||
|
"comment": "persister:fs",
|
||||||
|
"name": "Polly.JS",
|
||||||
|
"version": "4.3.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_id": "7c460842cac4a92c188d5451dfc533a2",
|
||||||
|
"_order": 0,
|
||||||
|
"cache": {},
|
||||||
|
"request": {
|
||||||
|
"bodySize": 587,
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept",
|
||||||
|
"value": "*/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "587"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_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\":\"TokenAuth\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"admin\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
|
},
|
||||||
|
"queryString": [],
|
||||||
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"bodySize": 1830,
|
||||||
|
"content": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"size": 1830,
|
||||||
|
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"rLPNMGNYKXH8VY4UNEWl4nEOFMseocljioigPl36IM2CqbdmOTEpNwvdHBAJ1ZWQ\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5NjAyODUxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU1RPT0iLCJpc19zdGFmZiI6dHJ1ZX0.eo8_Ew98HICB4cFQN2U7mCJ8ydGVOvQLGRT4CnkufMc\", \"user\": {\"id\": \"VXNlcjoyMQ==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"userPermissions\": [{\"code\": \"MANAGE_APPS\", \"name\": \"Manage apps\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_CHECKOUTS\", \"name\": \"Manage checkouts\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_DISCOUNTS\", \"name\": \"Manage sales and vouchers.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_GIFT_CARD\", \"name\": \"Manage gift cards.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_MENUS\", \"name\": \"Manage navigation.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_ORDERS\", \"name\": \"Manage orders.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PAGES\", \"name\": \"Manage pages.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PLUGINS\", \"name\": \"Manage plugins\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCTS\", \"name\": \"Manage products.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SETTINGS\", \"name\": \"Manage settings.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SHIPPING\", \"name\": \"Manage shipping.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_STAFF\", \"name\": \"Manage staff.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_TRANSLATIONS\", \"name\": \"Manage translations.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_USERS\", \"name\": \"Manage customers.\", \"__typename\": \"UserPermission\"}], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}]"
|
||||||
|
},
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"httpOnly": true,
|
||||||
|
"name": "refreshToken",
|
||||||
|
"path": "/",
|
||||||
|
"secure": true,
|
||||||
|
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5ODYyMDIxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lNUT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6InJMUE5NR05ZS1hIOFZZNFVORVdsNG5FT0ZNc2VvY2xqaW9pZ1BsMzZJTTJDcWJkbU9URXBOd3ZkSEJBSjFaV1EifQ.boD8G4pkSnZF-PLl5oOg85Uj-mqTiAzOkua9aAG3Bz4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "WSGIServer/0.2 CPython/3.8.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-origin",
|
||||||
|
"value": "http://localhost:9000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-methods",
|
||||||
|
"value": "POST, OPTIONS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-headers",
|
||||||
|
"value": "Origin, Content-Type, Accept, Authorization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "1830"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "set-cookie",
|
||||||
|
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5ODYyMDIxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lNUT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6InJMUE5NR05ZS1hIOFZZNFVORVdsNG5FT0ZNc2VvY2xqaW9pZ1BsMzZJTTJDcWJkbU9URXBOd3ZkSEJBSjFaV1EifQ.boD8G4pkSnZF-PLl5oOg85Uj-mqTiAzOkua9aAG3Bz4; HttpOnly; Path=/; Secure"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 768,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"redirectURL": "",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"startedDateTime": "2020-07-29T13:10:18.064Z",
|
||||||
|
"time": 118,
|
||||||
|
"timings": {
|
||||||
|
"blocked": -1,
|
||||||
|
"connect": -1,
|
||||||
|
"dns": -1,
|
||||||
|
"receive": 0,
|
||||||
|
"send": 0,
|
||||||
|
"ssl": -1,
|
||||||
|
"wait": 118
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [],
|
||||||
|
"version": "1.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"_recordingName": "User/will not be logged if has expired token",
|
||||||
|
"creator": {
|
||||||
|
"comment": "persister:fs",
|
||||||
|
"name": "Polly.JS",
|
||||||
|
"version": "4.3.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_id": "414f6b24b58b132c92e9a6ea67613a15",
|
||||||
|
"_order": 0,
|
||||||
|
"cache": {},
|
||||||
|
"request": {
|
||||||
|
"bodySize": 691,
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept",
|
||||||
|
"value": "*/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "691"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_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\":\"VerifyToken\",\"variables\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTUyMzk4OTcsImV4cCI6MTU5NTI0MDE5NywidG9rZW4iOiJxQ1Jia0dOMnpOT28iLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU1RPT0iLCJpc19zdGFmZiI6dHJ1ZX0.l-FnFDVmi5fASo7Uae2Emewu2pKyO2qLz7ZQl1fSzo4\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation VerifyToken($token: String!) {\\n tokenVerify(token: $token) {\\n payload\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
|
},
|
||||||
|
"queryString": [],
|
||||||
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"bodySize": 89,
|
||||||
|
"content": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"size": 89,
|
||||||
|
"text": "[{\"data\": {\"tokenVerify\": {\"payload\": null, \"user\": null, \"__typename\": \"VerifyToken\"}}}]"
|
||||||
|
},
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Tue, 21 Jul 2020 13:43:50 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "WSGIServer/0.2 CPython/3.8.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-origin",
|
||||||
|
"value": "*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-methods",
|
||||||
|
"value": "POST, OPTIONS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-headers",
|
||||||
|
"value": "Origin, Content-Type, Accept, Authorization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "89"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 314,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"redirectURL": "",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"startedDateTime": "2020-07-21T13:43:50.249Z",
|
||||||
|
"time": 17,
|
||||||
|
"timings": {
|
||||||
|
"blocked": -1,
|
||||||
|
"connect": -1,
|
||||||
|
"dns": -1,
|
||||||
|
"receive": 0,
|
||||||
|
"send": 0,
|
||||||
|
"ssl": -1,
|
||||||
|
"wait": 17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [],
|
||||||
|
"version": "1.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"_recordingName": "User/will not be logged if has invalid token",
|
||||||
|
"creator": {
|
||||||
|
"comment": "persister:fs",
|
||||||
|
"name": "Polly.JS",
|
||||||
|
"version": "4.3.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_id": "4836098613648775386c1e10728424dd",
|
||||||
|
"_order": 0,
|
||||||
|
"cache": {},
|
||||||
|
"request": {
|
||||||
|
"bodySize": 428,
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept",
|
||||||
|
"value": "*/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "428"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_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\":\"VerifyToken\",\"variables\":{\"token\":\"NotAToken\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation VerifyToken($token: String!) {\\n tokenVerify(token: $token) {\\n payload\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
|
},
|
||||||
|
"queryString": [],
|
||||||
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"bodySize": 89,
|
||||||
|
"content": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"size": 89,
|
||||||
|
"text": "[{\"data\": {\"tokenVerify\": {\"payload\": null, \"user\": null, \"__typename\": \"VerifyToken\"}}}]"
|
||||||
|
},
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "WSGIServer/0.2 CPython/3.8.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-origin",
|
||||||
|
"value": "http://localhost:9000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-methods",
|
||||||
|
"value": "POST, OPTIONS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-headers",
|
||||||
|
"value": "Origin, Content-Type, Accept, Authorization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "89"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 334,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"redirectURL": "",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"startedDateTime": "2020-07-29T13:10:18.368Z",
|
||||||
|
"time": 6,
|
||||||
|
"timings": {
|
||||||
|
"blocked": -1,
|
||||||
|
"connect": -1,
|
||||||
|
"dns": -1,
|
||||||
|
"receive": 0,
|
||||||
|
"send": 0,
|
||||||
|
"ssl": -1,
|
||||||
|
"wait": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [],
|
||||||
|
"version": "1.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"_recordingName": "User/will not be logged in if doesn't have valid credentials",
|
||||||
|
"creator": {
|
||||||
|
"comment": "persister:fs",
|
||||||
|
"name": "Polly.JS",
|
||||||
|
"version": "4.3.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_id": "86487093ff8b070d496fcdc566e01adf",
|
||||||
|
"_order": 0,
|
||||||
|
"cache": {},
|
||||||
|
"request": {
|
||||||
|
"bodySize": 603,
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept",
|
||||||
|
"value": "*/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "603"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_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\":\"TokenAuth\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"NotAValidPassword123!\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
|
},
|
||||||
|
"queryString": [],
|
||||||
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"bodySize": 214,
|
||||||
|
"content": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"size": 214,
|
||||||
|
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [{\"field\": \"email\", \"message\": \"Please, enter valid credentials\", \"__typename\": \"AccountError\"}], \"csrfToken\": null, \"token\": null, \"user\": null, \"__typename\": \"CreateToken\"}}}]"
|
||||||
|
},
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "WSGIServer/0.2 CPython/3.8.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-origin",
|
||||||
|
"value": "http://localhost:9000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-methods",
|
||||||
|
"value": "POST, OPTIONS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "access-control-allow-headers",
|
||||||
|
"value": "Origin, Content-Type, Accept, Authorization"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "214"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 335,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"redirectURL": "",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"startedDateTime": "2020-07-29T13:10:18.208Z",
|
||||||
|
"time": 99,
|
||||||
|
"timings": {
|
||||||
|
"blocked": -1,
|
||||||
|
"connect": -1,
|
||||||
|
"dns": -1,
|
||||||
|
"receive": 0,
|
||||||
|
"send": 0,
|
||||||
|
"ssl": -1,
|
||||||
|
"wait": 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [],
|
||||||
|
"version": "1.2"
|
||||||
|
}
|
||||||
|
}
|
77
src/auth/AuthProvider.test.ts
Normal file
77
src/auth/AuthProvider.test.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import setupApi from "@test/api";
|
||||||
|
import { act, renderHook } from "@testing-library/react-hooks";
|
||||||
|
import ApolloClient from "apollo-client";
|
||||||
|
|
||||||
|
import { useAuthProvider } from "./AuthProvider";
|
||||||
|
import { getTokens, setAuthToken } from "./utils";
|
||||||
|
|
||||||
|
const apolloClient = setupApi();
|
||||||
|
|
||||||
|
function renderAuthProvider(apolloClient: ApolloClient<any>) {
|
||||||
|
const intl = {
|
||||||
|
formatMessage: ({ defaultMessage }) => defaultMessage
|
||||||
|
};
|
||||||
|
const notify = jest.fn();
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useAuthProvider(intl as any, notify, apolloClient)
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = {
|
||||||
|
email: "admin@example.com",
|
||||||
|
password: "admin",
|
||||||
|
token: null
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("User", () => {
|
||||||
|
it("will be logged in if has valid credentials", async done => {
|
||||||
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
|
await act(() =>
|
||||||
|
hook.current.login(credentials.email, credentials.password)
|
||||||
|
);
|
||||||
|
expect(hook.current.userContext.email).toBe(credentials.email);
|
||||||
|
credentials.token = getTokens().auth;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will not be logged in if doesn't have valid credentials", async done => {
|
||||||
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
|
await act(() =>
|
||||||
|
hook.current.login(credentials.email, "NotAValidPassword123!")
|
||||||
|
);
|
||||||
|
expect(hook.current.userContext).toBe(null);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will be logged if has valid token", async done => {
|
||||||
|
setAuthToken(credentials.token, false);
|
||||||
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
|
await act(() => hook.current.autologinPromise.current);
|
||||||
|
expect(hook.current.userContext.email).toBe(credentials.email);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will not be logged if has invalid token", async done => {
|
||||||
|
setAuthToken("NotAToken", false);
|
||||||
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
|
await act(() => hook.current.autologinPromise.current);
|
||||||
|
expect(hook.current.userContext).toBe(undefined);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,228 +1,227 @@
|
||||||
|
import { IMessageContext } from "@saleor/components/messages";
|
||||||
import { DEMO_MODE } from "@saleor/config";
|
import { DEMO_MODE } from "@saleor/config";
|
||||||
import { User } from "@saleor/fragments/types/User";
|
import { User } from "@saleor/fragments/types/User";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { maybe } from "@saleor/misc";
|
import { getMutationStatus } from "@saleor/misc";
|
||||||
import {
|
import {
|
||||||
isSupported as isCredentialsManagementAPISupported,
|
isSupported as isCredentialsManagementAPISupported,
|
||||||
login as loginWithCredentialsManagementAPI,
|
login as loginWithCredentialsManagementAPI,
|
||||||
saveCredentials
|
saveCredentials
|
||||||
} from "@saleor/utils/credentialsManagement";
|
} from "@saleor/utils/credentialsManagement";
|
||||||
import React from "react";
|
import ApolloClient from "apollo-client";
|
||||||
import { MutationFunction, MutationResult } from "react-apollo";
|
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useApolloClient, useMutation } from "react-apollo";
|
||||||
|
import { IntlShape, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { UserContext } from "./";
|
import { UserContext } from "./";
|
||||||
import {
|
import {
|
||||||
TokenRefreshMutation,
|
tokenAuthMutation,
|
||||||
TypedTokenAuthMutation,
|
tokenRefreshMutation,
|
||||||
TypedVerifyTokenMutation
|
tokenVerifyMutation
|
||||||
} from "./mutations";
|
} from "./mutations";
|
||||||
import { RefreshToken, RefreshTokenVariables } from "./types/RefreshToken";
|
import { RefreshToken, RefreshTokenVariables } from "./types/RefreshToken";
|
||||||
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
||||||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
||||||
import {
|
import {
|
||||||
displayDemoMessage,
|
displayDemoMessage,
|
||||||
getAuthToken,
|
getTokens,
|
||||||
removeAuthToken,
|
removeTokens,
|
||||||
setAuthToken
|
setAuthToken,
|
||||||
|
setTokens
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
interface AuthProviderOperationsProps {
|
const persistToken = false;
|
||||||
children: (props: {
|
|
||||||
hasToken: boolean;
|
|
||||||
isAuthenticated: boolean;
|
|
||||||
tokenAuthLoading: boolean;
|
|
||||||
tokenVerifyLoading: boolean;
|
|
||||||
user: User;
|
|
||||||
}) => React.ReactNode;
|
|
||||||
}
|
|
||||||
const AuthProviderOperations: React.FC<AuthProviderOperationsProps> = ({
|
|
||||||
children
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const notify = useNotifier();
|
|
||||||
|
|
||||||
const handleLogin = () => {
|
export function useAuthProvider(
|
||||||
|
intl: IntlShape,
|
||||||
|
notify: IMessageContext,
|
||||||
|
apolloClient: ApolloClient<any>
|
||||||
|
) {
|
||||||
|
const [userContext, setUserContext] = useState<undefined | User>(undefined);
|
||||||
|
const autologinPromise = useRef<Promise<any>>();
|
||||||
|
const refreshPromise = useRef<Promise<boolean>>();
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
setUserContext(undefined);
|
||||||
|
if (isCredentialsManagementAPISupported) {
|
||||||
|
navigator.credentials.preventSilentAccess();
|
||||||
|
}
|
||||||
|
removeTokens();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [tokenAuth, tokenAuthResult] = useMutation<
|
||||||
|
TokenAuth,
|
||||||
|
TokenAuthVariables
|
||||||
|
>(tokenAuthMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
onCompleted: result => {
|
||||||
|
if (result.tokenCreate.errors.length > 0) {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = result.tokenCreate.user;
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
result.tokenCreate.token,
|
||||||
|
result.tokenCreate.csrfToken,
|
||||||
|
persistToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: logout
|
||||||
|
});
|
||||||
|
const [tokenRefresh] = useMutation<RefreshToken, RefreshTokenVariables>(
|
||||||
|
tokenRefreshMutation,
|
||||||
|
{
|
||||||
|
client: apolloClient,
|
||||||
|
onError: logout
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [tokenVerify, tokenVerifyResult] = useMutation<
|
||||||
|
VerifyToken,
|
||||||
|
VerifyTokenVariables
|
||||||
|
>(tokenVerifyMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLogin = () => {
|
||||||
if (DEMO_MODE) {
|
if (DEMO_MODE) {
|
||||||
displayDemoMessage(intl, notify);
|
displayDemoMessage(intl, notify);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<TypedTokenAuthMutation>
|
const token = getTokens().auth;
|
||||||
{(...tokenAuth) => (
|
if (!!token && !userContext) {
|
||||||
<TypedVerifyTokenMutation>
|
autologinPromise.current = tokenVerify({ variables: { token } });
|
||||||
{(...tokenVerify) => (
|
|
||||||
<TokenRefreshMutation>
|
|
||||||
{(...tokenRefresh) => (
|
|
||||||
<AuthProvider
|
|
||||||
tokenAuth={tokenAuth}
|
|
||||||
tokenVerify={tokenVerify}
|
|
||||||
tokenRefresh={tokenRefresh}
|
|
||||||
onLogin={handleLogin}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</AuthProvider>
|
|
||||||
)}
|
|
||||||
</TokenRefreshMutation>
|
|
||||||
)}
|
|
||||||
</TypedVerifyTokenMutation>
|
|
||||||
)}
|
|
||||||
</TypedTokenAuthMutation>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface AuthProviderProps {
|
|
||||||
children: (props: {
|
|
||||||
hasToken: boolean;
|
|
||||||
isAuthenticated: boolean;
|
|
||||||
tokenAuthLoading: boolean;
|
|
||||||
tokenVerifyLoading: boolean;
|
|
||||||
user: User;
|
|
||||||
}) => React.ReactNode;
|
|
||||||
tokenAuth: [
|
|
||||||
MutationFunction<TokenAuth, TokenAuthVariables>,
|
|
||||||
MutationResult<TokenAuth>
|
|
||||||
];
|
|
||||||
tokenVerify: [
|
|
||||||
MutationFunction<VerifyToken, VerifyTokenVariables>,
|
|
||||||
MutationResult<VerifyToken>
|
|
||||||
];
|
|
||||||
tokenRefresh: [
|
|
||||||
MutationFunction<RefreshToken, RefreshTokenVariables>,
|
|
||||||
MutationResult<RefreshToken>
|
|
||||||
];
|
|
||||||
onLogin?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthProviderState {
|
|
||||||
user: User;
|
|
||||||
persistToken: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthProvider extends React.Component<
|
|
||||||
AuthProviderProps,
|
|
||||||
AuthProviderState
|
|
||||||
> {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { persistToken: false, user: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(props: AuthProviderProps) {
|
|
||||||
const { tokenAuth, tokenVerify } = props;
|
|
||||||
const tokenAuthOpts = tokenAuth[1];
|
|
||||||
const tokenVerifyOpts = tokenVerify[1];
|
|
||||||
|
|
||||||
if (tokenAuthOpts.error || tokenVerifyOpts.error) {
|
|
||||||
this.logout();
|
|
||||||
}
|
|
||||||
if (tokenAuthOpts.data) {
|
|
||||||
const user = tokenAuthOpts.data.tokenCreate.user;
|
|
||||||
// FIXME: Now we set state also when auth fails and returned user is
|
|
||||||
// `null`, because the LoginView uses this `null` to display error.
|
|
||||||
this.setState({ user });
|
|
||||||
if (user) {
|
|
||||||
setAuthToken(
|
|
||||||
tokenAuthOpts.data.tokenCreate.token,
|
|
||||||
this.state.persistToken
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (maybe(() => tokenVerifyOpts.data.tokenVerify === null)) {
|
autologinPromise.current = loginWithCredentialsManagementAPI(login);
|
||||||
this.logout();
|
|
||||||
} else {
|
|
||||||
const user = maybe(() => tokenVerifyOpts.data.tokenVerify.user);
|
|
||||||
if (!!user) {
|
|
||||||
this.setState({ user });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
componentDidMount() {
|
const login = async (email: string, password: string) => {
|
||||||
const { user } = this.state;
|
const result = await tokenAuth({ variables: { email, password } });
|
||||||
const token = getAuthToken();
|
|
||||||
if (!!token && !user) {
|
|
||||||
this.verifyToken(token);
|
|
||||||
} else {
|
|
||||||
loginWithCredentialsManagementAPI(this.login);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
login = async (email: string, password: string) => {
|
|
||||||
const { tokenAuth, onLogin } = this.props;
|
|
||||||
const [tokenAuthFn] = tokenAuth;
|
|
||||||
|
|
||||||
tokenAuthFn({ variables: { email, password } }).then(result => {
|
|
||||||
if (result && !result.data.tokenCreate.errors.length) {
|
if (result && !result.data.tokenCreate.errors.length) {
|
||||||
if (!!onLogin) {
|
if (!!onLogin) {
|
||||||
onLogin();
|
onLogin();
|
||||||
}
|
}
|
||||||
saveCredentials(result.data.tokenCreate.user, password);
|
saveCredentials(result.data.tokenCreate.user, password);
|
||||||
|
|
||||||
|
return result.data.tokenCreate.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginByToken = (auth: string, refresh: string, user: User) => {
|
||||||
|
setUserContext(user);
|
||||||
|
setTokens(auth, refresh, persistToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshToken = (): Promise<boolean> => {
|
||||||
|
if (!!refreshPromise.current) {
|
||||||
|
return refreshPromise.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const token = getTokens().refresh;
|
||||||
|
|
||||||
|
return tokenRefresh({ variables: { token } }).then(refreshData => {
|
||||||
|
if (!!refreshData.data.tokenRefresh?.token) {
|
||||||
|
setAuthToken(refreshData.data.tokenRefresh.token, persistToken);
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
loginByToken = (token: string, user: User) => {
|
return {
|
||||||
this.setState({ user });
|
autologinPromise,
|
||||||
setAuthToken(token, this.state.persistToken);
|
login,
|
||||||
|
loginByToken,
|
||||||
|
logout,
|
||||||
|
refreshToken,
|
||||||
|
tokenAuthOpts,
|
||||||
|
tokenVerifyOpts,
|
||||||
|
userContext
|
||||||
};
|
};
|
||||||
|
|
||||||
logout = () => {
|
|
||||||
this.setState({ user: undefined });
|
|
||||||
if (isCredentialsManagementAPISupported) {
|
|
||||||
navigator.credentials.preventSilentAccess();
|
|
||||||
}
|
}
|
||||||
removeAuthToken();
|
|
||||||
};
|
|
||||||
|
|
||||||
verifyToken = (token: string) => {
|
interface AuthProviderProps {
|
||||||
const { tokenVerify } = this.props;
|
children: React.ReactNode;
|
||||||
const [tokenVerifyFn] = tokenVerify;
|
}
|
||||||
|
|
||||||
return tokenVerifyFn({ variables: { token } });
|
const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||||
};
|
const apolloClient = useApolloClient();
|
||||||
|
const intl = useIntl();
|
||||||
|
const notify = useNotifier();
|
||||||
|
|
||||||
refreshToken = async () => {
|
const {
|
||||||
const { tokenRefresh } = this.props;
|
login,
|
||||||
const [tokenRefreshFn] = tokenRefresh;
|
loginByToken,
|
||||||
const token = getAuthToken();
|
logout,
|
||||||
|
tokenAuthOpts,
|
||||||
const refreshData = await tokenRefreshFn({ variables: { token } });
|
refreshToken,
|
||||||
|
tokenVerifyOpts,
|
||||||
setAuthToken(refreshData.data.tokenRefresh.token, this.state.persistToken);
|
userContext
|
||||||
};
|
} = useAuthProvider(intl, notify, apolloClient);
|
||||||
|
|
||||||
render() {
|
|
||||||
const { children, tokenAuth, tokenVerify } = this.props;
|
|
||||||
const tokenAuthOpts = tokenAuth[1];
|
|
||||||
const tokenVerifyOpts = tokenVerify[1];
|
|
||||||
const { user } = this.state;
|
|
||||||
const isAuthenticated = !!user;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider
|
<UserContext.Provider
|
||||||
value={{
|
value={{
|
||||||
login: this.login,
|
login,
|
||||||
loginByToken: this.loginByToken,
|
loginByToken,
|
||||||
logout: this.logout,
|
logout,
|
||||||
tokenAuthLoading: tokenAuthOpts.loading,
|
tokenAuthLoading: tokenAuthOpts.loading,
|
||||||
tokenRefresh: this.refreshToken,
|
tokenRefresh: refreshToken,
|
||||||
tokenVerifyLoading: tokenVerifyOpts.loading,
|
tokenVerifyLoading: tokenVerifyOpts.loading,
|
||||||
user
|
user: userContext
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children({
|
{children}
|
||||||
hasToken: !!getAuthToken(),
|
|
||||||
isAuthenticated,
|
|
||||||
tokenAuthLoading: tokenAuthOpts.loading,
|
|
||||||
tokenVerifyLoading: tokenVerifyOpts.loading,
|
|
||||||
user
|
|
||||||
})}
|
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthProviderOperations;
|
export const useAuth = () => {
|
||||||
|
const user = useContext(UserContext);
|
||||||
|
const isAuthenticated = !!user.user;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasToken: !!getTokens(),
|
||||||
|
isAuthenticated,
|
||||||
|
tokenAuthLoading: user.tokenAuthLoading,
|
||||||
|
tokenVerifyLoading: user.tokenVerifyLoading,
|
||||||
|
user: user.user
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthProvider;
|
||||||
|
|
|
@ -2,8 +2,9 @@ import { findValueInEnum } from "@saleor/misc";
|
||||||
import { GraphQLError } from "graphql";
|
import { GraphQLError } from "graphql";
|
||||||
|
|
||||||
export enum JWTError {
|
export enum JWTError {
|
||||||
invalid = "JSONWebTokenError",
|
invalid = "InvalidTokenError",
|
||||||
expired = "JSONWebTokenExpired"
|
invalidSignature = "InvalidSignatureError",
|
||||||
|
expired = "ExpiredSignatureError"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isJwtError(error: GraphQLError): boolean {
|
export function isJwtError(error: GraphQLError): boolean {
|
||||||
|
|
|
@ -3,7 +3,6 @@ import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
|
|
||||||
import Layout from "./components/Layout";
|
import Layout from "./components/Layout";
|
||||||
import LoginLoading from "./components/LoginLoading";
|
|
||||||
import {
|
import {
|
||||||
newPasswordPath,
|
newPasswordPath,
|
||||||
passwordResetPath,
|
passwordResetPath,
|
||||||
|
@ -16,10 +15,10 @@ import ResetPasswordSuccess from "./views/ResetPasswordSuccess";
|
||||||
|
|
||||||
interface UserContext {
|
interface UserContext {
|
||||||
login: (username: string, password: string) => void;
|
login: (username: string, password: string) => void;
|
||||||
loginByToken: (token: string, user: User) => void;
|
loginByToken: (auth: string, csrf: string, user: User) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
tokenAuthLoading: boolean;
|
tokenAuthLoading: boolean;
|
||||||
tokenRefresh: () => Promise<void>;
|
tokenRefresh: () => Promise<boolean>;
|
||||||
tokenVerifyLoading: boolean;
|
tokenVerifyLoading: boolean;
|
||||||
user?: User;
|
user?: User;
|
||||||
}
|
}
|
||||||
|
@ -33,20 +32,12 @@ export const UserContext = React.createContext<UserContext>({
|
||||||
tokenVerifyLoading: false
|
tokenVerifyLoading: false
|
||||||
});
|
});
|
||||||
|
|
||||||
interface AuthRouterProps {
|
const AuthRouter: React.FC = () => (
|
||||||
hasToken: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthRouter: React.FC<AuthRouterProps> = ({ hasToken }) => (
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={passwordResetSuccessPath} component={ResetPasswordSuccess} />
|
<Route path={passwordResetSuccessPath} component={ResetPasswordSuccess} />
|
||||||
<Route path={passwordResetPath} component={ResetPassword} />
|
<Route path={passwordResetPath} component={ResetPassword} />
|
||||||
{!hasToken ? (
|
|
||||||
<Route path={newPasswordPath} component={NewPassword} />
|
<Route path={newPasswordPath} component={NewPassword} />
|
||||||
) : (
|
|
||||||
<LoginLoading />
|
|
||||||
)}
|
|
||||||
<Route component={LoginView} />
|
<Route component={LoginView} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
39
src/auth/link.ts
Normal file
39
src/auth/link.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { setContext } from "apollo-link-context";
|
||||||
|
import { ErrorResponse, onError } from "apollo-link-error";
|
||||||
|
|
||||||
|
import { getTokens, removeTokens } from "./";
|
||||||
|
import { isJwtError, JWTError } from "./errors";
|
||||||
|
|
||||||
|
interface ResponseError extends ErrorResponse {
|
||||||
|
networkError?: Error & {
|
||||||
|
statusCode?: number;
|
||||||
|
bodyText?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const invalidateTokenLink = onError((error: ResponseError) => {
|
||||||
|
if (
|
||||||
|
(error.networkError && error.networkError.statusCode === 401) ||
|
||||||
|
error.graphQLErrors?.some(isJwtError)
|
||||||
|
) {
|
||||||
|
if (error.graphQLErrors[0].extensions.code !== JWTError.expired) {
|
||||||
|
removeTokens();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tokenLink = setContext((_, context) => {
|
||||||
|
const authToken = getTokens().auth;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
headers: {
|
||||||
|
...context.headers,
|
||||||
|
Authorization: authToken ? `JWT ${authToken}` : null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = invalidateTokenLink.concat(tokenLink);
|
||||||
|
|
||||||
|
export default link;
|
|
@ -3,24 +3,22 @@ import { accountErrorFragment } from "@saleor/fragments/errors";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { TypedMutation } from "../mutations";
|
import { TypedMutation } from "../mutations";
|
||||||
import { RefreshToken, RefreshTokenVariables } from "./types/RefreshToken";
|
|
||||||
import {
|
import {
|
||||||
RequestPasswordReset,
|
RequestPasswordReset,
|
||||||
RequestPasswordResetVariables
|
RequestPasswordResetVariables
|
||||||
} from "./types/RequestPasswordReset";
|
} from "./types/RequestPasswordReset";
|
||||||
import { SetPassword, SetPasswordVariables } from "./types/SetPassword";
|
import { SetPassword, SetPasswordVariables } from "./types/SetPassword";
|
||||||
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
|
||||||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
|
||||||
|
|
||||||
export const tokenAuthMutation = gql`
|
export const tokenAuthMutation = gql`
|
||||||
${fragmentUser}
|
${fragmentUser}
|
||||||
mutation TokenAuth($email: String!, $password: String!) {
|
mutation TokenAuth($email: String!, $password: String!) {
|
||||||
tokenCreate(email: $email, password: $password) {
|
tokenCreate(email: $email, password: $password) {
|
||||||
token
|
errors: accountErrors {
|
||||||
errors {
|
|
||||||
field
|
field
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
|
csrfToken
|
||||||
|
token
|
||||||
user {
|
user {
|
||||||
...User
|
...User
|
||||||
}
|
}
|
||||||
|
@ -28,11 +26,6 @@ export const tokenAuthMutation = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypedTokenAuthMutation = TypedMutation<
|
|
||||||
TokenAuth,
|
|
||||||
TokenAuthVariables
|
|
||||||
>(tokenAuthMutation);
|
|
||||||
|
|
||||||
export const tokenVerifyMutation = gql`
|
export const tokenVerifyMutation = gql`
|
||||||
${fragmentUser}
|
${fragmentUser}
|
||||||
mutation VerifyToken($token: String!) {
|
mutation VerifyToken($token: String!) {
|
||||||
|
@ -45,10 +38,13 @@ export const tokenVerifyMutation = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypedVerifyTokenMutation = TypedMutation<
|
export const tokenRefreshMutation = gql`
|
||||||
VerifyToken,
|
mutation RefreshToken($token: String!) {
|
||||||
VerifyTokenVariables
|
tokenRefresh(csrfToken: $token) {
|
||||||
>(tokenVerifyMutation);
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const requestPasswordReset = gql`
|
export const requestPasswordReset = gql`
|
||||||
${accountErrorFragment}
|
${accountErrorFragment}
|
||||||
|
@ -73,6 +69,8 @@ export const setPassword = gql`
|
||||||
errors: accountErrors {
|
errors: accountErrors {
|
||||||
...AccountErrorFragment
|
...AccountErrorFragment
|
||||||
}
|
}
|
||||||
|
csrfToken
|
||||||
|
refreshToken
|
||||||
token
|
token
|
||||||
user {
|
user {
|
||||||
...User
|
...User
|
||||||
|
@ -84,15 +82,3 @@ export const SetPasswordMutation = TypedMutation<
|
||||||
SetPassword,
|
SetPassword,
|
||||||
SetPasswordVariables
|
SetPasswordVariables
|
||||||
>(setPassword);
|
>(setPassword);
|
||||||
|
|
||||||
const refreshToken = gql`
|
|
||||||
mutation RefreshToken($token: String!) {
|
|
||||||
tokenRefresh(csrfToken: $token) {
|
|
||||||
token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export const TokenRefreshMutation = TypedMutation<
|
|
||||||
RefreshToken,
|
|
||||||
RefreshTokenVariables
|
|
||||||
>(refreshToken);
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ export interface SetPassword_setPassword_user {
|
||||||
export interface SetPassword_setPassword {
|
export interface SetPassword_setPassword {
|
||||||
__typename: "SetPassword";
|
__typename: "SetPassword";
|
||||||
errors: SetPassword_setPassword_errors[];
|
errors: SetPassword_setPassword_errors[];
|
||||||
|
csrfToken: string | null;
|
||||||
|
refreshToken: string | null;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
user: SetPassword_setPassword_user | null;
|
user: SetPassword_setPassword_user | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { PermissionEnum } from "./../../types/globalTypes";
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface TokenAuth_tokenCreate_errors {
|
export interface TokenAuth_tokenCreate_errors {
|
||||||
__typename: "Error";
|
__typename: "AccountError";
|
||||||
field: string | null;
|
field: string | null;
|
||||||
message: string | null;
|
message: string | null;
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,9 @@ export interface TokenAuth_tokenCreate_user {
|
||||||
|
|
||||||
export interface TokenAuth_tokenCreate {
|
export interface TokenAuth_tokenCreate {
|
||||||
__typename: "CreateToken";
|
__typename: "CreateToken";
|
||||||
token: string | null;
|
|
||||||
errors: TokenAuth_tokenCreate_errors[];
|
errors: TokenAuth_tokenCreate_errors[];
|
||||||
|
csrfToken: string | null;
|
||||||
|
token: string | null;
|
||||||
user: TokenAuth_tokenCreate_user | null;
|
user: TokenAuth_tokenCreate_user | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
|
import { IMessageContext } from "@saleor/components/messages";
|
||||||
import { UseNotifierResult } from "@saleor/hooks/useNotifier";
|
import { UseNotifierResult } from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { ApolloError } from "apollo-client";
|
||||||
import { IntlShape } from "react-intl";
|
import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
const TOKEN_STORAGE_KEY = "dashboardAuth";
|
import { isJwtError, isTokenExpired } from "./errors";
|
||||||
|
|
||||||
export const getAuthToken = () =>
|
export enum TOKEN_STORAGE_KEY {
|
||||||
localStorage.getItem(TOKEN_STORAGE_KEY) ||
|
AUTH = "auth",
|
||||||
sessionStorage.getItem(TOKEN_STORAGE_KEY);
|
CSRF = "csrf"
|
||||||
|
}
|
||||||
|
|
||||||
export const setAuthToken = (token: string, persist: boolean) =>
|
export const getTokens = () => ({
|
||||||
persist
|
auth:
|
||||||
? localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
localStorage.getItem(TOKEN_STORAGE_KEY.AUTH) ||
|
||||||
: sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
|
sessionStorage.getItem(TOKEN_STORAGE_KEY.AUTH),
|
||||||
|
refresh:
|
||||||
|
localStorage.getItem(TOKEN_STORAGE_KEY.CSRF) ||
|
||||||
|
sessionStorage.getItem(TOKEN_STORAGE_KEY.CSRF)
|
||||||
|
});
|
||||||
|
|
||||||
export const removeAuthToken = () => {
|
export const setTokens = (auth: string, csrf: string, persist: boolean) => {
|
||||||
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
if (persist) {
|
||||||
sessionStorage.removeItem(TOKEN_STORAGE_KEY);
|
localStorage.setItem(TOKEN_STORAGE_KEY.AUTH, auth);
|
||||||
|
localStorage.setItem(TOKEN_STORAGE_KEY.CSRF, csrf);
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem(TOKEN_STORAGE_KEY.AUTH, auth);
|
||||||
|
sessionStorage.setItem(TOKEN_STORAGE_KEY.CSRF, csrf);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setAuthToken = (auth: string, persist: boolean) => {
|
||||||
|
if (persist) {
|
||||||
|
localStorage.setItem(TOKEN_STORAGE_KEY.AUTH, auth);
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem(TOKEN_STORAGE_KEY.AUTH, auth);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeTokens = () => {
|
||||||
|
localStorage.removeItem(TOKEN_STORAGE_KEY.AUTH);
|
||||||
|
sessionStorage.removeItem(TOKEN_STORAGE_KEY.AUTH);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const displayDemoMessage = (
|
export const displayDemoMessage = (
|
||||||
|
@ -26,3 +51,40 @@ export const displayDemoMessage = (
|
||||||
text: intl.formatMessage(commonMessages.demo)
|
text: intl.formatMessage(commonMessages.demo)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function handleQueryAuthError(
|
||||||
|
error: ApolloError,
|
||||||
|
notify: IMessageContext,
|
||||||
|
tokenRefresh: () => Promise<boolean>,
|
||||||
|
logout: () => void,
|
||||||
|
intl: IntlShape
|
||||||
|
) {
|
||||||
|
if (error.graphQLErrors.some(isJwtError)) {
|
||||||
|
if (error.graphQLErrors.every(isTokenExpired)) {
|
||||||
|
const success = await tokenRefresh();
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
logout();
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.sessionExpired)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logout();
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
!error.graphQLErrors.every(
|
||||||
|
err => err.extensions?.exception?.code === "PermissionDenied"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,11 @@ const NewPassword: React.FC<RouteComponentProps> = ({ location }) => {
|
||||||
|
|
||||||
const handleSetPassword = async (data: SetPassword) => {
|
const handleSetPassword = async (data: SetPassword) => {
|
||||||
if (data.setPassword.errors.length === 0) {
|
if (data.setPassword.errors.length === 0) {
|
||||||
loginByToken(data.setPassword.token, data.setPassword.user);
|
loginByToken(
|
||||||
|
data.setPassword.token,
|
||||||
|
data.setPassword.csrfToken,
|
||||||
|
data.setPassword.user
|
||||||
|
);
|
||||||
navigate("/", true);
|
navigate("/", true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { isJwtError } from "@saleor/auth/errors";
|
import { handleQueryAuthError } from "@saleor/auth";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { RequireAtLeastOne } from "@saleor/misc";
|
||||||
import { maybe, RequireAtLeastOne } from "@saleor/misc";
|
|
||||||
import { ApolloQueryResult } from "apollo-client";
|
import { ApolloQueryResult } from "apollo-client";
|
||||||
import { DocumentNode } from "graphql";
|
import { DocumentNode } from "graphql";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
@ -48,6 +47,14 @@ function makeQuery<TData, TVariables>(
|
||||||
},
|
},
|
||||||
errorPolicy: "all",
|
errorPolicy: "all",
|
||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network",
|
||||||
|
onError: error =>
|
||||||
|
handleQueryAuthError(
|
||||||
|
error,
|
||||||
|
notify,
|
||||||
|
user.tokenRefresh,
|
||||||
|
user.logout,
|
||||||
|
intl
|
||||||
|
),
|
||||||
skip,
|
skip,
|
||||||
variables
|
variables
|
||||||
});
|
});
|
||||||
|
@ -63,26 +70,6 @@ function makeQuery<TData, TVariables>(
|
||||||
}
|
}
|
||||||
}, [queryData.loading]);
|
}, [queryData.loading]);
|
||||||
|
|
||||||
if (queryData.error) {
|
|
||||||
if (queryData.error.graphQLErrors.some(isJwtError)) {
|
|
||||||
user.logout();
|
|
||||||
notify({
|
|
||||||
status: "error",
|
|
||||||
text: intl.formatMessage(commonMessages.sessionExpired)
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
!queryData.error.graphQLErrors.every(
|
|
||||||
err =>
|
|
||||||
maybe(() => err.extensions.exception.code) === "PermissionDenied"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
notify({
|
|
||||||
status: "error",
|
|
||||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadMore = (
|
const loadMore = (
|
||||||
mergeFunc: (previousResults: TData, fetchMoreResult: TData) => TData,
|
mergeFunc: (previousResults: TData, fetchMoreResult: TData) => TData,
|
||||||
extraVariables: RequireAtLeastOne<TVariables>
|
extraVariables: RequireAtLeastOne<TVariables>
|
||||||
|
|
|
@ -4,8 +4,6 @@ import { defaultDataIdFromObject, InMemoryCache } from "apollo-cache-inmemory";
|
||||||
import { ApolloClient } from "apollo-client";
|
import { ApolloClient } from "apollo-client";
|
||||||
import { ApolloLink } from "apollo-link";
|
import { ApolloLink } from "apollo-link";
|
||||||
import { BatchHttpLink } from "apollo-link-batch-http";
|
import { BatchHttpLink } from "apollo-link-batch-http";
|
||||||
import { setContext } from "apollo-link-context";
|
|
||||||
import { ErrorResponse, onError } from "apollo-link-error";
|
|
||||||
import { createUploadLink } from "apollo-upload-client";
|
import { createUploadLink } from "apollo-upload-client";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ApolloProvider } from "react-apollo";
|
import { ApolloProvider } from "react-apollo";
|
||||||
|
@ -19,11 +17,11 @@ import AppsSection from "./apps";
|
||||||
import { appsSection } from "./apps/urls";
|
import { appsSection } from "./apps/urls";
|
||||||
import AttributeSection from "./attributes";
|
import AttributeSection from "./attributes";
|
||||||
import { attributeSection } from "./attributes/urls";
|
import { attributeSection } from "./attributes/urls";
|
||||||
import Auth, { getAuthToken, removeAuthToken } from "./auth";
|
import Auth from "./auth";
|
||||||
import AuthProvider from "./auth/AuthProvider";
|
import AuthProvider, { useAuth } from "./auth/AuthProvider";
|
||||||
import LoginLoading from "./auth/components/LoginLoading/LoginLoading";
|
import LoginLoading from "./auth/components/LoginLoading/LoginLoading";
|
||||||
import SectionRoute from "./auth/components/SectionRoute";
|
import SectionRoute from "./auth/components/SectionRoute";
|
||||||
import { isJwtError } from "./auth/errors";
|
import authLink from "./auth/link";
|
||||||
import { hasPermission } from "./auth/misc";
|
import { hasPermission } from "./auth/misc";
|
||||||
import CategorySection from "./categories";
|
import CategorySection from "./categories";
|
||||||
import CollectionSection from "./collections";
|
import CollectionSection from "./collections";
|
||||||
|
@ -60,43 +58,15 @@ import { PermissionEnum } from "./types/globalTypes";
|
||||||
import WarehouseSection from "./warehouses";
|
import WarehouseSection from "./warehouses";
|
||||||
import { warehouseSection } from "./warehouses/urls";
|
import { warehouseSection } from "./warehouses/urls";
|
||||||
|
|
||||||
interface ResponseError extends ErrorResponse {
|
|
||||||
networkError?: Error & {
|
|
||||||
statusCode?: number;
|
|
||||||
bodyText?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.GTM_ID !== undefined) {
|
if (process.env.GTM_ID !== undefined) {
|
||||||
TagManager.initialize({ gtmId: GTM_ID });
|
TagManager.initialize({ gtmId: GTM_ID });
|
||||||
}
|
}
|
||||||
|
|
||||||
const invalidTokenLink = onError((error: ResponseError) => {
|
|
||||||
if (
|
|
||||||
(error.networkError && error.networkError.statusCode === 401) ||
|
|
||||||
error.graphQLErrors?.some(isJwtError)
|
|
||||||
) {
|
|
||||||
removeAuthToken();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const authLink = setContext((_, context) => {
|
|
||||||
const authToken = getAuthToken();
|
|
||||||
|
|
||||||
return {
|
|
||||||
...context,
|
|
||||||
headers: {
|
|
||||||
...context.headers,
|
|
||||||
Authorization: authToken ? `JWT ${authToken}` : null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// DON'T TOUCH THIS
|
// DON'T TOUCH THIS
|
||||||
// These are separate clients and do not share configs between themselves
|
// These are separate clients and do not share configs between themselves
|
||||||
// so we need to explicitly set them
|
// so we need to explicitly set them
|
||||||
const linkOptions = {
|
const linkOptions = {
|
||||||
credentials: "same-origin",
|
credentials: "include",
|
||||||
uri: API_URI
|
uri: API_URI
|
||||||
};
|
};
|
||||||
const uploadLink = createUploadLink(linkOptions);
|
const uploadLink = createUploadLink(linkOptions);
|
||||||
|
@ -122,7 +92,7 @@ const apolloClient = new ApolloClient({
|
||||||
return defaultDataIdFromObject(obj);
|
return defaultDataIdFromObject(obj);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
link: invalidTokenLink.concat(authLink.concat(link))
|
link: authLink.concat(link)
|
||||||
});
|
});
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
|
@ -138,7 +108,9 @@ const App: React.FC = () => {
|
||||||
<BackgroundTasksProvider>
|
<BackgroundTasksProvider>
|
||||||
<AppStateProvider>
|
<AppStateProvider>
|
||||||
<ShopProvider>
|
<ShopProvider>
|
||||||
|
<AuthProvider>
|
||||||
<Routes />
|
<Routes />
|
||||||
|
</AuthProvider>
|
||||||
</ShopProvider>
|
</ShopProvider>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
</BackgroundTasksProvider>
|
</BackgroundTasksProvider>
|
||||||
|
@ -154,19 +126,18 @@ const App: React.FC = () => {
|
||||||
const Routes: React.FC = () => {
|
const Routes: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [, dispatchAppState] = useAppState();
|
const [, dispatchAppState] = useAppState();
|
||||||
|
const {
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WindowTitle title={intl.formatMessage(commonMessages.dashboard)} />
|
|
||||||
<AuthProvider>
|
|
||||||
{({
|
|
||||||
hasToken,
|
hasToken,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
tokenAuthLoading,
|
tokenAuthLoading,
|
||||||
tokenVerifyLoading,
|
tokenVerifyLoading,
|
||||||
user
|
user
|
||||||
}) =>
|
} = useAuth();
|
||||||
isAuthenticated && !tokenAuthLoading && !tokenVerifyLoading ? (
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WindowTitle title={intl.formatMessage(commonMessages.dashboard)} />
|
||||||
|
{isAuthenticated && !tokenAuthLoading && !tokenVerifyLoading ? (
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<Navigator />
|
<Navigator />
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
|
@ -277,9 +248,7 @@ const Routes: React.FC = () => {
|
||||||
component={WarehouseSection}
|
component={WarehouseSection}
|
||||||
/>
|
/>
|
||||||
{createConfigurationMenu(intl).filter(menu =>
|
{createConfigurationMenu(intl).filter(menu =>
|
||||||
menu.menuItems.map(item =>
|
menu.menuItems.map(item => hasPermission(item.permission, user))
|
||||||
hasPermission(item.permission, user)
|
|
||||||
)
|
|
||||||
).length > 0 && (
|
).length > 0 && (
|
||||||
<SectionRoute
|
<SectionRoute
|
||||||
exact
|
exact
|
||||||
|
@ -294,10 +263,8 @@ const Routes: React.FC = () => {
|
||||||
) : hasToken && tokenVerifyLoading ? (
|
) : hasToken && tokenVerifyLoading ? (
|
||||||
<LoginLoading />
|
<LoginLoading />
|
||||||
) : (
|
) : (
|
||||||
<Auth hasToken={hasToken} />
|
<Auth />
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</AuthProvider>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -100,8 +100,8 @@ const getEventMessage = (event: OrderDetails_order_events, intl: IntlShape) => {
|
||||||
description: "order history message"
|
description: "order history message"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
invoiceNumber: event.invoiceNumber,
|
generatedBy: event.user ? event.user.email : null,
|
||||||
generatedBy: event.user ? event.user.email : null
|
invoiceNumber: event.invoiceNumber
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
case OrderEventsEnum.INVOICE_UPDATED:
|
case OrderEventsEnum.INVOICE_UPDATED:
|
||||||
|
|
|
@ -4,12 +4,11 @@ import React from "react";
|
||||||
import { Query, QueryResult } from "react-apollo";
|
import { Query, QueryResult } from "react-apollo";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { isJwtError } from "./auth/errors";
|
import { handleQueryAuthError } from "./auth";
|
||||||
import useAppState from "./hooks/useAppState";
|
import useAppState from "./hooks/useAppState";
|
||||||
import useNotifier from "./hooks/useNotifier";
|
import useNotifier from "./hooks/useNotifier";
|
||||||
import useUser from "./hooks/useUser";
|
import useUser from "./hooks/useUser";
|
||||||
import { commonMessages } from "./intl";
|
import { RequireAtLeastOne } from "./misc";
|
||||||
import { maybe, RequireAtLeastOne } from "./misc";
|
|
||||||
|
|
||||||
export interface LoadMore<TData, TVariables> {
|
export interface LoadMore<TData, TVariables> {
|
||||||
loadMore: (
|
loadMore: (
|
||||||
|
@ -79,29 +78,17 @@ export function TypedQuery<TData, TVariables>(
|
||||||
skip={skip}
|
skip={skip}
|
||||||
context={{ useBatching: true }}
|
context={{ useBatching: true }}
|
||||||
errorPolicy="all"
|
errorPolicy="all"
|
||||||
|
onError={error =>
|
||||||
|
handleQueryAuthError(
|
||||||
|
error,
|
||||||
|
notify,
|
||||||
|
user.tokenRefresh,
|
||||||
|
user.logout,
|
||||||
|
intl
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{(queryData: QueryResult<TData, TVariables>) => {
|
{(queryData: QueryResult<TData, TVariables>) => {
|
||||||
if (queryData.error) {
|
|
||||||
if (queryData.error.graphQLErrors.some(isJwtError)) {
|
|
||||||
user.logout();
|
|
||||||
notify({
|
|
||||||
status: "error",
|
|
||||||
text: intl.formatMessage(commonMessages.sessionExpired)
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
!queryData.error.graphQLErrors.every(
|
|
||||||
err =>
|
|
||||||
maybe(() => err.extensions.exception.code) ===
|
|
||||||
"PermissionDenied"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
notify({
|
|
||||||
status: "error",
|
|
||||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadMore = (
|
const loadMore = (
|
||||||
mergeFunc: (
|
mergeFunc: (
|
||||||
previousResults: TData,
|
previousResults: TData,
|
||||||
|
|
|
@ -3,14 +3,16 @@ import { User } from "@saleor/fragments/types/User";
|
||||||
export const isSupported =
|
export const isSupported =
|
||||||
navigator.credentials && navigator.credentials.preventSilentAccess;
|
navigator.credentials && navigator.credentials.preventSilentAccess;
|
||||||
|
|
||||||
export function login(loginFn: (id: string, password: string) => void) {
|
export function login<T>(loginFn: (id: string, password: string) => T): T {
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
navigator.credentials.get({ password: true }).then(credential => {
|
navigator.credentials.get({ password: true }).then(credential => {
|
||||||
if (credential instanceof PasswordCredential) {
|
if (credential instanceof PasswordCredential) {
|
||||||
loginFn(credential.id, credential.password);
|
return loginFn(credential.id, credential.password);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveCredentials(user: User, password: string) {
|
export function saveCredentials(user: User, password: string) {
|
||||||
|
|
51
testUtils/api.ts
Normal file
51
testUtils/api.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import NodeHttpAdapter from "@pollyjs/adapter-node-http";
|
||||||
|
import { Polly } from "@pollyjs/core";
|
||||||
|
import FSPersister from "@pollyjs/persister-fs";
|
||||||
|
import { InMemoryCache } from "apollo-cache-inmemory";
|
||||||
|
import ApolloClient from "apollo-client";
|
||||||
|
import { BatchHttpLink } from "apollo-link-batch-http";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import path from "path";
|
||||||
|
import { setupPolly } from "setup-polly-jest";
|
||||||
|
|
||||||
|
Polly.register(NodeHttpAdapter);
|
||||||
|
Polly.register(FSPersister);
|
||||||
|
|
||||||
|
function setupApi() {
|
||||||
|
setupPolly({
|
||||||
|
adapters: ["node-http"],
|
||||||
|
matchRequestsBy: {
|
||||||
|
headers: false,
|
||||||
|
url: {
|
||||||
|
hash: false,
|
||||||
|
hostname: false,
|
||||||
|
password: false,
|
||||||
|
pathname: false,
|
||||||
|
port: false,
|
||||||
|
protocol: false,
|
||||||
|
query: false,
|
||||||
|
username: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persister: "fs",
|
||||||
|
persisterOptions: {
|
||||||
|
fs: {
|
||||||
|
recordingsDir: path.resolve(__dirname, "../recordings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const cache = new InMemoryCache();
|
||||||
|
const link = new BatchHttpLink({
|
||||||
|
// @ts-ignore
|
||||||
|
fetch,
|
||||||
|
uri: process.env.API_URI || "http://localhost:8000/graphql/"
|
||||||
|
});
|
||||||
|
const apolloClient = new ApolloClient({
|
||||||
|
cache,
|
||||||
|
link
|
||||||
|
});
|
||||||
|
|
||||||
|
return apolloClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default setupApi;
|
Loading…
Reference in a new issue