Use Auth SDK (#1474)

* Use Auth SDK

* Update auth provider hook

* Update sdk module mapping

* Update setting password

* Fix no user details on first login

* Update auth tests

* Cleanups

* Update SDK

Update SDK

Update SDK

Update test recordings

Update SDK

* Implement SDK External Auth

Update new password view

Hnalde external logout

Update SDK

Fix logout external redirect

* Fix login page style

* Update SDK

* Auth Provider cleanups

Update and refactor auth

Auth types cleanups and refactor

* Update channel context provider

* Fix login error handling

* Logout immidiatelly non-staff user

* Update test snapshots

* Trigger CI

* Update to SDK v0.4, remove duplicated UserContext hook

* Handle server errors during login

* Fix wrong login page form submition handling

* Update login error messages

Co-authored-by: Jakub Majorek <majorek.jakub@gmail.com>
This commit is contained in:
Dawid Tarasiuk 2021-12-17 12:10:54 +01:00 committed by GitHub
parent 866bb91177
commit 4880093f63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1005 additions and 1610 deletions

View file

@ -1238,20 +1238,26 @@
"context": "link", "context": "link",
"string": "Use this link to recover it" "string": "Use this link to recover it"
}, },
"src_dot_auth_dot_components_dot_LoginPage_dot_3476994590": {
"string": "Sorry, your username and/or password are incorrect. Please try again."
},
"src_dot_auth_dot_components_dot_LoginPage_dot_3762459576": { "src_dot_auth_dot_components_dot_LoginPage_dot_3762459576": {
"context": "description", "context": "description",
"string": "or login using" "string": "or login using"
}, },
"src_dot_auth_dot_components_dot_LoginPage_dot_534894384": {
"string": "Sorry, login went wrong. Please try again."
},
"src_dot_auth_dot_components_dot_LoginPage_dot_599516345": { "src_dot_auth_dot_components_dot_LoginPage_dot_599516345": {
"context": "description", "context": "description",
"string": "Forgot password? {resetPasswordLink}" "string": "Forgot password? {resetPasswordLink}"
}, },
"src_dot_auth_dot_components_dot_LoginPage_dot_externalLoginError": {
"context": "error message",
"string": "Sorry, login went wrong. Please try again."
},
"src_dot_auth_dot_components_dot_LoginPage_dot_loginError": {
"context": "error message",
"string": "Sorry, your username and/or password are incorrect. Please try again."
},
"src_dot_auth_dot_components_dot_LoginPage_dot_serverError": {
"context": "error message",
"string": "Saleor is unavailable, please check your network connection and try again."
},
"src_dot_auth_dot_components_dot_NewPasswordPage_dot_1254879564": { "src_dot_auth_dot_components_dot_NewPasswordPage_dot_1254879564": {
"string": "New Password" "string": "New Password"
}, },

120
package-lock.json generated
View file

@ -4,6 +4,92 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@apollo/client": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.4.15.tgz",
"integrity": "sha512-CnlT9i7TgHagkKQNvti81A9KcbIMqgpUPGJJL6bg5spTsB2R/5J6E7qiPcMvXuuXwR2xe4FmE4Ey4HizStb8Hg==",
"requires": {
"@graphql-typed-document-node/core": "^3.0.0",
"@wry/context": "^0.6.0",
"@wry/equality": "^0.5.0",
"@wry/trie": "^0.3.0",
"graphql-tag": "^2.12.3",
"hoist-non-react-statics": "^3.3.2",
"optimism": "^0.16.1",
"prop-types": "^15.7.2",
"symbol-observable": "^4.0.0",
"ts-invariant": "^0.9.0",
"tslib": "^2.3.0",
"zen-observable-ts": "~1.1.0"
},
"dependencies": {
"@types/zen-observable": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz",
"integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw=="
},
"@wry/context": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.1.tgz",
"integrity": "sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw==",
"requires": {
"tslib": "^2.3.0"
}
},
"@wry/equality": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.2.tgz",
"integrity": "sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA==",
"requires": {
"tslib": "^2.3.0"
}
},
"graphql-tag": {
"version": "2.12.5",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.5.tgz",
"integrity": "sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ==",
"requires": {
"tslib": "^2.1.0"
}
},
"optimism": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.1.tgz",
"integrity": "sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==",
"requires": {
"@wry/context": "^0.6.0",
"@wry/trie": "^0.3.0"
}
},
"symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
"integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ=="
},
"ts-invariant": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.9.3.tgz",
"integrity": "sha512-HinBlTbFslQI0OHP07JLsSXPibSegec6r9ai5xxq/qHYCsIQbzpymLpDhAUsnXcSrDEcd0L62L8vsOEdzM0qlA==",
"requires": {
"tslib": "^2.1.0"
}
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
},
"zen-observable-ts": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz",
"integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==",
"requires": {
"@types/zen-observable": "0.8.3",
"zen-observable": "0.8.15"
}
}
}
},
"@apollo/federation": { "@apollo/federation": {
"version": "0.20.7", "version": "0.20.7",
"resolved": "https://registry.npmjs.org/@apollo/federation/-/federation-0.20.7.tgz", "resolved": "https://registry.npmjs.org/@apollo/federation/-/federation-0.20.7.tgz",
@ -3069,6 +3155,11 @@
} }
} }
}, },
"@graphql-typed-document-node/core": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz",
"integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg=="
},
"@hapi/address": { "@hapi/address": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@ -5187,6 +5278,15 @@
} }
} }
}, },
"@saleor/sdk": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@saleor/sdk/-/sdk-0.4.0.tgz",
"integrity": "sha512-1wJ1WC73uAX7OBcZ4qRDJBEFVKrc2yXbqlbkFTjdRcRT+0k/T9RUPHZNIgFP0mj4/v6y6fyd9lgkknnF5vJnxw==",
"requires": {
"cross-fetch": "^3.1.4",
"jwt-decode": "^3.1.2"
}
},
"@samverschueren/stream-to-observable": { "@samverschueren/stream-to-observable": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz", "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
@ -7414,6 +7514,21 @@
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },
"@wry/trie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.1.tgz",
"integrity": "sha512-WwB53ikYudh9pIorgxrkHKrQZcCqNM/Q/bDzZBffEaGUKGuHrRb3zZUT9Sh2qw9yogC7SsdRmQ1ER0pqvd3bfw==",
"requires": {
"tslib": "^2.3.0"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
}
}
},
"@xtuc/ieee754": { "@xtuc/ieee754": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -20067,6 +20182,11 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"keycode": { "keycode": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",

View file

@ -17,6 +17,7 @@
"npm": ">=6.11.0 <7" "npm": ">=6.11.0 <7"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.4.15",
"@editorjs/editorjs": "^2.19.3", "@editorjs/editorjs": "^2.19.3",
"@editorjs/header": "^2.6.1", "@editorjs/header": "^2.6.1",
"@editorjs/image": "^2.6.0", "@editorjs/image": "^2.6.0",
@ -28,6 +29,7 @@
"@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/lab": "^4.0.0-alpha.58",
"@material-ui/styles": "^4.11.4", "@material-ui/styles": "^4.11.4",
"@saleor/macaw-ui": "^0.2.7", "@saleor/macaw-ui": "^0.2.7",
"@saleor/sdk": "^0.4.0",
"@sentry/react": "^6.0.0", "@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6", "@types/faker": "^5.1.6",
"@uiw/react-color-hue": "0.0.34", "@uiw/react-color-hue": "0.0.34",
@ -118,7 +120,6 @@
"@types/fuzzaldrin": "^2.1.2", "@types/fuzzaldrin": "^2.1.2",
"@types/jest": "^24.0.24", "@types/jest": "^24.0.24",
"@types/lodash-es": "^4.17.3", "@types/lodash-es": "^4.17.3",
"@types/node-fetch": "^2.5.7",
"@types/pollyjs__adapter-node-http": "^2.0.1", "@types/pollyjs__adapter-node-http": "^2.0.1",
"@types/pollyjs__persister-fs": "^2.0.1", "@types/pollyjs__persister-fs": "^2.0.1",
"@types/react": "^16.9.16", "@types/react": "^16.9.16",
@ -169,7 +170,6 @@
"jest-localstorage-mock": "^2.4.3", "jest-localstorage-mock": "^2.4.3",
"lint-staged": "^10.5.1", "lint-staged": "^10.5.1",
"mock-apollo-client": "^0.4.0", "mock-apollo-client": "^0.4.0",
"node-fetch": "^2.6.1",
"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",
@ -220,7 +220,7 @@
"moduleNameMapper": { "moduleNameMapper": {
"@assets(.*)$": "<rootDir>/assets/$1", "@assets(.*)$": "<rootDir>/assets/$1",
"@locale(.*)$": "<rootDir>/locale/$1", "@locale(.*)$": "<rootDir>/locale/$1",
"@saleor(?!.*macaw)(.*)$": "<rootDir>/src/$1", "@saleor(?!.*macaw)(?!.*sdk)(.*)$": "<rootDir>/src/$1",
"@test/(.*)$": "<rootDir>/testUtils/$1", "@test/(.*)$": "<rootDir>/testUtils/$1",
"^lodash-es(.*)$": "lodash/$1", "^lodash-es(.*)$": "lodash/$1",
"^@material-ui/core$": "<rootDir>/node_modules/@material-ui/core", "^@material-ui/core$": "<rootDir>/node_modules/@material-ui/core",

View file

@ -1,120 +0,0 @@
{
"log": {
"_recordingName": "User/will be logged if has valid token",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "4.3.0"
},
"entries": [
{
"_id": "a3088678db2635ada66ab049f76c9722",
"_order": 0,
"cache": {},
"request": {
"bodySize": 702,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "702"
},
{
"_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.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMDYzMjg0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU5BPT0iLCJpc19zdGFmZiI6dHJ1ZX0.QDp4vlm1tKhk8iFnY2MnREvO-IubI5j8g_Wylb1XJqc\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\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": 1765,
"content": {
"mimeType": "application/json",
"size": 1765,
"text": "[{\"data\": {\"tokenVerify\": {\"payload\": {\"iat\": 1610632544, \"exp\": 1610632844, \"token\": \"ksEVMvgg6Bfi\", \"email\": \"admin@example.com\", \"type\": \"access\", \"user_id\": \"VXNlcjoyNA==\", \"is_staff\": true}, \"user\": {\"id\": \"VXNlcjoyNA==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": true, \"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_PRODUCT_TYPES_AND_ATTRIBUTES\", \"name\": \"Manage product types and attributes.\", \"__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": "Thu, 14 Jan 2021 14:10:40 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.8.7"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "1765"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
}
],
"headersSize": 194,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2021-01-14T14:10:40.434Z",
"time": 155,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 155
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -8,11 +8,11 @@
}, },
"entries": [ "entries": [
{ {
"_id": "4062f0ffdf1fab89a50cd1b667a889ef", "_id": "2c067f705383db903cdaf88cb881c922",
"_order": 0, "_order": 0,
"cache": {}, "cache": {},
"request": { "request": {
"bodySize": 583, "bodySize": 1154,
"cookies": [], "cookies": [],
"headers": [ "headers": [
{ {
@ -28,7 +28,7 @@
{ {
"_fromType": "array", "_fromType": "array",
"name": "content-length", "name": "content-length",
"value": "583" "value": "1154"
}, },
{ {
"_fromType": "array", "_fromType": "array",
@ -50,40 +50,43 @@
"value": "localhost:8000" "value": "localhost:8000"
} }
], ],
"headersSize": 281, "headersSize": 255,
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"method": "POST", "method": "POST",
"postData": { "postData": {
"mimeType": "application/json", "mimeType": "application/json",
"params": [], "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 isStaff\\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 {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]" "text": "{\"operationName\":\"login\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"admin\"},\"query\":\"fragment AccountErrorFragment on AccountError {\\n code\\n field\\n message\\n __typename\\n}\\n\\nfragment AddressFragment on Address {\\n id\\n firstName\\n lastName\\n companyName\\n streetAddress1\\n streetAddress2\\n city\\n cityArea\\n postalCode\\n country {\\n code\\n country\\n __typename\\n }\\n countryArea\\n phone\\n isDefaultBillingAddress\\n isDefaultShippingAddress\\n __typename\\n}\\n\\nfragment UserFragment on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n metadata {\\n key\\n value\\n __typename\\n }\\n defaultShippingAddress {\\n ...AddressFragment\\n __typename\\n }\\n defaultBillingAddress {\\n ...AddressFragment\\n __typename\\n }\\n addresses {\\n ...AddressFragment\\n __typename\\n }\\n __typename\\n}\\n\\nmutation login($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n csrfToken\\n token\\n errors {\\n ...AccountErrorFragment\\n __typename\\n }\\n user {\\n ...UserFragment\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}"
}, },
"queryString": [], "queryString": [],
"url": "http://localhost:8000/graphql/" "url": "http://localhost:8000/graphql/"
}, },
"response": { "response": {
"bodySize": 1976, "bodySize": 679,
"content": { "content": {
"mimeType": "application/json", "mimeType": "application/json",
"size": 1976, "size": 679,
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"UIzzJSFalS8pplfM1j5QNIUNiXb0VFH3kbe6kfTddYvLjJ9DhMasCtHJKXoGDfbw\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMDYzMjg0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU5BPT0iLCJpc19zdGFmZiI6dHJ1ZX0.QDp4vlm1tKhk8iFnY2MnREvO-IubI5j8g_Wylb1XJqc\", \"user\": {\"id\": \"VXNlcjoyNA==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": true, \"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_PRODUCT_TYPES_AND_ATTRIBUTES\", \"name\": \"Manage product types and attributes.\", \"__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\"}}}]" "text": "{\"data\": {\"tokenCreate\": {\"csrfToken\": \"yDcn3nGgfW2AXuCc62pvaLGtL0dKs7n513jhL30DqmDirUDXL4zwEJYQfji8L2yP\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzM5NTEwNzMsIm93bmVyIjoic2FsZW9yIiwiZXhwIjoxNjMzOTUxMzczLCJ0b2tlbiI6ImdtRU95YlM5TDhmaiIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ0eXBlIjoiYWNjZXNzIiwidXNlcl9pZCI6IlZYTmxjam94IiwiaXNfc3RhZmYiOnRydWV9.hf105U1yvWzf-X_bKit4jh6vU_EnCC52XupS9GVlKaw\", \"errors\": [], \"user\": {\"id\": \"VXNlcjox\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": true, \"metadata\": [], \"defaultShippingAddress\": null, \"defaultBillingAddress\": null, \"addresses\": [], \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}"
}, },
"cookies": [ "cookies": [
{ {
"expires": "2021-11-10T11:17:53.000Z",
"httpOnly": true, "httpOnly": true,
"maxAge": 2592000,
"name": "refreshToken", "name": "refreshToken",
"path": "/", "path": "/",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMzIyNDU0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lOQT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6IlVJenpKU0ZhbFM4cHBsZk0xajVRTklVTmlYYjBWRkgza2JlNmtmVGRkWXZMako5RGhNYXNDdEhKS1hvR0RmYncifQ.Br0GWGPPcnysyUxukjBBfXNbwCAm2qlR5OYClwFF3ZQ" "sameSite": "Lax",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzM5NTEwNzMsIm93bmVyIjoic2FsZW9yIiwiZXhwIjoxNjM2NTQzMDczLCJ0b2tlbiI6ImdtRU95YlM5TDhmaiIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ0eXBlIjoicmVmcmVzaCIsInVzZXJfaWQiOiJWWE5sY2pveCIsImlzX3N0YWZmIjp0cnVlLCJjc3JmVG9rZW4iOiJ5RGNuM25HZ2ZXMkFYdUNjNjJwdmFMR3RMMGRLczduNTEzamhMMzBEcW1EaXJVRFhMNHp3RUpZUWZqaThMMnlQIn0.zRkkzPiC8DPlBUvUPVcOSrEF4AzgDKcK8gpyQXSpKM4"
} }
], ],
"headers": [ "headers": [
{ {
"name": "date", "name": "date",
"value": "Thu, 14 Jan 2021 13:55:44 GMT" "value": "Mon, 11 Oct 2021 11:17:53 GMT"
}, },
{ {
"name": "server", "name": "server",
"value": "WSGIServer/0.2 CPython/3.8.7" "value": "WSGIServer/0.2 CPython/3.8.3"
}, },
{ {
"name": "content-type", "name": "content-type",
@ -91,7 +94,7 @@
}, },
{ {
"name": "content-length", "name": "content-length",
"value": "1976" "value": "679"
}, },
{ {
"name": "x-content-type-options", "name": "x-content-type-options",
@ -104,17 +107,17 @@
{ {
"_fromType": "array", "_fromType": "array",
"name": "set-cookie", "name": "set-cookie",
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMzIyNDU0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lOQT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6IlVJenpKU0ZhbFM4cHBsZk0xajVRTklVTmlYYjBWRkgza2JlNmtmVGRkWXZMako5RGhNYXNDdEhKS1hvR0RmYncifQ.Br0GWGPPcnysyUxukjBBfXNbwCAm2qlR5OYClwFF3ZQ; HttpOnly; Path=/" "value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzM5NTEwNzMsIm93bmVyIjoic2FsZW9yIiwiZXhwIjoxNjM2NTQzMDczLCJ0b2tlbiI6ImdtRU95YlM5TDhmaiIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ0eXBlIjoicmVmcmVzaCIsInVzZXJfaWQiOiJWWE5sY2pveCIsImlzX3N0YWZmIjp0cnVlLCJjc3JmVG9rZW4iOiJ5RGNuM25HZ2ZXMkFYdUNjNjJwdmFMR3RMMGRLczduNTEzamhMMzBEcW1EaXJVRFhMNHp3RUpZUWZqaThMMnlQIn0.zRkkzPiC8DPlBUvUPVcOSrEF4AzgDKcK8gpyQXSpKM4; expires=Wed, 10 Nov 2021 11:17:53 GMT; HttpOnly; Max-Age=2592000; Path=/; SameSite=Lax"
} }
], ],
"headersSize": 618, "headersSize": 704,
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"redirectURL": "", "redirectURL": "",
"status": 200, "status": 200,
"statusText": "OK" "statusText": "OK"
}, },
"startedDateTime": "2021-04-22T11:07:20.652Z", "startedDateTime": "2021-10-11T11:17:52.646Z",
"time": 758, "time": 521,
"timings": { "timings": {
"blocked": -1, "blocked": -1,
"connect": -1, "connect": -1,
@ -122,7 +125,7 @@
"receive": 0, "receive": 0,
"send": 0, "send": 0,
"ssl": -1, "ssl": -1,
"wait": 758 "wait": 521
} }
} }
], ],

View file

@ -1,128 +0,0 @@
{
"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"
}
}

View file

@ -1,120 +0,0 @@
{
"log": {
"_recordingName": "User/will not be logged if has invalid token",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "4.3.0"
},
"entries": [
{
"_id": "b1557b45bbbf7aed1a4a53f5141ca324",
"_order": 0,
"cache": {},
"request": {
"bodySize": 439,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "439"
},
{
"_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 isStaff\\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": "Thu, 14 Jan 2021 14:10:40 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.8.7"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "89"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
}
],
"headersSize": 192,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2021-01-14T14:10:40.611Z",
"time": 25,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 25
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -1,132 +0,0 @@
{
"log": {
"_recordingName": "User/will not be logged if is non-staff",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "5.0.0"
},
"entries": [
{
"_id": "0b09ec35ecae5b17a2ccda062b1d6ef5",
"_order": 0,
"cache": {},
"request": {
"bodySize": 602,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "602"
},
{
"_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\":\"client@example.com\",\"password\":\"password\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\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": 616,
"content": {
"mimeType": "application/json",
"size": 616,
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"Gac5v8mZt6dW0HBXp5RNt8GAWciTbVzsycpqtUKV797npCXajke5h9VoF4l9MreP\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2OTg2MzMsImV4cCI6MTYxMDY5ODkzMywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJhY2Nlc3MiLCJ1c2VyX2lkIjoiVlhObGNqb3pNQT09IiwiaXNfc3RhZmYiOmZhbHNlfQ.RVYwqQSPEZoi2E_ImC30Ml37RJ2Fu6AnSmfDkAYMcqY\", \"user\": {\"id\": \"VXNlcjozMA==\", \"email\": \"client@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": false, \"userPermissions\": [], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}]"
},
"cookies": [
{
"httpOnly": true,
"name": "refreshToken",
"path": "/",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2OTg2MzMsImV4cCI6MTYxMzI5MDYzMywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoiR2FjNXY4bVp0NmRXMEhCWHA1Uk50OEdBV2NpVGJWenN5Y3BxdFVLVjc5N25wQ1hhamtlNWg5Vm9GNGw5TXJlUCJ9.jUF_9vvtwT8EUbQ4GM7u0YVivk7TiSoSecHDZ0jJ2MI"
}
],
"headers": [
{
"name": "date",
"value": "Fri, 15 Jan 2021 08:17:13 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.8.7"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "616"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
},
{
"_fromType": "array",
"name": "set-cookie",
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2OTg2MzMsImV4cCI6MTYxMzI5MDYzMywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoiR2FjNXY4bVp0NmRXMEhCWHA1Uk50OEdBV2NpVGJWenN5Y3BxdFVLVjc5N25wQ1hhamtlNWg5Vm9GNGw5TXJlUCJ9.jUF_9vvtwT8EUbQ4GM7u0YVivk7TiSoSecHDZ0jJ2MI; HttpOnly; Path=/"
}
],
"headersSize": 619,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2021-01-15T08:17:12.850Z",
"time": 623,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 623
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -8,11 +8,11 @@
}, },
"entries": [ "entries": [
{ {
"_id": "6fed086670e88883223acb33bab4ff31", "_id": "f36c6d9d965b62862363338cf17d6135",
"_order": 0, "_order": 0,
"cache": {}, "cache": {},
"request": { "request": {
"bodySize": 599, "bodySize": 324,
"cookies": [], "cookies": [],
"headers": [ "headers": [
{ {
@ -28,7 +28,7 @@
{ {
"_fromType": "array", "_fromType": "array",
"name": "content-length", "name": "content-length",
"value": "599" "value": "324"
}, },
{ {
"_fromType": "array", "_fromType": "array",
@ -50,33 +50,33 @@
"value": "localhost:8000" "value": "localhost:8000"
} }
], ],
"headersSize": 281, "headersSize": 254,
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"method": "POST", "method": "POST",
"postData": { "postData": {
"mimeType": "application/json", "mimeType": "application/json",
"params": [], "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 isStaff\\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 {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]" "text": "[{\"operationName\":\"UserDetails\",\"variables\":{},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nquery UserDetails {\\n me {\\n ...User\\n __typename\\n }\\n}\\n\"}]"
}, },
"queryString": [], "queryString": [],
"url": "http://localhost:8000/graphql/" "url": "http://localhost:8000/graphql/"
}, },
"response": { "response": {
"bodySize": 214, "bodySize": 24,
"content": { "content": {
"mimeType": "application/json", "mimeType": "application/json",
"size": 214, "size": 24,
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [{\"field\": \"email\", \"message\": \"Please, enter valid credentials\", \"__typename\": \"Error\"}], \"csrfToken\": null, \"token\": null, \"user\": null, \"__typename\": \"CreateToken\"}}}]" "text": "[{\"data\": {\"me\": null}}]"
}, },
"cookies": [], "cookies": [],
"headers": [ "headers": [
{ {
"name": "date", "name": "date",
"value": "Thu, 14 Jan 2021 13:55:45 GMT" "value": "Thu, 07 Oct 2021 23:09:08 GMT"
}, },
{ {
"name": "server", "name": "server",
"value": "WSGIServer/0.2 CPython/3.8.7" "value": "WSGIServer/0.2 CPython/3.8.3"
}, },
{ {
"name": "content-type", "name": "content-type",
@ -84,7 +84,113 @@
}, },
{ {
"name": "content-length", "name": "content-length",
"value": "214" "value": "24"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "referrer-policy",
"value": "same-origin"
}
],
"headersSize": 192,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2021-10-07T23:09:08.310Z",
"time": 105,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 105
}
},
{
"_id": "156d01ee9c99f8b74ab79e7482eadf9f",
"_order": 0,
"cache": {},
"request": {
"bodySize": 1170,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "content-type",
"value": "application/json"
},
{
"_fromType": "array",
"name": "content-length",
"value": "1170"
},
{
"_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": 255,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\"operationName\":\"login\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"NotAValidPassword123!\"},\"query\":\"fragment AccountErrorFragment on AccountError {\\n code\\n field\\n message\\n __typename\\n}\\n\\nfragment AddressFragment on Address {\\n id\\n firstName\\n lastName\\n companyName\\n streetAddress1\\n streetAddress2\\n city\\n cityArea\\n postalCode\\n country {\\n code\\n country\\n __typename\\n }\\n countryArea\\n phone\\n isDefaultBillingAddress\\n isDefaultShippingAddress\\n __typename\\n}\\n\\nfragment UserFragment on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n metadata {\\n key\\n value\\n __typename\\n }\\n defaultShippingAddress {\\n ...AddressFragment\\n __typename\\n }\\n defaultBillingAddress {\\n ...AddressFragment\\n __typename\\n }\\n addresses {\\n ...AddressFragment\\n __typename\\n }\\n __typename\\n}\\n\\nmutation login($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n csrfToken\\n token\\n errors {\\n ...AccountErrorFragment\\n __typename\\n }\\n user {\\n ...UserFragment\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}"
},
"queryString": [],
"url": "http://localhost:8000/graphql/"
},
"response": {
"bodySize": 243,
"content": {
"mimeType": "application/json",
"size": 243,
"text": "{\"data\": {\"tokenCreate\": {\"csrfToken\": null, \"token\": null, \"errors\": [{\"code\": \"INVALID_CREDENTIALS\", \"field\": \"email\", \"message\": \"Please, enter valid credentials\", \"__typename\": \"AccountError\"}], \"user\": null, \"__typename\": \"CreateToken\"}}}"
},
"cookies": [],
"headers": [
{
"name": "date",
"value": "Mon, 11 Oct 2021 11:17:53 GMT"
},
{
"name": "server",
"value": "WSGIServer/0.2 CPython/3.8.3"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "243"
}, },
{ {
"name": "x-content-type-options", "name": "x-content-type-options",
@ -101,8 +207,8 @@
"status": 200, "status": 200,
"statusText": "OK" "statusText": "OK"
}, },
"startedDateTime": "2021-04-22T11:09:20.963Z", "startedDateTime": "2021-10-11T11:17:53.202Z",
"time": 555, "time": 438,
"timings": { "timings": {
"blocked": -1, "blocked": -1,
"connect": -1, "connect": -1,
@ -110,7 +216,7 @@
"receive": 0, "receive": 0,
"send": 0, "send": 0,
"ssl": -1, "ssl": -1,
"wait": 555 "wait": 438
} }
} }
], ],

View file

@ -8,11 +8,11 @@
}, },
"entries": [ "entries": [
{ {
"_id": "8948f5cbe2259e56b6dd03f068fbfa4d", "_id": "07fe7bf33b7e219c3280229514fcf39d",
"_order": 0, "_order": 0,
"cache": {}, "cache": {},
"request": { "request": {
"bodySize": 587, "bodySize": 1158,
"cookies": [], "cookies": [],
"headers": [ "headers": [
{ {
@ -28,7 +28,7 @@
{ {
"_fromType": "array", "_fromType": "array",
"name": "content-length", "name": "content-length",
"value": "587" "value": "1158"
}, },
{ {
"_fromType": "array", "_fromType": "array",
@ -50,40 +50,33 @@
"value": "localhost:8000" "value": "localhost:8000"
} }
], ],
"headersSize": 281, "headersSize": 255,
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"method": "POST", "method": "POST",
"postData": { "postData": {
"mimeType": "application/json", "mimeType": "application/json",
"params": [], "params": [],
"text": "[{\"operationName\":\"TokenAuth\",\"variables\":{\"email\":\"client@example.com\",\"password\":\"password\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]" "text": "{\"operationName\":\"login\",\"variables\":{\"email\":\"client@example.com\",\"password\":\"password\"},\"query\":\"fragment AccountErrorFragment on AccountError {\\n code\\n field\\n message\\n __typename\\n}\\n\\nfragment AddressFragment on Address {\\n id\\n firstName\\n lastName\\n companyName\\n streetAddress1\\n streetAddress2\\n city\\n cityArea\\n postalCode\\n country {\\n code\\n country\\n __typename\\n }\\n countryArea\\n phone\\n isDefaultBillingAddress\\n isDefaultShippingAddress\\n __typename\\n}\\n\\nfragment UserFragment on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n metadata {\\n key\\n value\\n __typename\\n }\\n defaultShippingAddress {\\n ...AddressFragment\\n __typename\\n }\\n defaultBillingAddress {\\n ...AddressFragment\\n __typename\\n }\\n addresses {\\n ...AddressFragment\\n __typename\\n }\\n __typename\\n}\\n\\nmutation login($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n csrfToken\\n token\\n errors {\\n ...AccountErrorFragment\\n __typename\\n }\\n user {\\n ...UserFragment\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}"
}, },
"queryString": [], "queryString": [],
"url": "http://localhost:8000/graphql/" "url": "http://localhost:8000/graphql/"
}, },
"response": { "response": {
"bodySize": 616, "bodySize": 243,
"content": { "content": {
"mimeType": "application/json", "mimeType": "application/json",
"size": 616, "size": 243,
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"ztCCq2djodzVWeaynv8ifQbIIB4nrM6HyInqcZ4xqoaIEfOOKUEhAqw9rVR8Cr8L\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTEwNjY1MTcsImV4cCI6MTYxMTA2NjgxNywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJhY2Nlc3MiLCJ1c2VyX2lkIjoiVlhObGNqb3pNQT09IiwiaXNfc3RhZmYiOmZhbHNlfQ.lXsNnIBxZCCL843TTjn84lkWpE05o88F5q811ApjdKA\", \"user\": {\"id\": \"VXNlcjozMA==\", \"email\": \"client@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": false, \"userPermissions\": [], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}]" "text": "{\"data\": {\"tokenCreate\": {\"csrfToken\": null, \"token\": null, \"errors\": [{\"code\": \"INVALID_CREDENTIALS\", \"field\": \"email\", \"message\": \"Please, enter valid credentials\", \"__typename\": \"AccountError\"}], \"user\": null, \"__typename\": \"CreateToken\"}}}"
}, },
"cookies": [ "cookies": [],
{
"httpOnly": true,
"name": "refreshToken",
"path": "/",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTEwNjY1MTcsImV4cCI6MTYxMzY1ODUxNywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoienRDQ3EyZGpvZHpWV2VheW52OGlmUWJJSUI0bnJNNkh5SW5xY1o0eHFvYUlFZk9PS1VFaEFxdzlyVlI4Q3I4TCJ9.hDMEK3HFSLol7rLd9dfaSCLTWgvetNDAFsb039L9PXQ"
}
],
"headers": [ "headers": [
{ {
"name": "date", "name": "date",
"value": "Tue, 19 Jan 2021 14:28:37 GMT" "value": "Mon, 11 Oct 2021 11:17:53 GMT"
}, },
{ {
"name": "server", "name": "server",
"value": "WSGIServer/0.2 CPython/3.9.1" "value": "WSGIServer/0.2 CPython/3.8.3"
}, },
{ {
"name": "content-type", "name": "content-type",
@ -91,7 +84,7 @@
}, },
{ {
"name": "content-length", "name": "content-length",
"value": "616" "value": "243"
}, },
{ {
"name": "x-content-type-options", "name": "x-content-type-options",
@ -100,21 +93,16 @@
{ {
"name": "referrer-policy", "name": "referrer-policy",
"value": "same-origin" "value": "same-origin"
},
{
"_fromType": "array",
"name": "set-cookie",
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTEwNjY1MTcsImV4cCI6MTYxMzY1ODUxNywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoienRDQ3EyZGpvZHpWV2VheW52OGlmUWJJSUI0bnJNNkh5SW5xY1o0eHFvYUlFZk9PS1VFaEFxdzlyVlI4Q3I4TCJ9.hDMEK3HFSLol7rLd9dfaSCLTWgvetNDAFsb039L9PXQ; HttpOnly; Path=/"
} }
], ],
"headersSize": 619, "headersSize": 193,
"httpVersion": "HTTP/1.1", "httpVersion": "HTTP/1.1",
"redirectURL": "", "redirectURL": "",
"status": 200, "status": 200,
"statusText": "OK" "statusText": "OK"
}, },
"startedDateTime": "2021-04-22T11:09:21.540Z", "startedDateTime": "2021-10-11T11:17:53.664Z",
"time": 382, "time": 61,
"timings": { "timings": {
"blocked": -1, "blocked": -1,
"connect": -1, "connect": -1,
@ -122,7 +110,7 @@
"receive": 0, "receive": 0,
"send": 0, "send": 0,
"ssl": -1, "ssl": -1,
"wait": 382 "wait": 61
} }
} }
], ],

View file

@ -1,99 +0,0 @@
import setupApi from "@test/api";
import { act, renderHook } from "@testing-library/react-hooks";
import ApolloClient from "apollo-client";
import { useAuthProvider } from "./hooks/useAuthProvider";
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({ apolloClient, intl: intl as any, notify })
);
return result;
}
const adminCredentials = {
email: "admin@example.com",
password: "admin",
token: null
};
const nonStaffUserCredentials = {
email: "client@example.com",
password: "password"
};
beforeEach(() => {
localStorage.clear();
sessionStorage.clear();
});
describe("User", () => {
it("will be logged in if has valid credentials", async done => {
const hook = renderAuthProvider(apolloClient);
await act(async () => {
await hook.current.login(
adminCredentials.email,
adminCredentials.password
);
});
expect(hook.current.user.email).toBe(adminCredentials.email);
adminCredentials.token = getTokens().auth;
done();
});
it("will not be logged in if doesn't have valid credentials", async done => {
const hook = renderAuthProvider(apolloClient);
await act(async () => {
await hook.current.login(adminCredentials.email, "NotAValidPassword123!");
});
expect(hook.current.user).toBe(null);
done();
});
it("will not be logged in if is non-staff", async done => {
const hook = renderAuthProvider(apolloClient);
await act(async () => {
await hook.current.login(
nonStaffUserCredentials.email,
nonStaffUserCredentials.password
);
});
expect(hook.current.user).toBe(undefined);
done();
});
it("will be logged if has valid token", async done => {
setAuthToken(adminCredentials.token, false);
const hook = renderAuthProvider(apolloClient);
await act(() => hook.current.autologinPromise.current);
expect(hook.current.user.email).toBe(adminCredentials.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.user).toBe(undefined);
done();
});
});

View file

@ -0,0 +1,92 @@
import { createSaleorClient, SaleorProvider } from "@saleor/sdk";
import setupApi from "@test/api";
import { act, renderHook } from "@testing-library/react-hooks";
import React from "react";
import { useAuthProvider } from "./hooks/useAuthProvider";
const apolloClient = setupApi();
function renderAuthProvider() {
const intl = {
formatMessage: ({ defaultMessage }) => defaultMessage
};
const notify = jest.fn();
const saleorClient = createSaleorClient({
apiUrl: process.env.API_URI || "http://localhost:8000/graphql/",
channel: ""
});
const wrapper = ({ children }) => (
<SaleorProvider client={saleorClient}>{children}</SaleorProvider>
);
const { result } = renderHook(
() => useAuthProvider({ intl: intl as any, notify, apolloClient }),
{ wrapper }
);
return result;
}
const adminCredentials = {
email: "admin@example.com",
password: "admin",
token: null
};
const nonStaffUserCredentials = {
email: "client@example.com",
password: "password"
};
beforeEach(() => {
localStorage.clear();
sessionStorage.clear();
});
describe("User", () => {
it("will be logged in if has valid credentials", async done => {
const hook = renderAuthProvider();
await act(async () => {
const result = await hook.current.login(
adminCredentials.email,
adminCredentials.password
);
expect(result.user?.email).toBe(adminCredentials.email);
});
expect(hook.current.authenticated).toBe(true);
done();
});
it("will not be logged in if doesn't have valid credentials", async done => {
const hook = renderAuthProvider();
await act(async () => {
const result = await hook.current.login(
adminCredentials.email,
"NotAValidPassword123!"
);
expect(result.user).toBe(null);
});
expect(hook.current.authenticated).toBe(false);
done();
});
it("will not be logged in if is non-staff", async done => {
const hook = renderAuthProvider();
await act(async () => {
const result = await hook.current.login(
nonStaffUserCredentials.email,
nonStaffUserCredentials.password
);
expect(result.user).toBe(null);
});
expect(hook.current.authenticated).toBe(false);
done();
});
});

View file

@ -1,11 +1,10 @@
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import React, { useContext } from "react"; import React from "react";
import { useApolloClient } from "react-apollo"; import { useApolloClient } from "react-apollo";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { UserContext } from "./"; import { UserContext } from "./";
import { useAuthProvider } from "./hooks/useAuthProvider"; import { useAuthProvider } from "./hooks/useAuthProvider";
import { getTokens } from "./utils";
interface AuthProviderProps { interface AuthProviderProps {
children: React.ReactNode; children: React.ReactNode;
@ -16,24 +15,11 @@ const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const intl = useIntl(); const intl = useIntl();
const notify = useNotifier(); const notify = useNotifier();
const authProvider = useAuthProvider({ apolloClient, intl, notify }); const authProvider = useAuthProvider({ intl, notify, apolloClient });
return ( return (
<UserContext.Provider value={authProvider}>{children}</UserContext.Provider> <UserContext.Provider value={authProvider}>{children}</UserContext.Provider>
); );
}; };
export const useAuth = () => {
const user = useContext(UserContext);
const isAuthenticated = !!user.user;
return {
hasToken: !!getTokens().auth,
isAuthenticated,
tokenAuthLoading: user.tokenAuthLoading,
tokenVerifyLoading: user.tokenVerifyLoading,
user: user.user
};
};
export default AuthProvider; export default AuthProvider;

View file

@ -7,7 +7,6 @@ import LoginPage, { LoginCardProps } from "../../../auth/components/LoginPage";
const props: Omit<LoginCardProps, "classes"> = { const props: Omit<LoginCardProps, "classes"> = {
disabled: false, disabled: false,
error: false,
externalAuthentications: [ externalAuthentications: [
{ {
__typename: "ExternalAuthentication", __typename: "ExternalAuthentication",
@ -15,7 +14,6 @@ const props: Omit<LoginCardProps, "classes"> = {
name: "Example auth plugin" name: "Example auth plugin"
} }
], ],
externalError: false,
loading: false, loading: false,
onExternalAuthentication: () => undefined, onExternalAuthentication: () => undefined,
onPasswordRecovery: undefined, onPasswordRecovery: undefined,
@ -26,6 +24,9 @@ storiesOf("Views / Authentication / Log in", module)
.addDecorator(CardDecorator) .addDecorator(CardDecorator)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <LoginPage {...props} />) .add("default", () => <LoginPage {...props} />)
.add("error", () => <LoginPage {...props} error={true} />) .add("error login", () => <LoginPage {...props} error={"loginError"} />)
.add("error external login", () => (
<LoginPage {...props} error={"externalLoginError"} />
))
.add("disabled", () => <LoginPage {...props} disabled={true} />) .add("disabled", () => <LoginPage {...props} disabled={true} />)
.add("loading", () => <LoginPage {...props} loading={true} />); .add("loading", () => <LoginPage {...props} loading={true} />);

View file

@ -5,6 +5,7 @@ import {
TextField, TextField,
Typography Typography
} from "@material-ui/core"; } from "@material-ui/core";
import { UserContextError } from "@saleor/auth/types";
import { AvailableExternalAuthentications_shop_availableExternalAuthentications } from "@saleor/auth/types/AvailableExternalAuthentications"; import { AvailableExternalAuthentications_shop_availableExternalAuthentications } from "@saleor/auth/types/AvailableExternalAuthentications";
import { FormSpacer } from "@saleor/components/FormSpacer"; import { FormSpacer } from "@saleor/components/FormSpacer";
import { SubmitPromise } from "@saleor/hooks/useForm"; import { SubmitPromise } from "@saleor/hooks/useForm";
@ -14,6 +15,7 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import LoginForm, { LoginFormData } from "./form"; import LoginForm, { LoginFormData } from "./form";
import { getErrorMessage } from "./messages";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -49,8 +51,7 @@ const useStyles = makeStyles(
); );
export interface LoginCardProps { export interface LoginCardProps {
error: boolean; error?: UserContextError;
externalError: boolean;
disabled: boolean; disabled: boolean;
loading: boolean; loading: boolean;
externalAuthentications?: AvailableExternalAuthentications_shop_availableExternalAuthentications[]; externalAuthentications?: AvailableExternalAuthentications_shop_availableExternalAuthentications[];
@ -62,7 +63,6 @@ export interface LoginCardProps {
const LoginCard: React.FC<LoginCardProps> = props => { const LoginCard: React.FC<LoginCardProps> = props => {
const { const {
error, error,
externalError,
disabled, disabled,
loading, loading,
externalAuthentications = [], externalAuthentications = [],
@ -84,19 +84,12 @@ const LoginCard: React.FC<LoginCardProps> = props => {
return ( return (
<LoginForm onSubmit={onSubmit}> <LoginForm onSubmit={onSubmit}>
{({ change: handleChange, data, submit: handleSubmit }) => ( {({ change: handleChange, data }) => (
<> <>
{error && ( {error && (
<div className={classes.panel} data-test="loginErrorMessage"> <div className={classes.panel} data-test="loginErrorMessage">
<Typography variant="caption"> <Typography variant="caption">
<FormattedMessage defaultMessage="Sorry, your username and/or password are incorrect. Please try again." /> {getErrorMessage(error, intl)}
</Typography>
</div>
)}
{externalError && (
<div className={classes.panel} data-test="loginErrorMessage">
<Typography variant="caption">
<FormattedMessage defaultMessage="Sorry, login went wrong. Please try again." />
</Typography> </Typography>
</div> </div>
)} )}
@ -136,7 +129,6 @@ const LoginCard: React.FC<LoginCardProps> = props => {
color="primary" color="primary"
disabled={disabled} disabled={disabled}
variant="contained" variant="contained"
onClick={handleSubmit}
type="submit" type="submit"
data-test="submit" data-test="submit"
> >
@ -184,7 +176,6 @@ const LoginCard: React.FC<LoginCardProps> = props => {
color="primary" color="primary"
fullWidth fullWidth
variant="outlined" variant="outlined"
size="large"
onClick={() => onClick={() =>
onExternalAuthentication(externalAuthentication.id) onExternalAuthentication(externalAuthentication.id)
} }

View file

@ -12,7 +12,7 @@ export interface UseLoginFormResult {
change: FormChange; change: FormChange;
data: LoginFormData; data: LoginFormData;
hasChanged: boolean; hasChanged: boolean;
submit: () => Promise<boolean>; submit: (event: React.FormEvent<HTMLFormElement>) => Promise<boolean>;
} }
export interface LoginFormProps { export interface LoginFormProps {
@ -53,7 +53,11 @@ function useLoginForm(
return errors; return errors;
}; };
const submit = async () => handleFormSubmit(data, handleSubmit, setChanged); const submit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
return handleFormSubmit(data, handleSubmit, setChanged);
};
return { return {
change: handleChange, change: handleChange,

View file

@ -0,0 +1,33 @@
import { UserContextError } from "@saleor/auth/types";
import { defineMessages, IntlShape } from "react-intl";
export const errorMessages = defineMessages({
loginError: {
defaultMessage:
"Sorry, your username and/or password are incorrect. Please try again.",
description: "error message"
},
externalLoginError: {
defaultMessage: "Sorry, login went wrong. Please try again.",
description: "error message"
},
serverError: {
defaultMessage:
"Saleor is unavailable, please check your network connection and try again.",
description: "error message"
}
});
export function getErrorMessage(
err: UserContextError,
intl: IntlShape
): string {
switch (err) {
case "loginError":
return intl.formatMessage(errorMessages.loginError);
case "externalLoginError":
return intl.formatMessage(errorMessages.externalLoginError);
case "serverError":
return intl.formatMessage(errorMessages.serverError);
}
}

View file

@ -21,7 +21,8 @@ storiesOf("Views / Authentication / Set up a new password", module)
__typename: "AccountError", __typename: "AccountError",
code: AccountErrorCode.PASSWORD_TOO_SHORT, code: AccountErrorCode.PASSWORD_TOO_SHORT,
field, field,
addressType: null addressType: null,
message: null
}))} }))}
disabled={false} disabled={false}
onSubmit={() => undefined} onSubmit={() => undefined}

View file

@ -1,8 +1,8 @@
import { Button, TextField, Typography } from "@material-ui/core"; import { Button, TextField, Typography } from "@material-ui/core";
import { SetPassword_setPassword_errors } from "@saleor/auth/types/SetPassword";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import { makeStyles } from "@saleor/macaw-ui"; import { makeStyles } from "@saleor/macaw-ui";
import { SetPasswordData } from "@saleor/sdk";
import getAccountErrorMessage from "@saleor/utils/errors/account"; import getAccountErrorMessage from "@saleor/utils/errors/account";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
@ -33,7 +33,7 @@ export interface NewPasswordPageFormData {
} }
export interface NewPasswordPageProps { export interface NewPasswordPageProps {
disabled: boolean; disabled: boolean;
errors: SetPassword_setPassword_errors[]; errors: SetPasswordData["errors"];
onSubmit: (data: NewPasswordPageFormData) => void; onSubmit: (data: NewPasswordPageFormData) => void;
} }

View file

@ -1,7 +1,7 @@
import useUser from "@saleor/hooks/useUser";
import React from "react"; import React from "react";
import { Route, RouteProps } from "react-router-dom"; import { Route, RouteProps } from "react-router-dom";
import { useUser } from "..";
import NotFound from "../../NotFound"; import NotFound from "../../NotFound";
import { PermissionEnum } from "../../types/globalTypes"; import { PermissionEnum } from "../../types/globalTypes";
import { hasAllPermissions, hasAnyPermissions } from "../misc"; import { hasAllPermissions, hasAnyPermissions } from "../misc";

View file

@ -1,58 +1,181 @@
import { IMessageContext } from "@saleor/components/messages"; import { IMessageContext } from "@saleor/components/messages";
import { User } from "@saleor/fragments/types/User"; import { APP_DEFAULT_URI, APP_MOUNT_URI, DEMO_MODE } from "@saleor/config";
import useLocalStorage from "@saleor/hooks/useLocalStorage"; import { commonMessages } from "@saleor/intl";
import {
GetExternalAccessTokenData,
LoginData,
useAuth,
useAuthState
} from "@saleor/sdk";
import {
isSupported as isCredentialsManagementAPISupported,
login as loginWithCredentialsManagementAPI,
saveCredentials
} from "@saleor/utils/credentialsManagement";
import ApolloClient from "apollo-client"; import ApolloClient from "apollo-client";
import { MutableRefObject } from "react"; import { useEffect, useState } from "react";
import { useQuery } from "react-apollo";
import { IntlShape } from "react-intl"; import { IntlShape } from "react-intl";
import urlJoin from "url-join";
import { useExternalAuthProvider } from "./useExternalAuthProvider"; import { userDetailsQuery } from "../queries";
import { useSaleorAuthProvider } from "./useSaleorAuthProvider"; import {
ExternalLoginInput,
RequestExternalLoginInput,
RequestExternalLogoutInput,
UserContext,
UserContextError
} from "../types";
import { UserDetails } from "../types/UserDetails";
import { displayDemoMessage } from "../utils";
export interface UseAuthProvider {
logout: () => void;
tokenAuthLoading: boolean;
tokenRefresh: () => Promise<boolean>;
tokenVerifyLoading: boolean;
user?: User;
autologinPromise?: MutableRefObject<Promise<any>>;
}
export interface UseAuthProviderOpts { export interface UseAuthProviderOpts {
intl: IntlShape; intl: IntlShape;
notify: IMessageContext; notify: IMessageContext;
apolloClient: ApolloClient<any>; apolloClient: ApolloClient<any>;
} }
export function useAuthProvider(opts: UseAuthProviderOpts) { export function useAuthProvider({
const [authPlugin, setAuthPlugin] = useLocalStorage("authPlugin", undefined); intl,
notify,
apolloClient
}: UseAuthProviderOpts): UserContext {
const {
login,
getExternalAuthUrl,
getExternalAccessToken,
logout
} = useAuth();
const { authenticated, authenticating, user } = useAuthState();
const [error, setError] = useState<UserContextError>();
const saleorAuth = useSaleorAuthProvider({ useEffect(() => {
authPlugin, if (authenticating && error) {
setAuthPlugin, setError(undefined);
...opts }
}, [authenticating]);
useEffect(() => {
if (!authenticated && !authenticating) {
loginWithCredentialsManagementAPI(handleLogin);
}
}, [authenticated, authenticating]);
const userDetails = useQuery<UserDetails>(userDetailsQuery, {
client: apolloClient,
skip: !authenticated
}); });
const externalAuth = useExternalAuthProvider({ const handleLogout = async () => {
authPlugin, const result = await logout({
setAuthPlugin, input: JSON.stringify({
...opts returnTo: urlJoin(
}); window.location.origin,
APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI
)
} as RequestExternalLogoutInput)
});
const loginAuth = { if (isCredentialsManagementAPISupported) {
login: saleorAuth.login, navigator.credentials.preventSilentAccess();
loginByExternalPlugin: externalAuth.loginByExternalPlugin, }
loginByToken: saleorAuth.loginByToken,
requestLoginByExternalPlugin: externalAuth.requestLoginByExternalPlugin if (!result) {
return;
}
const errors = result.errors || [];
const logoutUrl = JSON.parse(
result.data?.externalLogout?.logoutData || null
)?.logoutUrl;
if (!errors.length && logoutUrl) {
window.location.href = logoutUrl;
}
}; };
if (authPlugin) { const handleLogin = async (email: string, password: string) => {
return { try {
...externalAuth, const result = await login({
...loginAuth email,
}; password
} });
if (result && !result.data.tokenCreate.errors.length) {
if (DEMO_MODE) {
displayDemoMessage(intl, notify);
}
saveCredentials(result.data.tokenCreate.user, password);
} else {
setError("loginError");
}
await logoutNonStaffUser(result.data.tokenCreate);
return result.data.tokenCreate;
} catch (error) {
setError("serverError");
}
};
const handleRequestExternalLogin = async (
pluginId: string,
input: RequestExternalLoginInput
) => {
const result = await getExternalAuthUrl({
pluginId,
input: JSON.stringify(input)
});
return result?.data?.externalAuthenticationUrl;
};
const handleExternalLogin = async (
pluginId: string,
input: ExternalLoginInput
) => {
try {
const result = await getExternalAccessToken({
pluginId,
input: JSON.stringify(input)
});
if (result && !result.data?.externalObtainAccessTokens.errors.length) {
if (DEMO_MODE) {
displayDemoMessage(intl, notify);
}
} else {
setError("externalLoginError");
}
await logoutNonStaffUser(result.data.externalObtainAccessTokens);
return result?.data?.externalObtainAccessTokens;
} catch (error) {
setError("serverError");
}
};
const logoutNonStaffUser = async (
data: LoginData | GetExternalAccessTokenData
) => {
if (data.user && !data.user.isStaff) {
notify({
status: "error",
text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess),
title: intl.formatMessage(commonMessages.insufficientPermissions)
});
await handleLogout();
}
};
return { return {
...saleorAuth, login: handleLogin,
...loginAuth requestLoginByExternalPlugin: handleRequestExternalLogin,
loginByExternalPlugin: handleExternalLogin,
logout: handleLogout,
authenticating: authenticating && !error,
authenticated: authenticated && user?.isStaff,
user: userDetails.data?.me,
error
}; };
} }

View file

@ -1,261 +0,0 @@
import { DEMO_MODE } from "@saleor/config";
import { User } from "@saleor/fragments/types/User";
import { commonMessages } from "@saleor/intl";
import { getFullName, getMutationStatus } from "@saleor/misc";
import errorTracker from "@saleor/services/errorTracking";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
import { useMutation } from "react-apollo";
import {
externalAuthenticationUrlMutation,
externalObtainAccessTokensMutation,
externalTokenRefreshMutation,
externalTokenVerifyMutation
} from "../mutations";
import {
ExternalAuthenticationUrl,
ExternalAuthenticationUrlVariables
} from "../types/ExternalAuthenticationUrl";
import {
ExternalObtainAccessTokens,
ExternalObtainAccessTokens_externalObtainAccessTokens,
ExternalObtainAccessTokensVariables
} from "../types/ExternalObtainAccessTokens";
import {
ExternalRefreshToken,
ExternalRefreshTokenVariables
} from "../types/ExternalRefreshToken";
import {
ExternalVerifyToken,
ExternalVerifyTokenVariables
} from "../types/ExternalVerifyToken";
import {
displayDemoMessage,
getTokens,
removeTokens,
setAuthToken,
setTokens
} from "../utils";
import { UseAuthProvider, UseAuthProviderOpts } from "./useAuthProvider";
export interface RequestExternalLoginInput {
redirectUri: string;
}
export interface ExternalLoginInput {
code: string;
state: string;
}
export interface UseExternalAuthProvider extends UseAuthProvider {
requestLoginByExternalPlugin: (
pluginId: string,
input: RequestExternalLoginInput
) => Promise<void>;
loginByExternalPlugin: (
input: ExternalLoginInput
) => Promise<ExternalObtainAccessTokens_externalObtainAccessTokens>;
}
export interface UseExternalAuthProviderOpts extends UseAuthProviderOpts {
setAuthPlugin: Dispatch<SetStateAction<any>>;
authPlugin: string;
}
const persistToken = false;
export function useExternalAuthProvider({
apolloClient,
authPlugin,
intl,
notify,
setAuthPlugin
}: UseExternalAuthProviderOpts): UseExternalAuthProvider {
const [userContext, setUserContext] = useState<undefined | User>(undefined);
const autologinPromise = useRef<Promise<any>>();
const refreshPromise = useRef<Promise<boolean>>();
useEffect(() => {
const token = getTokens().auth;
if (authPlugin && !!token && !userContext) {
const input = JSON.stringify({
token
});
autologinPromise.current = tokenVerify({
variables: { input, pluginId: authPlugin }
});
}
}, []);
useEffect(() => {
if (authPlugin && userContext) {
const { id, email } = userContext;
errorTracker.setUserData({
email,
id,
username: getFullName(userContext)
});
if (!userContext.isStaff) {
logout();
notify({
status: "error",
text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess),
title: intl.formatMessage(commonMessages.insufficientPermissions)
});
}
}
}, [userContext]);
const logout = () => {
setUserContext(undefined);
setAuthPlugin(undefined);
removeTokens();
};
const [externalAuthenticationUrl] = useMutation<
ExternalAuthenticationUrl,
ExternalAuthenticationUrlVariables
>(externalAuthenticationUrlMutation, {
client: apolloClient,
onError: logout
});
const [obtainAccessTokens, obtainAccessTokensResult] = useMutation<
ExternalObtainAccessTokens,
ExternalObtainAccessTokensVariables
>(externalObtainAccessTokensMutation, {
client: apolloClient,
onCompleted: ({ externalObtainAccessTokens }) => {
if (externalObtainAccessTokens.errors.length > 0) {
logout();
}
const user = externalObtainAccessTokens.user;
setUserContext(user);
if (user) {
setTokens(
externalObtainAccessTokens.token,
externalObtainAccessTokens.csrfToken,
persistToken
);
}
},
onError: logout
});
const [tokenRefresh] = useMutation<
ExternalRefreshToken,
ExternalRefreshTokenVariables
>(externalTokenRefreshMutation, {
client: apolloClient,
onError: logout
});
const [tokenVerify, tokenVerifyResult] = useMutation<
ExternalVerifyToken,
ExternalVerifyTokenVariables
>(externalTokenVerifyMutation, {
client: apolloClient,
onCompleted: result => {
if (result.externalVerify === null) {
logout();
} else {
const user = result.externalVerify?.user;
if (!!user) {
setUserContext(user);
}
}
},
onError: logout
});
const obtainAccessTokensOpts = {
...obtainAccessTokensResult,
status: getMutationStatus(obtainAccessTokensResult)
};
const tokenVerifyOpts = {
...tokenVerifyResult,
status: getMutationStatus(tokenVerifyResult)
};
const onLogin = () => {
if (DEMO_MODE) {
displayDemoMessage(intl, notify);
}
};
const requestLoginByExternalPlugin = async (
pluginId: string,
pluginInput: RequestExternalLoginInput
) => {
const input = JSON.stringify(pluginInput);
const result = await externalAuthenticationUrl({
variables: {
input,
pluginId
}
});
if (result && !result.data.externalAuthenticationUrl.errors.length) {
setAuthPlugin(pluginId);
const authenticationData = JSON.parse(
result.data.externalAuthenticationUrl.authenticationData
);
location.href = authenticationData.authorizationUrl;
} else {
setAuthPlugin(undefined);
}
};
const loginByExternalPlugin = async (loginInput: ExternalLoginInput) => {
const input = JSON.stringify(loginInput);
const result = await obtainAccessTokens({
variables: { input, pluginId: authPlugin }
});
if (result && !result.data?.externalObtainAccessTokens?.errors?.length) {
if (!!onLogin) {
onLogin();
}
} else {
setAuthPlugin(undefined);
}
return result?.data?.externalObtainAccessTokens;
};
const refreshToken = (): Promise<boolean> => {
if (!!refreshPromise.current) {
return refreshPromise.current;
}
return new Promise(resolve => {
const token = getTokens().refresh;
const input = JSON.stringify({
refreshToken: token
});
return tokenRefresh({ variables: { input, pluginId: authPlugin } }).then(
refreshData => {
if (!!refreshData.data.externalRefresh?.token) {
setAuthToken(refreshData.data.externalRefresh.token, persistToken);
return resolve(true);
}
return resolve(false);
}
);
});
};
return {
autologinPromise,
loginByExternalPlugin,
logout,
requestLoginByExternalPlugin,
tokenAuthLoading: obtainAccessTokensOpts.loading,
tokenRefresh: refreshToken,
tokenVerifyLoading: tokenVerifyOpts.loading,
user: userContext
};
}

View file

@ -1,203 +0,0 @@
import { DEMO_MODE } from "@saleor/config";
import { User } from "@saleor/fragments/types/User";
import { commonMessages } from "@saleor/intl";
import { getFullName, getMutationStatus } from "@saleor/misc";
import errorTracker from "@saleor/services/errorTracking";
import {
isSupported as isCredentialsManagementAPISupported,
login as loginWithCredentialsManagementAPI,
saveCredentials
} from "@saleor/utils/credentialsManagement";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
import { useMutation } from "react-apollo";
import {
tokenAuthMutation,
tokenRefreshMutation,
tokenVerifyMutation
} from "../mutations";
import { RefreshToken, RefreshTokenVariables } from "../types/RefreshToken";
import {
TokenAuth,
TokenAuth_tokenCreate,
TokenAuthVariables
} from "../types/TokenAuth";
import { VerifyToken, VerifyTokenVariables } from "../types/VerifyToken";
import {
displayDemoMessage,
getTokens,
removeTokens,
setAuthToken,
setTokens
} from "../utils";
import { UseAuthProvider, UseAuthProviderOpts } from "./useAuthProvider";
export interface UseSaleorAuthProvider extends UseAuthProvider {
login: (username: string, password: string) => Promise<TokenAuth_tokenCreate>;
loginByToken: (auth: string, csrf: string, user: User) => void;
}
export interface UseSaleorAuthProviderOpts extends UseAuthProviderOpts {
setAuthPlugin: Dispatch<SetStateAction<any>>;
authPlugin: string;
}
const persistToken = false;
export function useSaleorAuthProvider({
apolloClient,
authPlugin,
intl,
notify,
setAuthPlugin
}: UseSaleorAuthProviderOpts): UseSaleorAuthProvider {
const [userContext, setUserContext] = useState<undefined | User>(undefined);
const autologinPromise = useRef<Promise<any>>();
const refreshPromise = useRef<Promise<boolean>>();
useEffect(() => {
const token = getTokens().auth;
if (!authPlugin && !!token && !userContext) {
autologinPromise.current = tokenVerify({ variables: { token } });
} else if (!authPlugin) {
autologinPromise.current = loginWithCredentialsManagementAPI(login);
}
}, []);
useEffect(() => {
if (!authPlugin && userContext) {
const { id, email } = userContext;
errorTracker.setUserData({
email,
id,
username: getFullName(userContext)
});
if (!userContext.isStaff) {
logout();
notify({
status: "error",
text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess),
title: intl.formatMessage(commonMessages.insufficientPermissions)
});
}
}
}, [userContext]);
const logout = () => {
setUserContext(undefined);
if (isCredentialsManagementAPISupported) {
navigator.credentials.preventSilentAccess();
}
removeTokens();
};
const [tokenAuth, tokenAuthResult] = useMutation<
TokenAuth,
TokenAuthVariables
>(tokenAuthMutation, {
client: apolloClient,
onCompleted: ({ tokenCreate }) => {
if (tokenCreate.errors.length > 0) {
logout();
}
const user = tokenCreate.user;
setUserContext(user);
if (user) {
setTokens(tokenCreate.token, 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);
}
};
const login = async (email: string, password: string) => {
setAuthPlugin(undefined);
const result = await tokenAuth({ variables: { email, password } });
if (result && !result.data.tokenCreate.errors.length) {
if (!!onLogin) {
onLogin();
}
saveCredentials(result.data.tokenCreate.user, password);
}
return result.data.tokenCreate;
};
const loginByToken = (auth: string, refresh: string, user: User) => {
setAuthPlugin(undefined);
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);
});
});
};
return {
autologinPromise,
login,
loginByToken,
logout,
tokenAuthLoading: tokenAuthOpts.loading,
tokenRefresh: refreshToken,
tokenVerifyLoading: tokenVerifyOpts.loading,
user: userContext
};
}

View file

@ -1,15 +1,9 @@
import { User } from "@saleor/fragments/types/User";
import { parse as parseQs } from "qs"; import { parse as parseQs } from "qs";
import React, { MutableRefObject } from "react"; import React, { useContext } from "react";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import Layout from "./components/Layout"; import Layout from "./components/Layout";
import { import { UserContext as Context } from "./types";
ExternalLoginInput,
RequestExternalLoginInput
} from "./hooks/useExternalAuthProvider";
import { ExternalObtainAccessTokens_externalObtainAccessTokens } from "./types/ExternalObtainAccessTokens";
import { TokenAuth_tokenCreate } from "./types/TokenAuth";
import { import {
LoginUrlQueryParams, LoginUrlQueryParams,
newPasswordPath, newPasswordPath,
@ -28,33 +22,13 @@ const LoginView: React.FC<RouteComponentProps<any>> = () => {
return <LoginViewComponent params={params} />; return <LoginViewComponent params={params} />;
}; };
interface UserContext { export const UserContext = React.createContext<Context>({
login: (username: string, password: string) => Promise<TokenAuth_tokenCreate>;
loginByExternalPlugin: (
input: ExternalLoginInput
) => Promise<ExternalObtainAccessTokens_externalObtainAccessTokens>;
loginByToken: (auth: string, csrf: string, user: User) => void;
logout: () => void;
requestLoginByExternalPlugin: (
pluginId: string,
input: RequestExternalLoginInput
) => Promise<void>;
tokenAuthLoading: boolean;
tokenRefresh: () => Promise<boolean>;
tokenVerifyLoading: boolean;
user?: User;
autologinPromise?: MutableRefObject<Promise<any>>;
}
export const UserContext = React.createContext<UserContext>({
login: undefined, login: undefined,
loginByExternalPlugin: undefined, loginByExternalPlugin: undefined,
loginByToken: undefined,
logout: undefined, logout: undefined,
requestLoginByExternalPlugin: undefined, requestLoginByExternalPlugin: undefined,
tokenAuthLoading: false, authenticating: false,
tokenRefresh: undefined, authenticated: false
tokenVerifyLoading: false
}); });
const AuthRouter: React.FC = () => ( const AuthRouter: React.FC = () => (
@ -72,3 +46,4 @@ AuthRouter.displayName = "AuthRouter";
export default AuthRouter; export default AuthRouter;
export * from "./utils"; export * from "./utils";
export const useUser = () => useContext(UserContext);

View file

@ -1,39 +0,0 @@
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-Bearer": authToken || null
}
};
});
const link = invalidateTokenLink.concat(tokenLink);
export default link;

View file

@ -1,4 +1,3 @@
import { fragmentUser } from "@saleor/fragments/auth";
import { accountErrorFragment } from "@saleor/fragments/errors"; import { accountErrorFragment } from "@saleor/fragments/errors";
import gql from "graphql-tag"; import gql from "graphql-tag";
@ -7,44 +6,6 @@ import {
RequestPasswordReset, RequestPasswordReset,
RequestPasswordResetVariables RequestPasswordResetVariables
} from "./types/RequestPasswordReset"; } from "./types/RequestPasswordReset";
import { SetPassword, SetPasswordVariables } from "./types/SetPassword";
export const tokenAuthMutation = gql`
${fragmentUser}
mutation TokenAuth($email: String!, $password: String!) {
tokenCreate(email: $email, password: $password) {
errors {
field
message
}
csrfToken
token
user {
...User
}
}
}
`;
export const tokenVerifyMutation = gql`
${fragmentUser}
mutation VerifyToken($token: String!) {
tokenVerify(token: $token) {
payload
user {
...User
}
}
}
`;
export const tokenRefreshMutation = gql`
mutation RefreshToken($token: String!) {
tokenRefresh(csrfToken: $token) {
token
}
}
`;
export const requestPasswordReset = gql` export const requestPasswordReset = gql`
${accountErrorFragment} ${accountErrorFragment}
@ -60,74 +21,3 @@ export const RequestPasswordResetMutation = TypedMutation<
RequestPasswordReset, RequestPasswordReset,
RequestPasswordResetVariables RequestPasswordResetVariables
>(requestPasswordReset); >(requestPasswordReset);
export const setPassword = gql`
${accountErrorFragment}
${fragmentUser}
mutation SetPassword($email: String!, $password: String!, $token: String!) {
setPassword(email: $email, password: $password, token: $token) {
errors {
...AccountErrorFragment
}
csrfToken
refreshToken
token
user {
...User
}
}
}
`;
export const SetPasswordMutation = TypedMutation<
SetPassword,
SetPasswordVariables
>(setPassword);
export const externalAuthenticationUrlMutation = gql`
${accountErrorFragment}
mutation ExternalAuthenticationUrl($pluginId: String!, $input: JSONString!) {
externalAuthenticationUrl(pluginId: $pluginId, input: $input) {
authenticationData
errors {
...AccountErrorFragment
}
}
}
`;
export const externalObtainAccessTokensMutation = gql`
${accountErrorFragment}
${fragmentUser}
mutation ExternalObtainAccessTokens($pluginId: String!, $input: JSONString!) {
externalObtainAccessTokens(pluginId: $pluginId, input: $input) {
token
csrfToken
user {
...User
}
errors {
...AccountErrorFragment
}
}
}
`;
export const externalTokenRefreshMutation = gql`
mutation ExternalRefreshToken($pluginId: String!, $input: JSONString!) {
externalRefresh(pluginId: $pluginId, input: $input) {
token
}
}
`;
export const externalTokenVerifyMutation = gql`
${fragmentUser}
mutation ExternalVerifyToken($pluginId: String!, $input: JSONString!) {
externalVerify(pluginId: $pluginId, input: $input) {
verifyData
user {
...User
}
}
}
`;

View file

@ -1,3 +1,4 @@
import { fragmentUser } from "@saleor/fragments/auth";
import gql from "graphql-tag"; import gql from "graphql-tag";
export const availableExternalAuthentications = gql` export const availableExternalAuthentications = gql`
@ -10,3 +11,12 @@ export const availableExternalAuthentications = gql`
} }
} }
`; `;
export const userDetailsQuery = gql`
${fragmentUser}
query UserDetails {
me {
...User
}
}
`;

41
src/auth/types.ts Normal file
View file

@ -0,0 +1,41 @@
import { User } from "@saleor/fragments/types/User";
import {
GetExternalAccessTokenData,
GetExternalAuthUrlData,
LoginData
} from "@saleor/sdk";
export interface RequestExternalLoginInput {
redirectUri: string;
}
export interface ExternalLoginInput {
code: string;
state: string;
}
export interface RequestExternalLogoutInput {
returnTo: string;
}
export type UserContextError =
| "loginError"
| "externalLoginError"
| "serverError";
export interface UserContext {
login: (username: string, password: string) => Promise<LoginData>;
loginByExternalPlugin: (
pluginId: string,
input: ExternalLoginInput
) => Promise<GetExternalAccessTokenData>;
logout: () => Promise<void>;
requestLoginByExternalPlugin: (
pluginId: string,
input: RequestExternalLoginInput
) => Promise<GetExternalAuthUrlData>;
user?: User;
authenticating: boolean;
authenticated: boolean;
error?: UserContextError;
}

View file

@ -0,0 +1,36 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: UserDetails
// ====================================================
export interface UserDetails_me_userPermissions {
__typename: "UserPermission";
code: PermissionEnum;
name: string;
}
export interface UserDetails_me_avatar {
__typename: "Image";
url: string;
}
export interface UserDetails_me {
__typename: "User";
id: string;
email: string;
firstName: string;
lastName: string;
isStaff: boolean;
userPermissions: (UserDetails_me_userPermissions | null)[] | null;
avatar: UserDetails_me_avatar | null;
}
export interface UserDetails {
me: UserDetails_me | null;
}

View file

@ -6,43 +6,6 @@ import { IntlShape } from "react-intl";
import { isJwtError, isTokenExpired } from "./errors"; import { isJwtError, isTokenExpired } from "./errors";
export enum TOKEN_STORAGE_KEY {
AUTH = "auth",
CSRF = "csrf"
}
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 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 = ( export const displayDemoMessage = (
intl: IntlShape, intl: IntlShape,
notify: UseNotifierResult notify: UseNotifierResult
@ -55,23 +18,17 @@ export const displayDemoMessage = (
export async function handleQueryAuthError( export async function handleQueryAuthError(
error: ApolloError, error: ApolloError,
notify: IMessageContext, notify: IMessageContext,
tokenRefresh: () => Promise<boolean>,
logout: () => void, logout: () => void,
intl: IntlShape intl: IntlShape
) { ) {
if (error.graphQLErrors.some(isJwtError)) { if (error.graphQLErrors.some(isJwtError)) {
logout();
if (error.graphQLErrors.every(isTokenExpired)) { if (error.graphQLErrors.every(isTokenExpired)) {
const success = await tokenRefresh(); notify({
status: "error",
if (!success) { text: intl.formatMessage(commonMessages.sessionExpired)
logout(); });
notify({
status: "error",
text: intl.formatMessage(commonMessages.sessionExpired)
});
}
} else { } else {
logout();
notify({ notify({
status: "error", status: "error",
text: intl.formatMessage(commonMessages.somethingWentWrong) text: intl.formatMessage(commonMessages.somethingWentWrong)

View file

@ -1,11 +1,12 @@
import { APP_DEFAULT_URI, APP_MOUNT_URI } from "@saleor/config"; import { APP_DEFAULT_URI, APP_MOUNT_URI } from "@saleor/config";
import useLocalStorage from "@saleor/hooks/useLocalStorage";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser"; import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { useQuery } from "react-apollo"; import { useQuery } from "react-apollo";
import urlJoin from "url-join"; import urlJoin from "url-join";
import useRouter from "use-react-router"; import useRouter from "use-react-router";
import { useUser } from "..";
import LoginPage from "../components/LoginPage"; import LoginPage from "../components/LoginPage";
import { LoginFormData } from "../components/LoginPage/form"; import { LoginFormData } from "../components/LoginPage/form";
import { availableExternalAuthentications } from "../queries"; import { availableExternalAuthentications } from "../queries";
@ -27,46 +28,50 @@ const LoginView: React.FC<LoginViewProps> = ({ params }) => {
login, login,
requestLoginByExternalPlugin, requestLoginByExternalPlugin,
loginByExternalPlugin, loginByExternalPlugin,
tokenAuthLoading authenticating,
error
} = useUser(); } = useUser();
const [isError, setIsError] = useState(false);
const [isExternalError, setIsExternalError] = useState(false);
const { const {
data: externalAuthentications, data: externalAuthentications,
loading: externalAuthenticationsLoading loading: externalAuthenticationsLoading
} = useQuery<AvailableExternalAuthentications>( } = useQuery<AvailableExternalAuthentications>(
availableExternalAuthentications availableExternalAuthentications
); );
const [
requestedExternalPluginId,
setRequestedExternalPluginId
] = useLocalStorage("requestedExternalPluginId", null);
const handleSubmit = async (data: LoginFormData) => { const handleSubmit = async (data: LoginFormData) => {
const result = await login(data.email, data.password); const result = await login(data.email, data.password);
const errors = result?.errors || []; const errors = result?.errors || [];
setIsExternalError(false);
setIsError(!result || errors?.length > 0);
return errors; return errors;
}; };
const handleRequestExternalAuthentication = (pluginId: string) => const handleRequestExternalAuthentication = async (pluginId: string) => {
requestLoginByExternalPlugin(pluginId, { const result = await requestLoginByExternalPlugin(pluginId, {
redirectUri: urlJoin( redirectUri: urlJoin(
window.location.origin, window.location.origin,
APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI, APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI,
loginCallbackPath loginCallbackPath
) )
}); });
const data = JSON.parse(result?.authenticationData || "");
if (data && !result?.errors?.length) {
setRequestedExternalPluginId(pluginId);
window.location.href = data.authorizationUrl;
}
};
const handleExternalAuthentication = async (code: string, state: string) => { const handleExternalAuthentication = async (code: string, state: string) => {
const result = await loginByExternalPlugin({ code, state }); const result = await loginByExternalPlugin(requestedExternalPluginId, {
const errors = result?.errors || []; code,
state
setIsError(false); });
if (!result || errors?.length > 0) { if (result && !result?.errors?.length) {
setIsExternalError(true);
} else {
navigate(APP_DEFAULT_URI); navigate(APP_DEFAULT_URI);
} }
return errors;
}; };
useEffect(() => { useEffect(() => {
@ -80,13 +85,12 @@ const LoginView: React.FC<LoginViewProps> = ({ params }) => {
return ( return (
<LoginPage <LoginPage
error={isError} error={error}
externalError={isExternalError} disabled={authenticating}
disabled={tokenAuthLoading}
externalAuthentications={ externalAuthentications={
externalAuthentications?.shop?.availableExternalAuthentications externalAuthentications?.shop?.availableExternalAuthentications
} }
loading={externalAuthenticationsLoading || tokenAuthLoading} loading={externalAuthenticationsLoading || authenticating}
onExternalAuthentication={handleRequestExternalAuthentication} onExternalAuthentication={handleRequestExternalAuthentication}
onPasswordRecovery={() => navigate(passwordResetUrl)} onPasswordRecovery={() => navigate(passwordResetUrl)}
onSubmit={handleSubmit} onSubmit={handleSubmit}

View file

@ -1,54 +1,49 @@
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser"; import { SetPasswordData, useAuth } from "@saleor/sdk";
import { parse as parseQs } from "qs"; import { parse as parseQs } from "qs";
import React from "react"; import React, { useState } from "react";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
import NewPasswordPage, { import NewPasswordPage, {
NewPasswordPageFormData NewPasswordPageFormData
} from "../components/NewPasswordPage"; } from "../components/NewPasswordPage";
import { SetPasswordMutation } from "../mutations";
import { SetPassword } from "../types/SetPassword";
import { NewPasswordUrlQueryParams } from "../urls"; import { NewPasswordUrlQueryParams } from "../urls";
const NewPassword: React.FC<RouteComponentProps> = ({ location }) => { const NewPassword: React.FC<RouteComponentProps> = ({ location }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const { loginByToken } = useUser();
const { setPassword } = useAuth();
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<SetPasswordData["errors"]>([]);
const params: NewPasswordUrlQueryParams = parseQs(location.search.substr(1)); const params: NewPasswordUrlQueryParams = parseQs(location.search.substr(1));
const handleSetPassword = async (data: SetPassword) => { const handleSubmit = async (data: NewPasswordPageFormData) => {
if (data.setPassword.errors.length === 0) { setLoading(true);
loginByToken(
data.setPassword.token, const result = await setPassword({
data.setPassword.csrfToken, email: params.email,
data.setPassword.user password: data.password,
); token: params.token
});
const errors = result.data?.setPassword?.errors || [];
setErrors(errors);
setLoading(false);
if (!errors.length) {
navigate("/", { replace: true }); navigate("/", { replace: true });
} }
}; };
return ( return (
<SetPasswordMutation onCompleted={handleSetPassword}> <NewPasswordPage
{(setPassword, setPasswordOpts) => { errors={errors}
const handleSubmit = (data: NewPasswordPageFormData) => disabled={loading}
setPassword({ onSubmit={handleSubmit}
variables: { />
email: params.email,
password: data.password,
token: params.token
}
});
return (
<NewPasswordPage
errors={setPasswordOpts.data?.setPassword.errors || []}
disabled={setPasswordOpts.loading}
onSubmit={handleSubmit}
/>
);
}}
</SetPasswordMutation>
); );
}; };

View file

@ -8,9 +8,9 @@ import {
ListItemText, ListItemText,
Typography Typography
} from "@material-ui/core"; } from "@material-ui/core";
import { useUser } from "@saleor/auth";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import useUser from "@saleor/hooks/useUser";
import { makeStyles } from "@saleor/macaw-ui"; import { makeStyles } from "@saleor/macaw-ui";
import { PermissionData } from "@saleor/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage"; import { PermissionData } from "@saleor/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage";
import React from "react"; import React from "react";

View file

@ -1,9 +1,10 @@
import { useAuth } from "@saleor/auth/AuthProvider"; import { useUser } from "@saleor/auth";
import { useBaseChannelsList } from "@saleor/channels/queries"; import { useBaseChannelsList } from "@saleor/channels/queries";
import { BaseChannels_channels } from "@saleor/channels/types/BaseChannels"; import { BaseChannels_channels } from "@saleor/channels/types/BaseChannels";
import { ChannelFragment } from "@saleor/fragments/types/ChannelFragment"; import { ChannelFragment } from "@saleor/fragments/types/ChannelFragment";
import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useLocalStorage from "@saleor/hooks/useLocalStorage";
import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
import { useSaleorConfig } from "@saleor/sdk";
import React from "react"; import React from "react";
interface UseAppChannel { interface UseAppChannel {
@ -38,10 +39,11 @@ const isValidChannel = (
}; };
export const AppChannelProvider: React.FC = ({ children }) => { export const AppChannelProvider: React.FC = ({ children }) => {
const { isAuthenticated } = useAuth(); const { setChannel } = useSaleorConfig();
const { authenticated } = useUser();
const [selectedChannel, setSelectedChannel] = useLocalStorage("channel", ""); const [selectedChannel, setSelectedChannel] = useLocalStorage("channel", "");
const { data: channelData, refetch } = useBaseChannelsList({ const { data: channelData, refetch } = useBaseChannelsList({
skip: !isAuthenticated skip: !authenticated
}); });
const [isPickerActive, setPickerActive] = React.useState(false); const [isPickerActive, setPickerActive] = React.useState(false);
@ -54,6 +56,10 @@ export const AppChannelProvider: React.FC = ({ children }) => {
} }
}, [channelData]); }, [channelData]);
React.useEffect(() => {
setChannel(selectedChannel);
}, [selectedChannel]);
const availableChannels = channelData?.channels || []; const availableChannels = channelData?.channels || [];
const channel = const channel =

View file

@ -1,7 +1,7 @@
import { LinearProgress, useMediaQuery } from "@material-ui/core"; import { LinearProgress, useMediaQuery } from "@material-ui/core";
import { useUser } from "@saleor/auth";
import useAppState from "@saleor/hooks/useAppState"; import useAppState from "@saleor/hooks/useAppState";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser";
import { import {
makeStyles, makeStyles,
SaleorTheme, SaleorTheme,

View file

@ -1,8 +1,8 @@
import { Button, Card, CardContent, Typography } from "@material-ui/core"; import { Button, Card, CardContent, Typography } from "@material-ui/core";
import { useUser } from "@saleor/auth";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import RequirePermissions from "@saleor/components/RequirePermissions"; import RequirePermissions from "@saleor/components/RequirePermissions";
import useUser from "@saleor/hooks/useUser";
import { PermissionEnum } from "@saleor/types/globalTypes"; import { PermissionEnum } from "@saleor/types/globalTypes";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";

View file

@ -2,7 +2,7 @@ import appleTouchIcon from "@assets/favicons/apple-touch-icon.png";
import favicon16 from "@assets/favicons/favicon-16x16.png"; import favicon16 from "@assets/favicons/favicon-16x16.png";
import favicon32 from "@assets/favicons/favicon-32x32.png"; import favicon32 from "@assets/favicons/favicon-32x32.png";
import safariPinnedTab from "@assets/favicons/safari-pinned-tab.svg"; import safariPinnedTab from "@assets/favicons/safari-pinned-tab.svg";
import { useAuth } from "@saleor/auth/AuthProvider"; import { useUser } from "@saleor/auth";
import React from "react"; import React from "react";
import Helmet from "react-helmet"; import Helmet from "react-helmet";
@ -14,10 +14,10 @@ type ShopContext = ShopInfo_shop;
export const ShopContext = React.createContext<ShopContext>(undefined); export const ShopContext = React.createContext<ShopContext>(undefined);
export const ShopProvider: React.FC = ({ children }) => { export const ShopProvider: React.FC = ({ children }) => {
const { isAuthenticated } = useAuth(); const { authenticated } = useUser();
return ( return (
<TypedShopInfoQuery skip={!isAuthenticated}> <TypedShopInfoQuery skip={!authenticated}>
{({ data }) => ( {({ data }) => (
<> <>
<Helmet> <Helmet>

View file

@ -1,10 +1,10 @@
import { attributeListUrl } from "@saleor/attributes/urls"; import { attributeListUrl } from "@saleor/attributes/urls";
import { useUser } from "@saleor/auth";
import { channelsListUrl } from "@saleor/channels/urls"; import { channelsListUrl } from "@saleor/channels/urls";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import { APP_VERSION as dashboardVersion } from "@saleor/config"; import { APP_VERSION as dashboardVersion } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import useUser from "@saleor/hooks/useUser";
import Attributes from "@saleor/icons/Attributes"; import Attributes from "@saleor/icons/Attributes";
import Channels from "@saleor/icons/Channels"; import Channels from "@saleor/icons/Channels";
import Navigation from "@saleor/icons/Navigation"; import Navigation from "@saleor/icons/Navigation";

View file

@ -1,7 +1,7 @@
import { useUser } from "@saleor/auth";
import { channelsListUrl } from "@saleor/channels/urls"; import { channelsListUrl } from "@saleor/channels/urls";
import useAppChannel from "@saleor/components/AppLayout/AppChannelContext"; import useAppChannel from "@saleor/components/AppLayout/AppChannelContext";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser";
import { mapEdgesToItems } from "@saleor/utils/maps"; import { mapEdgesToItems } from "@saleor/utils/maps";
import React from "react"; import React from "react";

View file

@ -1,3 +1,4 @@
import { useUser } from "@saleor/auth";
import { isJwtError } from "@saleor/auth/errors"; import { isJwtError } from "@saleor/auth/errors";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getMutationStatus } from "@saleor/misc"; import { getMutationStatus } from "@saleor/misc";
@ -13,7 +14,6 @@ import {
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import useNotifier from "./useNotifier"; import useNotifier from "./useNotifier";
import useUser from "./useUser";
export type MutationResultWithOpts<TData> = MutationResult<TData> & export type MutationResultWithOpts<TData> = MutationResult<TData> &
MutationResultAdditionalProps; MutationResultAdditionalProps;

View file

@ -1,4 +1,5 @@
import { handleQueryAuthError } from "@saleor/auth"; import { handleQueryAuthError } from "@saleor/auth";
import { useUser } from "@saleor/auth";
import { RequireAtLeastOne } from "@saleor/misc"; import { RequireAtLeastOne } from "@saleor/misc";
import { ApolloQueryResult, WatchQueryFetchPolicy } from "apollo-client"; import { ApolloQueryResult, WatchQueryFetchPolicy } from "apollo-client";
import { DocumentNode } from "graphql"; import { DocumentNode } from "graphql";
@ -11,7 +12,6 @@ import { PrefixedPermissions } from "../types/extendedTypes";
import { PermissionEnum } from "../types/globalTypes"; import { PermissionEnum } from "../types/globalTypes";
import useAppState from "./useAppState"; import useAppState from "./useAppState";
import useNotifier from "./useNotifier"; import useNotifier from "./useNotifier";
import useUser from "./useUser";
const getPermissionKey = (permission: string) => const getPermissionKey = (permission: string) =>
`PERMISSION_${permission}` as PrefixedPermissions; `PERMISSION_${permission}` as PrefixedPermissions;
@ -81,14 +81,7 @@ function makeQuery<TData, TVariables>(
}, },
errorPolicy: "all", errorPolicy: "all",
fetchPolicy: fetchPolicy || "cache-and-network", fetchPolicy: fetchPolicy || "cache-and-network",
onError: error => onError: error => handleQueryAuthError(error, notify, user.logout, intl),
handleQueryAuthError(
error,
notify,
user.tokenRefresh,
user.logout,
intl
),
skip, skip,
variables: variablesWithPermissions variables: variablesWithPermissions
}); });

View file

@ -1,9 +0,0 @@
import { useContext } from "react";
import { UserContext } from "../auth";
function useUser() {
const user = useContext(UserContext);
return user;
}
export default useUser;

View file

@ -1,6 +1,7 @@
import DemoBanner from "@saleor/components/DemoBanner"; import DemoBanner from "@saleor/components/DemoBanner";
import useAppState from "@saleor/hooks/useAppState"; import useAppState from "@saleor/hooks/useAppState";
import { ThemeProvider } from "@saleor/macaw-ui"; import { ThemeProvider } from "@saleor/macaw-ui";
import { createFetch, createSaleorClient, SaleorProvider } from "@saleor/sdk";
import { defaultDataIdFromObject, InMemoryCache } from "apollo-cache-inmemory"; import { defaultDataIdFromObject, InMemoryCache } from "apollo-cache-inmemory";
import { IntrospectionFragmentMatcher } from "apollo-cache-inmemory"; import { IntrospectionFragmentMatcher } from "apollo-cache-inmemory";
import { ApolloClient } from "apollo-client"; import { ApolloClient } from "apollo-client";
@ -21,11 +22,10 @@ import { ExternalAppProvider } from "./apps/components/ExternalAppContext";
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 from "./auth"; import Auth, { useUser } from "./auth";
import AuthProvider, { useAuth } from "./auth/AuthProvider"; import AuthProvider 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 authLink from "./auth/link";
import CategorySection from "./categories"; import CategorySection from "./categories";
import ChannelsSection from "./channels"; import ChannelsSection from "./channels";
import { channelsSection } from "./channels/urls"; import { channelsSection } from "./channels/urls";
@ -83,7 +83,8 @@ errorTracker.init();
// so we need to explicitly set them // so we need to explicitly set them
const linkOptions = { const linkOptions = {
credentials: "include", credentials: "include",
uri: API_URI uri: API_URI,
fetch: createFetch()
}; };
const uploadLink = createUploadLink(linkOptions); const uploadLink = createUploadLink(linkOptions);
const batchLink = new BatchHttpLink({ const batchLink = new BatchHttpLink({
@ -113,60 +114,57 @@ const apolloClient = new ApolloClient({
return defaultDataIdFromObject(obj); return defaultDataIdFromObject(obj);
} }
}), }),
link: authLink.concat(link) link
});
const saleorClient = createSaleorClient({
apiUrl: API_URI,
channel: ""
}); });
const App: React.FC = () => ( const App: React.FC = () => (
<ApolloProvider client={apolloClient}> <SaleorProvider client={saleorClient}>
<BrowserRouter basename={APP_MOUNT_URI}> <ApolloProvider client={apolloClient}>
<ThemeProvider overrides={themeOverrides}> <BrowserRouter basename={APP_MOUNT_URI}>
<DateProvider> <ThemeProvider overrides={themeOverrides}>
<LocaleProvider> <DateProvider>
<MessageManagerProvider> <LocaleProvider>
<ServiceWorker /> <MessageManagerProvider>
<BackgroundTasksProvider> <ServiceWorker />
<AppStateProvider> <BackgroundTasksProvider>
<AuthProvider> <AppStateProvider>
<ShopProvider> <AuthProvider>
<AppChannelProvider> <ShopProvider>
<ExternalAppProvider> <AppChannelProvider>
<Routes /> <ExternalAppProvider>
</ExternalAppProvider> <Routes />
</AppChannelProvider> </ExternalAppProvider>
</ShopProvider> </AppChannelProvider>
</AuthProvider> </ShopProvider>
</AppStateProvider> </AuthProvider>
</BackgroundTasksProvider> </AppStateProvider>
</MessageManagerProvider> </BackgroundTasksProvider>
</LocaleProvider> </MessageManagerProvider>
</DateProvider> </LocaleProvider>
</ThemeProvider> </DateProvider>
</BrowserRouter> </ThemeProvider>
</ApolloProvider> </BrowserRouter>
</ApolloProvider>
</SaleorProvider>
); );
const Routes: React.FC = () => { const Routes: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const [, dispatchAppState] = useAppState(); const [, dispatchAppState] = useAppState();
const { const { authenticated, authenticating } = useUser();
hasToken,
isAuthenticated,
tokenAuthLoading,
tokenVerifyLoading
} = useAuth();
const { channel } = useAppChannel(false); const { channel } = useAppChannel(false);
const channelLoaded = typeof channel !== "undefined"; const channelLoaded = typeof channel !== "undefined";
const homePageLoaded = const homePageLoaded = channelLoaded && authenticated;
channelLoaded &&
isAuthenticated &&
!tokenAuthLoading &&
!tokenVerifyLoading;
const homePageLoading = const homePageLoading = (authenticated && !channelLoaded) || authenticating;
(isAuthenticated && !channelLoaded) || (hasToken && tokenVerifyLoading);
return ( return (
<> <>

View file

@ -4,9 +4,9 @@ import React from "react";
import { Mutation, MutationFunction, MutationResult } from "react-apollo"; import { Mutation, MutationFunction, MutationResult } from "react-apollo";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { useUser } from "./auth";
import { isJwtError } from "./auth/errors"; import { isJwtError } from "./auth/errors";
import useNotifier from "./hooks/useNotifier"; import useNotifier from "./hooks/useNotifier";
import useUser from "./hooks/useUser";
import { commonMessages } from "./intl"; import { commonMessages } from "./intl";
import { getMutationStatus } from "./misc"; import { getMutationStatus } from "./misc";
import { MutationResultAdditionalProps } from "./types"; import { MutationResultAdditionalProps } from "./types";

View file

@ -1,8 +1,8 @@
import { useUser } from "@saleor/auth";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { useCustomerAddressesQuery } from "@saleor/customers/queries"; import { useCustomerAddressesQuery } from "@saleor/customers/queries";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser";
import { CustomerEditData } from "@saleor/orders/components/OrderCustomer"; import { CustomerEditData } from "@saleor/orders/components/OrderCustomer";
import OrderCustomerAddressesEditDialog, { import OrderCustomerAddressesEditDialog, {
OrderCustomerAddressesEditDialogOutput OrderCustomerAddressesEditDialogOutput

View file

@ -1,6 +1,6 @@
import { useUser } from "@saleor/auth";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser";
import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog"; import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog";
import OrderFulfillmentApproveDialog from "@saleor/orders/components/OrderFulfillmentApproveDialog"; import OrderFulfillmentApproveDialog from "@saleor/orders/components/OrderFulfillmentApproveDialog";
import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog"; import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog";

View file

@ -1,7 +1,7 @@
import { useUser } from "@saleor/auth";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser";
import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog"; import OrderCannotCancelOrderDialog from "@saleor/orders/components/OrderCannotCancelOrderDialog";
import OrderFulfillmentApproveDialog from "@saleor/orders/components/OrderFulfillmentApproveDialog"; import OrderFulfillmentApproveDialog from "@saleor/orders/components/OrderFulfillmentApproveDialog";
import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog"; import OrderInvoiceEmailSendDialog from "@saleor/orders/components/OrderInvoiceEmailSendDialog";

View file

@ -1,8 +1,8 @@
import { useUser } from "@saleor/auth";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import useUser from "@saleor/hooks/useUser";
import { PermissionData } from "@saleor/permissionGroups/components/PermissionGroupDetailsPage"; import { PermissionData } from "@saleor/permissionGroups/components/PermissionGroupDetailsPage";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";

View file

@ -1,11 +1,11 @@
import { Button } from "@material-ui/core"; import { Button } from "@material-ui/core";
import { useUser } from "@saleor/auth";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import useUser from "@saleor/hooks/useUser";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import MembersErrorDialog from "@saleor/permissionGroups/components/MembersErrorDialog"; import MembersErrorDialog from "@saleor/permissionGroups/components/MembersErrorDialog";
import { import {

View file

@ -4,10 +4,9 @@ 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 { handleQueryAuthError } from "./auth"; import { handleQueryAuthError, useUser } 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 { RequireAtLeastOne } from "./misc"; import { RequireAtLeastOne } from "./misc";
export interface LoadMore<TData, TVariables> { export interface LoadMore<TData, TVariables> {
@ -79,13 +78,7 @@ export function TypedQuery<TData, TVariables>(
context={{ useBatching: true }} context={{ useBatching: true }}
errorPolicy="all" errorPolicy="all"
onError={error => onError={error =>
handleQueryAuthError( handleQueryAuthError(error, notify, user.logout, intl)
error,
notify,
user.tokenRefresh,
user.logout,
intl
)
} }
> >
{(queryData: QueryResult<TData, TVariables>) => { {(queryData: QueryResult<TData, TVariables>) => {

View file

@ -1,5 +1,6 @@
import { DialogContentText, IconButton } from "@material-ui/core"; import { DialogContentText, IconButton } from "@material-ui/core";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import { useUser } from "@saleor/auth";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import { configurationMenuUrl } from "@saleor/configuration"; import { configurationMenuUrl } from "@saleor/configuration";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
@ -11,7 +12,6 @@ import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import useUser from "@saleor/hooks/useUser";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getStringOrPlaceholder, maybe } from "@saleor/misc"; import { getStringOrPlaceholder, maybe } from "@saleor/misc";
import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; import { getById } from "@saleor/orders/components/OrderReturnPage/utils";

View file

@ -1,11 +1,11 @@
import { DialogContentText } from "@material-ui/core"; import { DialogContentText } from "@material-ui/core";
import { useUser } from "@saleor/auth";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import useUser from "@saleor/hooks/useUser";
import { commonMessages, errorMessages } from "@saleor/intl"; import { commonMessages, errorMessages } from "@saleor/intl";
import { getStringOrPlaceholder, maybe } from "@saleor/misc"; import { getStringOrPlaceholder, maybe } from "@saleor/misc";
import usePermissionGroupSearch from "@saleor/searches/usePermissionGroupSearch"; import usePermissionGroupSearch from "@saleor/searches/usePermissionGroupSearch";

View file

@ -6,14 +6,12 @@ export const UserDecorator = (user: User) => storyFn => (
<UserContext.Provider <UserContext.Provider
value={{ value={{
login: undefined, login: undefined,
loginByExternalPlugin: undefined,
loginByToken: undefined,
logout: undefined,
requestLoginByExternalPlugin: undefined, requestLoginByExternalPlugin: undefined,
tokenAuthLoading: false, loginByExternalPlugin: undefined,
tokenRefresh: undefined, logout: undefined,
tokenVerifyLoading: false, user,
user authenticated: false,
authenticating: false
}} }}
> >
{storyFn()} {storyFn()}

View file

@ -36087,7 +36087,7 @@ exports[`Storyshots Views / Authentication / Log in default 1`] = `
class="FormSpacer-spacer-id" class="FormSpacer-spacer-id"
/> />
<button <button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-outlinedSizeLarge-id MuiButton-sizeLarge-id MuiButton-fullWidth-id" class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-fullWidth-id"
data-test="external-authentication" data-test="external-authentication"
tabindex="0" tabindex="0"
type="button" type="button"
@ -36244,7 +36244,7 @@ exports[`Storyshots Views / Authentication / Log in disabled 1`] = `
class="FormSpacer-spacer-id" class="FormSpacer-spacer-id"
/> />
<button <button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-outlinedSizeLarge-id MuiButton-sizeLarge-id MuiButton-disabled-id MuiButton-fullWidth-id MuiButtonBase-disabled-id" class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-disabled-id MuiButton-fullWidth-id MuiButtonBase-disabled-id"
data-test="external-authentication" data-test="external-authentication"
disabled="" disabled=""
tabindex="-1" tabindex="-1"
@ -36262,7 +36262,171 @@ exports[`Storyshots Views / Authentication / Log in disabled 1`] = `
</div> </div>
`; `;
exports[`Storyshots Views / Authentication / Log in error 1`] = ` exports[`Storyshots Views / Authentication / Log in error external login 1`] = `
<div
style="padding:24px"
>
<div
class="MuiPaper-root-id MuiCard-root-id MuiPaper-elevation0-id MuiPaper-rounded-id"
style="margin:auto;overflow:visible;position:relative;width:400px"
>
<div
class="MuiCardContent-root-id"
>
<form>
<div
class="LoginCard-panel-id"
data-test="loginErrorMessage"
>
<div
class="MuiTypography-root-id MuiTypography-caption-id"
>
Sorry, login went wrong. Please try again.
</div>
</div>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id"
data-shrink="false"
>
E-mail Address
</label>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
>
<input
aria-invalid="false"
autocomplete="username"
autofocus=""
class="MuiInputBase-input-id MuiOutlinedInput-input-id"
data-test="email"
name="email"
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
>
<legend
class="PrivateNotchedOutline-legendLabelled-id"
>
<span>
E-mail Address
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="FormSpacer-spacer-id"
/>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id"
data-shrink="false"
>
Password
</label>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
>
<input
aria-invalid="false"
autocomplete="password"
class="MuiInputBase-input-id MuiOutlinedInput-input-id"
data-test="password"
name="password"
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
>
<legend
class="PrivateNotchedOutline-legendLabelled-id"
>
<span>
Password
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="FormSpacer-spacer-id"
/>
<div
class="LoginCard-buttonContainer-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id LoginCard-loginButton-id MuiButton-containedPrimary-id"
data-test="submit"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label-id"
>
Login
</span>
</button>
</div>
<div
class="FormSpacer-spacer-id"
/>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
Forgot password?
<a
class="LoginCard-link-id"
data-test-id="reset-password-link"
>
Use this link to recover it
</a>
</div>
<div
class="FormSpacer-spacer-id"
/>
<hr
class="MuiDivider-root-id"
/>
<div
class="FormSpacer-spacer-id"
/>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
or login using
</div>
<div
class="FormSpacer-spacer-id"
/>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-fullWidth-id"
data-test="external-authentication"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Example auth plugin
</span>
</button>
</form>
</div>
</div>
</div>
`;
exports[`Storyshots Views / Authentication / Log in error login 1`] = `
<div <div
style="padding:24px" style="padding:24px"
> >
@ -36409,7 +36573,7 @@ exports[`Storyshots Views / Authentication / Log in error 1`] = `
class="FormSpacer-spacer-id" class="FormSpacer-spacer-id"
/> />
<button <button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-outlinedSizeLarge-id MuiButton-sizeLarge-id MuiButton-fullWidth-id" class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-fullWidth-id"
data-test="external-authentication" data-test="external-authentication"
tabindex="0" tabindex="0"
type="button" type="button"

View file

@ -1,4 +1,4 @@
import { User } from "@saleor/fragments/types/User"; import { LoginData } from "@saleor/sdk";
export const isSupported = !!( export const isSupported = !!(
navigator?.credentials?.preventSilentAccess && window.PasswordCredential navigator?.credentials?.preventSilentAccess && window.PasswordCredential
@ -22,14 +22,13 @@ export async function login<T>(
} }
export function saveCredentials( export function saveCredentials(
user: User, user: LoginData["user"],
password: string password: string
): Promise<CredentialType | null> { ): Promise<CredentialType | null> {
let result: Promise<CredentialType | null>; let result: Promise<CredentialType | null>;
if (isSupported) { if (isSupported) {
const cred = new PasswordCredential({ const cred = new PasswordCredential({
iconURL: user.avatar ? user.avatar.url : undefined,
id: user.email, id: user.email,
name: user.firstName ? `${user.firstName} ${user.lastName}` : undefined, name: user.firstName ? `${user.firstName} ${user.lastName}` : undefined,
password password

View file

@ -1,4 +1,4 @@
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment"; import { SetPasswordData } from "@saleor/sdk";
import { AccountErrorCode } from "@saleor/types/globalTypes"; import { AccountErrorCode } from "@saleor/types/globalTypes";
import { defineMessages, IntlShape } from "react-intl"; import { defineMessages, IntlShape } from "react-intl";
@ -31,10 +31,11 @@ const messages = defineMessages({
} }
}); });
function getAccountErrorMessage( interface ErrorFragment {
err: AccountErrorFragment, code: AccountErrorCode | SetPasswordData["errors"][number]["code"];
intl: IntlShape }
): string {
function getAccountErrorMessage(err: ErrorFragment, intl: IntlShape): string {
if (err) { if (err) {
switch (err.code) { switch (err.code) {
case AccountErrorCode.INVALID_PASSWORD: case AccountErrorCode.INVALID_PASSWORD:

View file

@ -17,7 +17,7 @@ type CommonErrorCode = "GRAPHQL_ERROR" | "INVALID" | "REQUIRED";
interface CommonError<ErrorCode> { interface CommonError<ErrorCode> {
code: ErrorCode | CommonErrorCode; code: ErrorCode | CommonErrorCode;
field: string | null; field?: string | null;
} }
export function getCommonFormFieldErrorMessage<ErrorCode>( export function getCommonFormFieldErrorMessage<ErrorCode>(

View file

@ -7,14 +7,7 @@ function getStaffErrorMessage(
err: StaffErrorFragment, err: StaffErrorFragment,
intl: IntlShape intl: IntlShape
): string { ): string {
return getAccountErrorMessage( return getAccountErrorMessage(err, intl);
err && {
...err,
__typename: "AccountError",
addressType: null
},
intl
);
} }
export default getStaffErrorMessage; export default getStaffErrorMessage;

View file

@ -1,10 +1,10 @@
import NodeHttpAdapter from "@pollyjs/adapter-node-http"; import NodeHttpAdapter from "@pollyjs/adapter-node-http";
import { Polly } from "@pollyjs/core"; import { Polly } from "@pollyjs/core";
import FSPersister from "@pollyjs/persister-fs"; import FSPersister from "@pollyjs/persister-fs";
import { createFetch } from "@saleor/sdk";
import { InMemoryCache } from "apollo-cache-inmemory"; import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client"; import ApolloClient from "apollo-client";
import { BatchHttpLink } from "apollo-link-batch-http"; import { BatchHttpLink } from "apollo-link-batch-http";
import fetch from "node-fetch";
import path from "path"; import path from "path";
import { setupPolly } from "setup-polly-jest"; import { setupPolly } from "setup-polly-jest";
@ -36,8 +36,7 @@ function setupApi() {
}); });
const cache = new InMemoryCache(); const cache = new InMemoryCache();
const link = new BatchHttpLink({ const link = new BatchHttpLink({
// @ts-ignore fetch: createFetch(),
fetch,
uri: process.env.API_URI || "http://localhost:8000/graphql/" uri: process.env.API_URI || "http://localhost:8000/graphql/"
}); });
const apolloClient = new ApolloClient({ const apolloClient = new ApolloClient({