From 4bf8a318de8a6023ccbf516db0b1b68cbfa2cdcf Mon Sep 17 00:00:00 2001 From: Miha Drofenik Date: Sat, 2 Nov 2024 13:41:12 +0100 Subject: [PATCH] Added OAuth authentication flow for Android --- .nvmrc | 1 + android/app/build.gradle | 4 + package-lock.json | 150 ++++++++++++++++++++++++------ package.json | 2 + src/components/AppDrawer.tsx | 6 +- src/components/SideNavigation.tsx | 8 +- src/navigation/index.tsx | 31 +++--- src/navigation/screens/Login.tsx | 17 ++-- src/providers/AuthProvider.tsx | 48 ++++------ src/redux/index.ts | 2 + src/redux/slices/authSlice.ts | 116 +++++++++++++++++++++++ src/utils/helpers.ts | 21 +++++ 12 files changed, 314 insertions(+), 92 deletions(-) create mode 100644 .nvmrc create mode 100644 src/redux/slices/authSlice.ts diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2edeafb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index fb6a4fa..e765d1d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -94,6 +94,10 @@ android { // Workaround for https://github.com/facebook/react-native/issues/42024 // Should be fixed in the next react-native minor version resConfigs "en" + + manifestPlaceholders = [ + appAuthRedirectScheme: 'com.wildlife.auth' + ] } signingConfigs { debug { diff --git a/package-lock.json b/package-lock.json index 5db6769..b4dc4c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "wildlifewatcher", "version": "0.0.1", "dependencies": { + "@react-native-async-storage/async-storage": "^2.0.0", "@react-native-firebase/app": "^18.7.3", "@react-native-firebase/app-distribution": "^18.7.3", "@react-navigation/native": "^6.1.12", @@ -21,6 +22,7 @@ "lodash.isempty": "^4.4.0", "react": "18.2.0", "react-native": "0.73.1", + "react-native-app-auth": "^8.0.0", "react-native-ble-manager": "^11.3.2", "react-native-bluetooth-state-manager": "^1.3.5", "react-native-bootsplash": "^5.4.1", @@ -3407,6 +3409,18 @@ "node": ">= 8" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.0.0.tgz", + "integrity": "sha512-af6H9JjfL6G/PktBfUivvexoiFKQTJGQCtSWxMdivLzNIY94mu9DdiY0JqCSg/LyPCLGKhHPUlRQhNvpu3/KVA==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native-community/cli": { "version": "12.3.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.0.tgz", @@ -6063,11 +6077,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6898,6 +6913,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6919,6 +6935,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -7817,6 +7834,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7996,9 +8014,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8221,6 +8240,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8615,6 +8635,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -8630,6 +8651,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8787,9 +8809,10 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", + "license": "MIT" }, "node_modules/is-array-buffer": { "version": "3.0.2", @@ -9019,6 +9042,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -9047,6 +9071,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -11473,6 +11506,18 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -11878,11 +11923,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -12919,6 +12965,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13034,6 +13081,25 @@ "react": "18.2.0" } }, + "node_modules/react-native-app-auth": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/react-native-app-auth/-/react-native-app-auth-8.0.0.tgz", + "integrity": "sha512-uAlbR8ggVmFvH1zQwBWzksRTwpBMVIhAKsLhidwwlUH5idyTN4b4hJO7oDIa5FbvAAYcEZU5d0ChZDoIklomFw==", + "license": "MIT", + "dependencies": { + "invariant": "2.2.4", + "react-native-base64": "0.0.2" + }, + "peerDependencies": { + "react-native": ">=0.63.0" + } + }, + "node_modules/react-native-base64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/react-native-base64/-/react-native-base64-0.0.2.tgz", + "integrity": "sha512-Fu/J1a2y0X22EJDWqJR2oEa1fpP4gTFjYxk8ElJdt1Yak3HOXmFJ7EohLVHU2DaQkgmKfw8qb7u/48gpzveRbg==", + "license": "MIT" + }, "node_modules/react-native-ble-manager": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/react-native-ble-manager/-/react-native-ble-manager-11.3.2.tgz", @@ -13361,9 +13427,10 @@ } }, "node_modules/react-native/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", "dependencies": { "async-limiter": "~1.0.0" } @@ -13764,9 +13831,10 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13790,6 +13858,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13797,12 +13866,14 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -13813,12 +13884,14 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/send/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -13830,6 +13903,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13843,19 +13917,29 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -13893,7 +13977,8 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -14624,6 +14709,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -14635,6 +14721,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -15173,9 +15260,10 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { "node": ">=8.3.0" }, diff --git a/package.json b/package.json index 3737357..c43fbb6 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test": "jest" }, "dependencies": { + "@react-native-async-storage/async-storage": "^2.0.0", "@react-native-firebase/app": "^18.7.3", "@react-native-firebase/app-distribution": "^18.7.3", "@react-navigation/native": "^6.1.12", @@ -24,6 +25,7 @@ "lodash.isempty": "^4.4.0", "react": "18.2.0", "react-native": "0.73.1", + "react-native-app-auth": "^8.0.0", "react-native-ble-manager": "^11.3.2", "react-native-bluetooth-state-manager": "^1.3.5", "react-native-bootsplash": "^5.4.1", diff --git a/src/components/AppDrawer.tsx b/src/components/AppDrawer.tsx index d8df327..319aad6 100644 --- a/src/components/AppDrawer.tsx +++ b/src/components/AppDrawer.tsx @@ -10,10 +10,10 @@ import { Drawer } from "react-native-drawer-layout" import { Avatar, Surface } from "react-native-paper" import { WWText } from "./ui/WWText" import { useExtendedTheme } from "../theme" -import { useAuth } from "../providers/AuthProvider" import { useSafeAreaInsets } from "react-native-safe-area-context" import { getReadableVersion } from "react-native-device-info" import { SideNavigation } from "./SideNavigation" +import { useAppSelector } from "../redux" type DrawerContextProps = { isOpen: boolean @@ -26,12 +26,12 @@ export const useAppDrawer = () => useContext(DrawerContext) export const AppDrawer = ({ children }: PropsWithChildren) => { const [isOpen, setIsOpen] = useState(false) const { appPadding, spacing } = useExtendedTheme() - const { isLoggedIn } = useAuth() + const { auth } = useAppSelector((state) => state.authentication) const { top } = useSafeAreaInsets() return ( setIsOpen(true)} onClose={() => setIsOpen(false)} diff --git a/src/components/SideNavigation.tsx b/src/components/SideNavigation.tsx index ee437db..51138d9 100644 --- a/src/components/SideNavigation.tsx +++ b/src/components/SideNavigation.tsx @@ -1,9 +1,10 @@ import { StyleSheet, View } from "react-native" import { Button } from "react-native-paper" import { useAppNavigation } from "../hooks/useAppNavigation" -import { useAuth } from "../providers/AuthProvider" import { useExtendedTheme } from "../theme" import { Dispatch } from "react" +import { useAppDispatch } from "../redux" +import { logout } from "../redux/slices/authSlice" type Props = { drawerControls: Dispatch> @@ -11,7 +12,7 @@ type Props = { export const SideNavigation = ({ drawerControls }: Props) => { const navigation = useAppNavigation() - const { setIsLoggedIn } = useAuth() + const dispatch = useAppDispatch() const { spacing, colors, appPadding } = useExtendedTheme() const goTo = (link: string) => { @@ -20,7 +21,8 @@ export const SideNavigation = ({ drawerControls }: Props) => { } const onLogout = () => { - setIsLoggedIn(false) + dispatch(logout()) + console.log("im here") drawerControls(false) } diff --git a/src/navigation/index.tsx b/src/navigation/index.tsx index e43cf8b..470a139 100644 --- a/src/navigation/index.tsx +++ b/src/navigation/index.tsx @@ -11,7 +11,6 @@ import { Terminal } from "./screens/TerminalScreen" import BootSplash from "react-native-bootsplash" import { NavigationBar } from "../components/NavigationBar" import { Login } from "./screens/Login" -import { useAuth } from "../providers/AuthProvider" import { AppLoading } from "./screens/AppLoading" import { AppDrawer } from "../components/AppDrawer" import { Notifications } from "./screens/Notifications" @@ -48,20 +47,17 @@ export const MainNavigation = () => { const { initialized, initialLoad: bleLoading } = useAppSelector( (state) => state.bleLibrary, ) - const { isLoggedIn } = useAuth() + const { auth, initialLoad: authLoading } = useAppSelector( + (state) => state.authentication, + ) - const somethingWrong = - status !== "PoweredOn" || !locationEnabled || !initialized - const appLoading = blLoading || locLoading || bleLoading + const appLoading = blLoading || locLoading || bleLoading || authLoading useEffect(() => { - if (!appLoading && somethingWrong) { - BootSplash.hide({ fade: true }) - } - if (isLoggedIn !== undefined) { + if (!appLoading) { BootSplash.hide({ fade: true }) } - }, [appLoading, somethingWrong, isLoggedIn]) + }, [appLoading]) /* * Stops the app from running until every important component @@ -80,6 +76,7 @@ export const MainNavigation = () => { ) } + console.log(auth?.accessToken.length) return ( @@ -107,12 +104,14 @@ export const MainNavigation = () => { name="BLEProblems" component={BleProblems} /> - ) : !isLoggedIn ? ( - + ) : !auth?.accessToken ? ( + + + ) : ( { - const { setIsLoggedIn } = useAuth() + const dispatch = useAppDispatch() + const loginPressed = () => dispatch(login()) return ( - - - - - - - diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index 74dc629..6b67c72 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -1,37 +1,29 @@ +import { PropsWithChildren, useEffect } from "react" +import { AuthorizeResult } from "react-native-app-auth" +import { useAppDispatch, useAppSelector } from "../redux" import { - PropsWithChildren, - createContext, - useContext, - useEffect, - useState, -} from "react" - -type AuthContextType = { - isLoggedIn: boolean | undefined - setIsLoggedIn: (value: boolean) => void -} - -export const AuthContext = createContext({} as AuthContextType) - -export const useAuth = () => useContext(AuthContext) + authStart, + authDone, + AUTH_STORAGE_KEY, +} from "../redux/slices/authSlice" +import { getStorageData } from "../utils/helpers" export const AuthProvider = ({ children }: PropsWithChildren) => { - const [isLoggedIn, setIsLoggedIn] = useState() + const dispatch = useAppDispatch() + const { auth } = useAppSelector((state) => state.authentication) - // Mock useEffect(() => { - const t = setTimeout(() => { - setIsLoggedIn(false) - }, 2000) + const init = async () => { + dispatch(authStart()) + dispatch( + authDone(await getStorageData(AUTH_STORAGE_KEY)), + ) + } - return () => { - clearTimeout(t) + if (!auth?.accessToken) { + init() } - }, []) + }, [dispatch, auth]) - return ( - - {children} - - ) + return children } diff --git a/src/redux/index.ts b/src/redux/index.ts index 5fed0a9..8eb026d 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -8,6 +8,7 @@ import devicesReducer from "./slices/devicesSlice" import locationStatusReducer from "./slices/locationStatusSlice" import logsReducer from "./slices/logsSlice" import scanningReducer from "./slices/scanningSlice" +import authReducer from "./slices/authSlice" import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit" const store = configureStore({ @@ -20,6 +21,7 @@ const store = configureStore({ blStatus: blStatusReducer, locationStatus: locationStatusReducer, androidPermissions: androidPermissionsReducer, + authentication: authReducer, }, }) diff --git a/src/redux/slices/authSlice.ts b/src/redux/slices/authSlice.ts new file mode 100644 index 0000000..7f3b69c --- /dev/null +++ b/src/redux/slices/authSlice.ts @@ -0,0 +1,116 @@ +import { + PayloadAction, + createAsyncThunk, + createSlice, + isAnyOf, +} from "@reduxjs/toolkit" +import { authorize, AuthorizeResult, revoke } from "react-native-app-auth" +import { storeDataToStorage } from "../../utils/helpers" +import { RootState } from ".." + +const TENANT_ID = "56bdb921-ae1e-4375-8bcd-2a978870cacb" +const CLIENT_ID = "bde3bf0d-0793-40e4-9fb1-e267d84cf766" +export const AUTH_STORAGE_KEY = "auth" + +const config = { + issuer: `https://login.microsoftonline.com/${TENANT_ID}/v2.0`, + clientId: CLIENT_ID, + redirectUrl: "com.wildlife.auth://callback/", + scopes: ["openid", "profile", "email"], + // serviceConfiguration: { + // authorizationEndpoint: `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize`, + // tokenEndpoint: `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`, + // revocationEndpoint: `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/logout`, + // }, +} + +interface ScanningState { + loading: boolean + initialLoad: boolean + error?: Error + auth?: AuthorizeResult +} + +const initialState: ScanningState = { + loading: false, + initialLoad: true, +} + +export const login = createAsyncThunk("authentication/login", async () => { + const authData = await authorize(config) + await storeDataToStorage(AUTH_STORAGE_KEY, authData) + + return authData +}) + +export const logout = createAsyncThunk( + "authentication/logout", + async (_, { getState }) => { + const { + authentication: { auth }, + } = getState() as RootState + + try { + await revoke(config, { + tokenToRevoke: `${auth?.accessToken}`, + includeBasicAuth: true, + sendClientId: true, + }) + } catch (e: any) { + console.log(e?.message) + } + + await storeDataToStorage(AUTH_STORAGE_KEY, null) + }, +) + +export const authSlice = createSlice({ + name: "authentication", + initialState: initialState, + reducers: { + authStart: (state) => { + state.loading = true + state.error = undefined + }, + authDone: (state, action: PayloadAction) => { + state.loading = false + state.initialLoad = false + state.auth = action.payload + }, + authError: (state, action: PayloadAction) => { + state.error = action.payload + state.loading = false + state.initialLoad = false + }, + }, + extraReducers: (builder) => { + builder.addCase(login.fulfilled, (state, { payload }) => { + state.loading = false + state.initialLoad = false + state.auth = payload + }) + builder.addCase(logout.fulfilled, (state) => { + state.loading = false + state.initialLoad = false + state.auth = undefined + + console.log("im here 4") + }) + builder.addMatcher(isAnyOf(login.pending, logout.pending), (state) => { + state.loading = true + state.error = undefined + }) + builder.addMatcher( + isAnyOf(login.rejected, logout.rejected), + (state, { payload }) => { + state.error = payload as Error + state.loading = false + state.initialLoad = false + }, + ) + }, +}) + +export const { authStart, authDone, authError } = authSlice.actions + +export default authSlice.reducer diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 9f80d66..362abeb 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -10,6 +10,7 @@ import BleManager from "react-native-ble-manager" import { Buffer } from "buffer" import { readlineParserEmitter } from "../hooks/useBleListeners" import { Services } from "../ble/types" +import AsyncStorage from "@react-native-async-storage/async-storage" export const clearAllDeviceIntervals = ( device: ExtendedPeripheral | undefined | null, @@ -166,3 +167,23 @@ export const extractServiceAndCharacteristic = (services?: Services) => { } } } + +export const storeDataToStorage = async (key: string, value: T) => { + try { + const jsonValue = JSON.stringify(value) + await AsyncStorage.setItem(key, jsonValue) + } catch (e: any) { + console.error(`Could not save to storage. Reason: ${e.message}`) + } +} + +export const getStorageData = async ( + key: string, +): Promise => { + try { + const jsonValue = await AsyncStorage.getItem(key) + return jsonValue != null ? JSON.parse(jsonValue) : undefined + } catch (e: any) { + console.error(`Could not read from storage. Reason: ${e.message}`) + } +}