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 Cypress e2e runner - #584 by @krzysztofwolski
|
||||
- create Apps - #599 by @AlicjaSzu
|
||||
- Refactor authorization - #624 by @dominik-zeglen
|
||||
|
||||
## 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-typescript": "^7.7.4",
|
||||
"@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/react": "^5.1.9",
|
||||
"@testing-library/react-hooks": "^1.1.0",
|
||||
|
@ -93,6 +96,9 @@
|
|||
"@types/jest": "^24.0.24",
|
||||
"@types/lodash-es": "^4.17.3",
|
||||
"@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-dom": "^16.8.5",
|
||||
"@types/react-dropzone": "^4.2.2",
|
||||
|
@ -103,6 +109,7 @@
|
|||
"@types/react-sortable-tree": "^0.3.6",
|
||||
"@types/react-test-renderer": "^16.8.2",
|
||||
"@types/semver-compare": "^1.0.1",
|
||||
"@types/setup-polly-jest": "^0.5.0",
|
||||
"@types/storybook__addon-storyshots": "^3.4.9",
|
||||
"@types/storybook__react": "^4.0.2",
|
||||
"@types/url-join": "^4.0.0",
|
||||
|
@ -133,8 +140,10 @@
|
|||
"husky": "^3.0.8",
|
||||
"jest": "^24.8.0",
|
||||
"jest-file": "^1.0.0",
|
||||
"jest-localstorage-mock": "^2.4.3",
|
||||
"lint-staged": "^9.4.2",
|
||||
"mock-apollo-client": "^0.4.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"prettier": "^1.19.1",
|
||||
"react-intl-translations-manager": "^5.0.3",
|
||||
"react-test-renderer": "^16.12.0",
|
||||
|
@ -142,6 +151,8 @@
|
|||
"require-context.macro": "^1.1.1",
|
||||
"rimraf": "^3.0.0",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"setup-polly-jest": "^0.8.0",
|
||||
"testcafe": "^1.3.3",
|
||||
"ts-jest": "^24.2.0",
|
||||
"tsconfig-paths-webpack-plugin": "^3.2.0",
|
||||
"webpack": "^4.35.3",
|
||||
|
@ -152,6 +163,9 @@
|
|||
"fsevents": "^1.2.9"
|
||||
},
|
||||
"jest": {
|
||||
"setupFiles": [
|
||||
"jest-localstorage-mock"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.(jsx?|tsx?)$": "babel-jest",
|
||||
"^.+\\.(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 { User } from "@saleor/fragments/types/User";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { getMutationStatus } from "@saleor/misc";
|
||||
import {
|
||||
isSupported as isCredentialsManagementAPISupported,
|
||||
login as loginWithCredentialsManagementAPI,
|
||||
saveCredentials
|
||||
} from "@saleor/utils/credentialsManagement";
|
||||
import React from "react";
|
||||
import { MutationFunction, MutationResult } from "react-apollo";
|
||||
import { useIntl } from "react-intl";
|
||||
import ApolloClient from "apollo-client";
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useApolloClient, useMutation } from "react-apollo";
|
||||
import { IntlShape, useIntl } from "react-intl";
|
||||
|
||||
import { UserContext } from "./";
|
||||
import {
|
||||
TokenRefreshMutation,
|
||||
TypedTokenAuthMutation,
|
||||
TypedVerifyTokenMutation
|
||||
tokenAuthMutation,
|
||||
tokenRefreshMutation,
|
||||
tokenVerifyMutation
|
||||
} from "./mutations";
|
||||
import { RefreshToken, RefreshTokenVariables } from "./types/RefreshToken";
|
||||
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
||||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
||||
import {
|
||||
displayDemoMessage,
|
||||
getAuthToken,
|
||||
removeAuthToken,
|
||||
setAuthToken
|
||||
getTokens,
|
||||
removeTokens,
|
||||
setAuthToken,
|
||||
setTokens
|
||||
} from "./utils";
|
||||
|
||||
interface AuthProviderOperationsProps {
|
||||
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 persistToken = false;
|
||||
|
||||
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) {
|
||||
displayDemoMessage(intl, notify);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TypedTokenAuthMutation>
|
||||
{(...tokenAuth) => (
|
||||
<TypedVerifyTokenMutation>
|
||||
{(...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
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
const token = getTokens().auth;
|
||||
if (!!token && !userContext) {
|
||||
autologinPromise.current = tokenVerify({ variables: { token } });
|
||||
} else {
|
||||
if (maybe(() => tokenVerifyOpts.data.tokenVerify === null)) {
|
||||
this.logout();
|
||||
} else {
|
||||
const user = maybe(() => tokenVerifyOpts.data.tokenVerify.user);
|
||||
if (!!user) {
|
||||
this.setState({ user });
|
||||
}
|
||||
}
|
||||
}
|
||||
autologinPromise.current = loginWithCredentialsManagementAPI(login);
|
||||
}
|
||||
}, []);
|
||||
|
||||
componentDidMount() {
|
||||
const { user } = this.state;
|
||||
const token = getAuthToken();
|
||||
if (!!token && !user) {
|
||||
this.verifyToken(token);
|
||||
} else {
|
||||
loginWithCredentialsManagementAPI(this.login);
|
||||
}
|
||||
}
|
||||
const login = async (email: string, password: string) => {
|
||||
const result = await tokenAuth({ variables: { email, password } });
|
||||
|
||||
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 (!!onLogin) {
|
||||
onLogin();
|
||||
}
|
||||
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) => {
|
||||
this.setState({ user });
|
||||
setAuthToken(token, this.state.persistToken);
|
||||
return {
|
||||
autologinPromise,
|
||||
login,
|
||||
loginByToken,
|
||||
logout,
|
||||
refreshToken,
|
||||
tokenAuthOpts,
|
||||
tokenVerifyOpts,
|
||||
userContext
|
||||
};
|
||||
}
|
||||
|
||||
logout = () => {
|
||||
this.setState({ user: undefined });
|
||||
if (isCredentialsManagementAPISupported) {
|
||||
navigator.credentials.preventSilentAccess();
|
||||
}
|
||||
removeAuthToken();
|
||||
};
|
||||
interface AuthProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
verifyToken = (token: string) => {
|
||||
const { tokenVerify } = this.props;
|
||||
const [tokenVerifyFn] = tokenVerify;
|
||||
const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const apolloClient = useApolloClient();
|
||||
const intl = useIntl();
|
||||
const notify = useNotifier();
|
||||
|
||||
return tokenVerifyFn({ variables: { token } });
|
||||
};
|
||||
|
||||
refreshToken = async () => {
|
||||
const { tokenRefresh } = this.props;
|
||||
const [tokenRefreshFn] = tokenRefresh;
|
||||
const token = getAuthToken();
|
||||
|
||||
const refreshData = await tokenRefreshFn({ variables: { token } });
|
||||
|
||||
setAuthToken(refreshData.data.tokenRefresh.token, this.state.persistToken);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, tokenAuth, tokenVerify } = this.props;
|
||||
const tokenAuthOpts = tokenAuth[1];
|
||||
const tokenVerifyOpts = tokenVerify[1];
|
||||
const { user } = this.state;
|
||||
const isAuthenticated = !!user;
|
||||
const {
|
||||
login,
|
||||
loginByToken,
|
||||
logout,
|
||||
tokenAuthOpts,
|
||||
refreshToken,
|
||||
tokenVerifyOpts,
|
||||
userContext
|
||||
} = useAuthProvider(intl, notify, apolloClient);
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
login: this.login,
|
||||
loginByToken: this.loginByToken,
|
||||
logout: this.logout,
|
||||
login,
|
||||
loginByToken,
|
||||
logout,
|
||||
tokenAuthLoading: tokenAuthOpts.loading,
|
||||
tokenRefresh: this.refreshToken,
|
||||
tokenRefresh: refreshToken,
|
||||
tokenVerifyLoading: tokenVerifyOpts.loading,
|
||||
user
|
||||
user: userContext
|
||||
}}
|
||||
>
|
||||
{children({
|
||||
hasToken: !!getAuthToken(),
|
||||
isAuthenticated,
|
||||
tokenAuthLoading: tokenAuthOpts.loading,
|
||||
tokenVerifyLoading: tokenVerifyOpts.loading,
|
||||
user
|
||||
})}
|
||||
{children}
|
||||
</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";
|
||||
|
||||
export enum JWTError {
|
||||
invalid = "JSONWebTokenError",
|
||||
expired = "JSONWebTokenExpired"
|
||||
invalid = "InvalidTokenError",
|
||||
invalidSignature = "InvalidSignatureError",
|
||||
expired = "ExpiredSignatureError"
|
||||
}
|
||||
|
||||
export function isJwtError(error: GraphQLError): boolean {
|
||||
|
|
|
@ -3,7 +3,6 @@ import React from "react";
|
|||
import { Route, Switch } from "react-router-dom";
|
||||
|
||||
import Layout from "./components/Layout";
|
||||
import LoginLoading from "./components/LoginLoading";
|
||||
import {
|
||||
newPasswordPath,
|
||||
passwordResetPath,
|
||||
|
@ -16,10 +15,10 @@ import ResetPasswordSuccess from "./views/ResetPasswordSuccess";
|
|||
|
||||
interface UserContext {
|
||||
login: (username: string, password: string) => void;
|
||||
loginByToken: (token: string, user: User) => void;
|
||||
loginByToken: (auth: string, csrf: string, user: User) => void;
|
||||
logout: () => void;
|
||||
tokenAuthLoading: boolean;
|
||||
tokenRefresh: () => Promise<void>;
|
||||
tokenRefresh: () => Promise<boolean>;
|
||||
tokenVerifyLoading: boolean;
|
||||
user?: User;
|
||||
}
|
||||
|
@ -33,20 +32,12 @@ export const UserContext = React.createContext<UserContext>({
|
|||
tokenVerifyLoading: false
|
||||
});
|
||||
|
||||
interface AuthRouterProps {
|
||||
hasToken: boolean;
|
||||
}
|
||||
|
||||
const AuthRouter: React.FC<AuthRouterProps> = ({ hasToken }) => (
|
||||
const AuthRouter: React.FC = () => (
|
||||
<Layout>
|
||||
<Switch>
|
||||
<Route path={passwordResetSuccessPath} component={ResetPasswordSuccess} />
|
||||
<Route path={passwordResetPath} component={ResetPassword} />
|
||||
{!hasToken ? (
|
||||
<Route path={newPasswordPath} component={NewPassword} />
|
||||
) : (
|
||||
<LoginLoading />
|
||||
)}
|
||||
<Route component={LoginView} />
|
||||
</Switch>
|
||||
</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 { TypedMutation } from "../mutations";
|
||||
import { RefreshToken, RefreshTokenVariables } from "./types/RefreshToken";
|
||||
import {
|
||||
RequestPasswordReset,
|
||||
RequestPasswordResetVariables
|
||||
} from "./types/RequestPasswordReset";
|
||||
import { SetPassword, SetPasswordVariables } from "./types/SetPassword";
|
||||
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
||||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
||||
|
||||
export const tokenAuthMutation = gql`
|
||||
${fragmentUser}
|
||||
mutation TokenAuth($email: String!, $password: String!) {
|
||||
tokenCreate(email: $email, password: $password) {
|
||||
token
|
||||
errors {
|
||||
errors: accountErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
csrfToken
|
||||
token
|
||||
user {
|
||||
...User
|
||||
}
|
||||
|
@ -28,11 +26,6 @@ export const tokenAuthMutation = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const TypedTokenAuthMutation = TypedMutation<
|
||||
TokenAuth,
|
||||
TokenAuthVariables
|
||||
>(tokenAuthMutation);
|
||||
|
||||
export const tokenVerifyMutation = gql`
|
||||
${fragmentUser}
|
||||
mutation VerifyToken($token: String!) {
|
||||
|
@ -45,10 +38,13 @@ export const tokenVerifyMutation = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const TypedVerifyTokenMutation = TypedMutation<
|
||||
VerifyToken,
|
||||
VerifyTokenVariables
|
||||
>(tokenVerifyMutation);
|
||||
export const tokenRefreshMutation = gql`
|
||||
mutation RefreshToken($token: String!) {
|
||||
tokenRefresh(csrfToken: $token) {
|
||||
token
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const requestPasswordReset = gql`
|
||||
${accountErrorFragment}
|
||||
|
@ -73,6 +69,8 @@ export const setPassword = gql`
|
|||
errors: accountErrors {
|
||||
...AccountErrorFragment
|
||||
}
|
||||
csrfToken
|
||||
refreshToken
|
||||
token
|
||||
user {
|
||||
...User
|
||||
|
@ -84,15 +82,3 @@ export const SetPasswordMutation = TypedMutation<
|
|||
SetPassword,
|
||||
SetPasswordVariables
|
||||
>(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 {
|
||||
__typename: "SetPassword";
|
||||
errors: SetPassword_setPassword_errors[];
|
||||
csrfToken: string | null;
|
||||
refreshToken: string | null;
|
||||
token: string | null;
|
||||
user: SetPassword_setPassword_user | null;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { PermissionEnum } from "./../../types/globalTypes";
|
|||
// ====================================================
|
||||
|
||||
export interface TokenAuth_tokenCreate_errors {
|
||||
__typename: "Error";
|
||||
__typename: "AccountError";
|
||||
field: string | null;
|
||||
message: string | null;
|
||||
}
|
||||
|
@ -37,8 +37,9 @@ export interface TokenAuth_tokenCreate_user {
|
|||
|
||||
export interface TokenAuth_tokenCreate {
|
||||
__typename: "CreateToken";
|
||||
token: string | null;
|
||||
errors: TokenAuth_tokenCreate_errors[];
|
||||
csrfToken: string | null;
|
||||
token: string | null;
|
||||
user: TokenAuth_tokenCreate_user | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,46 @@
|
|||
import { IMessageContext } from "@saleor/components/messages";
|
||||
import { UseNotifierResult } from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { ApolloError } from "apollo-client";
|
||||
import { IntlShape } from "react-intl";
|
||||
|
||||
const TOKEN_STORAGE_KEY = "dashboardAuth";
|
||||
import { isJwtError, isTokenExpired } from "./errors";
|
||||
|
||||
export const getAuthToken = () =>
|
||||
localStorage.getItem(TOKEN_STORAGE_KEY) ||
|
||||
sessionStorage.getItem(TOKEN_STORAGE_KEY);
|
||||
export enum TOKEN_STORAGE_KEY {
|
||||
AUTH = "auth",
|
||||
CSRF = "csrf"
|
||||
}
|
||||
|
||||
export const setAuthToken = (token: string, persist: boolean) =>
|
||||
persist
|
||||
? localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
||||
: sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
|
||||
export const getTokens = () => ({
|
||||
auth:
|
||||
localStorage.getItem(TOKEN_STORAGE_KEY.AUTH) ||
|
||||
sessionStorage.getItem(TOKEN_STORAGE_KEY.AUTH),
|
||||
refresh:
|
||||
localStorage.getItem(TOKEN_STORAGE_KEY.CSRF) ||
|
||||
sessionStorage.getItem(TOKEN_STORAGE_KEY.CSRF)
|
||||
});
|
||||
|
||||
export const removeAuthToken = () => {
|
||||
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||
sessionStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||
export const setTokens = (auth: string, csrf: string, persist: boolean) => {
|
||||
if (persist) {
|
||||
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 = (
|
||||
|
@ -26,3 +51,40 @@ export const displayDemoMessage = (
|
|||
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) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { isJwtError } from "@saleor/auth/errors";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { maybe, RequireAtLeastOne } from "@saleor/misc";
|
||||
import { handleQueryAuthError } from "@saleor/auth";
|
||||
import { RequireAtLeastOne } from "@saleor/misc";
|
||||
import { ApolloQueryResult } from "apollo-client";
|
||||
import { DocumentNode } from "graphql";
|
||||
import { useEffect } from "react";
|
||||
|
@ -48,6 +47,14 @@ function makeQuery<TData, TVariables>(
|
|||
},
|
||||
errorPolicy: "all",
|
||||
fetchPolicy: "cache-and-network",
|
||||
onError: error =>
|
||||
handleQueryAuthError(
|
||||
error,
|
||||
notify,
|
||||
user.tokenRefresh,
|
||||
user.logout,
|
||||
intl
|
||||
),
|
||||
skip,
|
||||
variables
|
||||
});
|
||||
|
@ -63,26 +70,6 @@ function makeQuery<TData, TVariables>(
|
|||
}
|
||||
}, [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 = (
|
||||
mergeFunc: (previousResults: TData, fetchMoreResult: TData) => TData,
|
||||
extraVariables: RequireAtLeastOne<TVariables>
|
||||
|
|
|
@ -4,8 +4,6 @@ import { defaultDataIdFromObject, InMemoryCache } from "apollo-cache-inmemory";
|
|||
import { ApolloClient } from "apollo-client";
|
||||
import { ApolloLink } from "apollo-link";
|
||||
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 React from "react";
|
||||
import { ApolloProvider } from "react-apollo";
|
||||
|
@ -19,11 +17,11 @@ import AppsSection from "./apps";
|
|||
import { appsSection } from "./apps/urls";
|
||||
import AttributeSection from "./attributes";
|
||||
import { attributeSection } from "./attributes/urls";
|
||||
import Auth, { getAuthToken, removeAuthToken } from "./auth";
|
||||
import AuthProvider from "./auth/AuthProvider";
|
||||
import Auth from "./auth";
|
||||
import AuthProvider, { useAuth } from "./auth/AuthProvider";
|
||||
import LoginLoading from "./auth/components/LoginLoading/LoginLoading";
|
||||
import SectionRoute from "./auth/components/SectionRoute";
|
||||
import { isJwtError } from "./auth/errors";
|
||||
import authLink from "./auth/link";
|
||||
import { hasPermission } from "./auth/misc";
|
||||
import CategorySection from "./categories";
|
||||
import CollectionSection from "./collections";
|
||||
|
@ -60,43 +58,15 @@ import { PermissionEnum } from "./types/globalTypes";
|
|||
import WarehouseSection from "./warehouses";
|
||||
import { warehouseSection } from "./warehouses/urls";
|
||||
|
||||
interface ResponseError extends ErrorResponse {
|
||||
networkError?: Error & {
|
||||
statusCode?: number;
|
||||
bodyText?: string;
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.GTM_ID !== undefined) {
|
||||
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
|
||||
// These are separate clients and do not share configs between themselves
|
||||
// so we need to explicitly set them
|
||||
const linkOptions = {
|
||||
credentials: "same-origin",
|
||||
credentials: "include",
|
||||
uri: API_URI
|
||||
};
|
||||
const uploadLink = createUploadLink(linkOptions);
|
||||
|
@ -122,7 +92,7 @@ const apolloClient = new ApolloClient({
|
|||
return defaultDataIdFromObject(obj);
|
||||
}
|
||||
}),
|
||||
link: invalidTokenLink.concat(authLink.concat(link))
|
||||
link: authLink.concat(link)
|
||||
});
|
||||
|
||||
const App: React.FC = () => {
|
||||
|
@ -138,7 +108,9 @@ const App: React.FC = () => {
|
|||
<BackgroundTasksProvider>
|
||||
<AppStateProvider>
|
||||
<ShopProvider>
|
||||
<AuthProvider>
|
||||
<Routes />
|
||||
</AuthProvider>
|
||||
</ShopProvider>
|
||||
</AppStateProvider>
|
||||
</BackgroundTasksProvider>
|
||||
|
@ -154,19 +126,18 @@ const App: React.FC = () => {
|
|||
const Routes: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const [, dispatchAppState] = useAppState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(commonMessages.dashboard)} />
|
||||
<AuthProvider>
|
||||
{({
|
||||
const {
|
||||
hasToken,
|
||||
isAuthenticated,
|
||||
tokenAuthLoading,
|
||||
tokenVerifyLoading,
|
||||
user
|
||||
}) =>
|
||||
isAuthenticated && !tokenAuthLoading && !tokenVerifyLoading ? (
|
||||
} = useAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(commonMessages.dashboard)} />
|
||||
{isAuthenticated && !tokenAuthLoading && !tokenVerifyLoading ? (
|
||||
<AppLayout>
|
||||
<Navigator />
|
||||
<ErrorBoundary
|
||||
|
@ -277,9 +248,7 @@ const Routes: React.FC = () => {
|
|||
component={WarehouseSection}
|
||||
/>
|
||||
{createConfigurationMenu(intl).filter(menu =>
|
||||
menu.menuItems.map(item =>
|
||||
hasPermission(item.permission, user)
|
||||
)
|
||||
menu.menuItems.map(item => hasPermission(item.permission, user))
|
||||
).length > 0 && (
|
||||
<SectionRoute
|
||||
exact
|
||||
|
@ -294,10 +263,8 @@ const Routes: React.FC = () => {
|
|||
) : hasToken && tokenVerifyLoading ? (
|
||||
<LoginLoading />
|
||||
) : (
|
||||
<Auth hasToken={hasToken} />
|
||||
)
|
||||
}
|
||||
</AuthProvider>
|
||||
<Auth />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -100,8 +100,8 @@ const getEventMessage = (event: OrderDetails_order_events, intl: IntlShape) => {
|
|||
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:
|
||||
|
|
|
@ -4,12 +4,11 @@ import React from "react";
|
|||
import { Query, QueryResult } from "react-apollo";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { isJwtError } from "./auth/errors";
|
||||
import { handleQueryAuthError } from "./auth";
|
||||
import useAppState from "./hooks/useAppState";
|
||||
import useNotifier from "./hooks/useNotifier";
|
||||
import useUser from "./hooks/useUser";
|
||||
import { commonMessages } from "./intl";
|
||||
import { maybe, RequireAtLeastOne } from "./misc";
|
||||
import { RequireAtLeastOne } from "./misc";
|
||||
|
||||
export interface LoadMore<TData, TVariables> {
|
||||
loadMore: (
|
||||
|
@ -79,29 +78,17 @@ export function TypedQuery<TData, TVariables>(
|
|||
skip={skip}
|
||||
context={{ useBatching: true }}
|
||||
errorPolicy="all"
|
||||
onError={error =>
|
||||
handleQueryAuthError(
|
||||
error,
|
||||
notify,
|
||||
user.tokenRefresh,
|
||||
user.logout,
|
||||
intl
|
||||
)
|
||||
}
|
||||
>
|
||||
{(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 = (
|
||||
mergeFunc: (
|
||||
previousResults: TData,
|
||||
|
|
|
@ -3,14 +3,16 @@ import { User } from "@saleor/fragments/types/User";
|
|||
export const isSupported =
|
||||
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) {
|
||||
navigator.credentials.get({ password: true }).then(credential => {
|
||||
if (credential instanceof PasswordCredential) {
|
||||
loginFn(credential.id, credential.password);
|
||||
return loginFn(credential.id, credential.password);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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