diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5c6b814..4ebdba0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -70,7 +70,11 @@ docker run --name arangodb -d -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb If you are adding new resolver, 1. create new resolver test file [here](https://github.com/authorizerdev/authorizer/tree/main/server/__test__) +<<<<<<< HEAD + Naming convention filename: `resolver_name_test.go` function name: `resolverNameTest(t *testing.T, s TestSetup)` +======= Naming convention filename: `resolver_name_test.go` function name: `resolverNameTest(s TestSetup, t *testing.T)` +>>>>>>> main 2. Add your tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L38) **Command to run tests:** diff --git a/Makefile b/Makefile index e45a5d5..1fca360 100644 --- a/Makefile +++ b/Makefile @@ -10,4 +10,4 @@ build-dashboard: clean: rm -rf build test: - cd server && go clean --testcache && go test -v ./__test__ \ No newline at end of file + cd server && go clean --testcache && go test -v ./test diff --git a/app/src/App.tsx b/app/src/App.tsx index 137da9d..e902d35 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -42,7 +42,11 @@ export default function App() { >>>>>> main redirectURL: globalState.redirectURL, }} > diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 10b7230..f3fd9d0 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -1,1682 +1,1682 @@ { - "name": "dashboard", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "requires": { - "@babel/highlight": "^7.16.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", - "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==" - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" - }, - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz", - "integrity": "sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.5" - } - }, - "@babel/runtime": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz", - "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "to-fast-properties": "^2.0.0" - } - }, - "@chakra-ui/accordion": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-1.4.2.tgz", - "integrity": "sha512-BAGMvcm2sFE5Ft7jwC9nF03/Yv7qztuhzwKBBy4iL0p1nCPh6kV54RBXUcoj3VWe+yrmNiAVYKRTdqQBTJFwOw==", - "requires": { - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/alert": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-1.3.2.tgz", - "integrity": "sha512-+OMeVeGtydpj6nry0zH7qFDt36zEaxckRnufx1BGiCfWdUg6ahVwKXl8qX93Q8w82od7eAoBKMgGJz7IVL5NPw==", - "requires": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/anatomy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-1.2.1.tgz", - "integrity": "sha512-kNS+FiEDTSnwpQUW4dEjZ5745xhkvB0XtmqjY1wpclUSpFfptLZM9QIHPTnBt2bzM9R+idmRRP+WkTt6kyTrLw==", - "requires": { - "@chakra-ui/theme-tools": "^1.3.1" - } - }, - "@chakra-ui/avatar": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-1.3.1.tgz", - "integrity": "sha512-WI0/kcpTJViOH093V0bz8EB+e/rc+gjF+T5DkOuh1YWFxRRG5v+4Yd3PdEJtQgzWtBVhlbGWmE7WvBizyKwFCA==", - "requires": { - "@chakra-ui/image": "1.1.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/breadcrumb": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-1.3.1.tgz", - "integrity": "sha512-b1IoBmtr5FcP2fn5NRbdOdQo2c866OQ/WhcTcZ6UKae1jjik+36/qWE+X+RKzxC6FLfqo5qayV5zSgsnZym7Pg==", - "requires": { - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/button": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-1.5.1.tgz", - "integrity": "sha512-BvP29quEhP6OTgDiRsugD6adgkeOTEQpoDsZUVEmHnNVrbFfdsICEKKQTtDJ2iPf+hmpFrtnpN50vCLdAANKcw==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/spinner": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/checkbox": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-1.6.1.tgz", - "integrity": "sha512-Z5ZMeUYIRjRbi/knhYhSQshZH7OnROA7ezl9a9oVSKRF7iLMNMibQSlQLXmqUWaTKSgrS37cpKAzfgEuemyiUQ==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - } - }, - "@chakra-ui/clickable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-1.2.1.tgz", - "integrity": "sha512-B0CIbKzDMwzG1APeTpW9H2Jl8dkarI1Qstb3hDOy23O+N5TU6lpDdVnXQ7fpFJS6mu5JjFqtkwzGAVZnkkv1rw==", - "requires": { - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/close-button": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-1.2.2.tgz", - "integrity": "sha512-SqeLib0qgMjK3OsO1g5OnAHUmdCC8GMjToNEea7TeSrA44bH9EXVhFTkMMu2PnDVHbQmi4Ee1cuulNJt0UhQ3g==", - "requires": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/color-mode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-1.3.2.tgz", - "integrity": "sha512-/rWcbrzbaWCyyUnT07Qjz0xf/ltHS31CHOKtVCWr2uTgfn2gOQpdxsKRbjrLYPOYZGTMdINUHNiAsqQjLoAoTQ==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/control-box": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-1.1.1.tgz", - "integrity": "sha512-ZFbh85pzzZoiSjGnvLUzMB5BoA8Xm6TBMWvMtzLY5xiFGb9/mBeRDH2KFjr1GJzoqleWKkQwvFD6JM0kXcekpg==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/counter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-1.2.1.tgz", - "integrity": "sha512-Gm4njMzEsDyAzdQtExn40TvmupzkPBrT5DiCu0DlxYqpLqCfqV49HgJHEG5oW3WV+WaC9mzg7VV+idKYh/d+Gg==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/css-reset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz", - "integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==" - }, - "@chakra-ui/descendant": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-2.1.1.tgz", - "integrity": "sha512-JasdVaN4MjL7QFo1vMnADy6EtFAlPKT1kTJ1LwMtl9AaF9VFLBsfGxm0L+WQK+3NJMuCSDBXWJB8mV4AQ11Edg==", - "requires": { - "@chakra-ui/react-utils": "^1.2.1" - } - }, - "@chakra-ui/editable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-1.3.1.tgz", - "integrity": "sha512-MwyTtsnHNqmKmHv9SH3KIHWa06D4gBwcuTawTiSnYBUJL6My8ry/Wdca1to9So2tD6hcjz3TPTzOJOlyv0eiZg==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/focus-lock": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-1.2.1.tgz", - "integrity": "sha512-HYu39nvfaXUrBx+dIDJkFgebNCGEi9oZTfLUKzIJC+zPkmReTDSXV0dzSb/8vCAOq5fph1gFKsdbGy2U98P8GQ==", - "requires": { - "@chakra-ui/utils": "1.9.1", - "react-focus-lock": "2.5.2" - } - }, - "@chakra-ui/form-control": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-1.5.2.tgz", - "integrity": "sha512-uWv0/f+JEM0ZE5Hnj3TzCnJ09EB+A+DSs9QgyECOuxx9Ju6gnns2uaRki2BfxksQL9ZZomPCkMtXazY9Wa81ag==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/hooks": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-1.7.1.tgz", - "integrity": "sha512-hgN19X6GUKQYAHczmFY+GAT8vl9h+X+nGWrIAnmvZ6BgUXxDajnTNhZeWhj0ZkR+7A7dCE6Y/3X44GafUgChMw==", - "requires": { - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "compute-scroll-into-view": "1.0.14", - "copy-to-clipboard": "3.3.1" - } - }, - "@chakra-ui/icon": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-2.0.0.tgz", - "integrity": "sha512-/GuU+xIcOIy9uSUUUCu249ZJB/nLDbjWGkfpoSdBwqT4+ytJrKt+0Ckh3Ub14sz3BJD+Z6IiIt6ySOA9+7lbsA==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/image": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-1.1.1.tgz", - "integrity": "sha512-bz1pn08XlXcO3r1KnpdjQgN3R2soiTx10sG2d5Pw9BdGdySf7Y73wiLh+Tan1xJHp6p2KH1hz4f7uKXXDn7Qmw==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/input": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-1.3.2.tgz", - "integrity": "sha512-VMxmQgFiQ2UnBlkgLX/336G0IfYfw8YWF2ZoEFj5WL9kDSrrL1FXSBgjFGxrper74G4W20tESBCfU1S891y6cg==", - "requires": { - "@chakra-ui/form-control": "1.5.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/layout": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-1.6.0.tgz", - "integrity": "sha512-WUfQ104y1wNueU33/hPlZsMzYJGjO0dXMpVkQf5ZNhNX3IGDO+5+MO2x2xloP+j45yNPi3p8ti/HBnm3dXI+3Q==", - "requires": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/live-region": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-1.1.1.tgz", - "integrity": "sha512-BSdI5gLIffNRETEp6W18kBNg9tL0ZLLzfWGRnuO9tEbox7NrcgqIeLF8mNKwhDOZz88NKHtUOPVzjAUKW1SryQ==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/media-query": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-1.2.2.tgz", - "integrity": "sha512-xSmDVleE1drWiGH/MX3RqyVm29x/8Vf6G0UGaI2kCpbNmon+Q1zHW/yDHvptIuctLrPHYO8LOBxuUjfnIXwC2g==", - "requires": { - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/menu": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-1.8.2.tgz", - "integrity": "sha512-u2GfkwTqbWa8L/7i/kOFbU3JANiT2HStR+gsYKuiuOPiuBcUb8OlgfJfP70OtVKegNKmVEMjvzXtld3wCCo/1g==", - "requires": { - "@chakra-ui/clickable": "1.2.1", - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/modal": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-1.10.2.tgz", - "integrity": "sha512-ZlmYetPHwHW4CAM09j4/+Ui54dXR1nzU6mOwhWe4/IzLvEyoEU6fHJeKyGxVUpYTG/7wltG/wKFRJpYa77tiBg==", - "requires": { - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/focus-lock": "1.2.1", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/portal": "1.3.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.4.1" - } - }, - "@chakra-ui/number-input": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-1.3.2.tgz", - "integrity": "sha512-7x7AoqwPXU1odyDcqIwjBwf0MJUwYMM2fa+6YZ52F941GKlvkDiiJOhK6xfhhBzkLUQD6DN8zgAmmGhaZ6UQXw==", - "requires": { - "@chakra-ui/counter": "1.2.1", - "@chakra-ui/form-control": "1.5.2", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/pin-input": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-1.7.1.tgz", - "integrity": "sha512-eFFc5sofiyion+NxELWfCzD23XHIBDrJcfKKbNxt8jdXg9Ek4mFpmvnxBVrK0DIz6cVYgKY8c364OmxNUf4IyA==", - "requires": { - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/popover": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-1.11.0.tgz", - "integrity": "sha512-cCHXAfhIRir+M0ehlYIjDw3mHpiCxDTJ9WV0H1zHQV8nDYVIlZw3nEntaq8oJrv0wpIzq2WCW5ss+bBR7nLZ1A==", - "requires": { - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/popper": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-2.4.1.tgz", - "integrity": "sha512-cuwnwXx6RUXZGGynVOGG8fEIiMNBXUCy3UqWQD1eEd8200eWQobgNk4Z0YwzKuSzJwp0Auy+j5iKefi5FSkyog==", - "requires": { - "@chakra-ui/react-utils": "1.2.1", - "@popperjs/core": "^2.9.3" - } - }, - "@chakra-ui/portal": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-1.3.1.tgz", - "integrity": "sha512-6UOGZCfujgdijcPs/JTEY5IB5WtKvUbfrSQYsG5CDa+guIwvnoP5qZ+rH6BR6DSSM8Wr/1n+WrtanhfFZShHKA==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/progress": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-1.2.1.tgz", - "integrity": "sha512-213nN8nbODvD/A23vAtg+r3bRKKatWQHafgmLzeznUcxa/+ac0eVurIS8XSYLRkY4EXQ505re3ZkLhDd98a7QA==", - "requires": { - "@chakra-ui/theme-tools": "1.3.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/provider": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-1.7.3.tgz", - "integrity": "sha512-D1SrQ7do4yzAv9/OTF3yj/BkLm7kFo5DdeuOCyvXGpVJumnvbtjltRmC7rFQH4R+y9qXPvfQP4LKMNBqSxPNng==", - "requires": { - "@chakra-ui/css-reset": "1.1.1", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/portal": "1.3.1", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/system": "1.8.3", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/radio": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-1.4.3.tgz", - "integrity": "sha512-TQdyfdUD3BLklOP67n82JN8ksQv1BYjvaYsK0m6WCa0LDJr9aCC+XtUPgVq/1L2t4HqHdiGOrGBooF4vvy/+BA==", - "requires": { - "@chakra-ui/form-control": "1.5.2", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - } - }, - "@chakra-ui/react": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-1.7.3.tgz", - "integrity": "sha512-6mrfDUOa9MoQ44Xvi7xgdDq48jTTTjW9BupCGf2R3DI+z6RbUKIHzbcoDJZt2HGY6j9EarMVNRoQJzvzGUKpoQ==", - "requires": { - "@chakra-ui/accordion": "1.4.2", - "@chakra-ui/alert": "1.3.2", - "@chakra-ui/avatar": "1.3.1", - "@chakra-ui/breadcrumb": "1.3.1", - "@chakra-ui/button": "1.5.1", - "@chakra-ui/checkbox": "1.6.1", - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/control-box": "1.1.1", - "@chakra-ui/counter": "1.2.1", - "@chakra-ui/css-reset": "1.1.1", - "@chakra-ui/editable": "1.3.1", - "@chakra-ui/form-control": "1.5.2", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/image": "1.1.1", - "@chakra-ui/input": "1.3.2", - "@chakra-ui/layout": "1.6.0", - "@chakra-ui/live-region": "1.1.1", - "@chakra-ui/media-query": "1.2.2", - "@chakra-ui/menu": "1.8.2", - "@chakra-ui/modal": "1.10.2", - "@chakra-ui/number-input": "1.3.2", - "@chakra-ui/pin-input": "1.7.1", - "@chakra-ui/popover": "1.11.0", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/portal": "1.3.1", - "@chakra-ui/progress": "1.2.1", - "@chakra-ui/provider": "1.7.3", - "@chakra-ui/radio": "1.4.3", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/select": "1.2.2", - "@chakra-ui/skeleton": "1.2.3", - "@chakra-ui/slider": "1.5.2", - "@chakra-ui/spinner": "1.2.1", - "@chakra-ui/stat": "1.2.2", - "@chakra-ui/switch": "1.3.1", - "@chakra-ui/system": "1.8.3", - "@chakra-ui/table": "1.3.1", - "@chakra-ui/tabs": "1.6.1", - "@chakra-ui/tag": "1.2.2", - "@chakra-ui/textarea": "1.2.2", - "@chakra-ui/theme": "1.12.2", - "@chakra-ui/toast": "1.5.0", - "@chakra-ui/tooltip": "1.4.2", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - } - }, - "@chakra-ui/react-env": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-1.1.1.tgz", - "integrity": "sha512-Lgmb0y4kv0ffsGMelAOaYOd4tYZAv4FYWgV86ckGMjmYQWA8drv4v/lHTNltixxWMmBEpjcHALpJuS6yAZYHug==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/react-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-1.2.1.tgz", - "integrity": "sha512-bV8FRaXiOgGxOg03iTNin/B02I+tHH9PQtqUTl3U7cJaoI+5AUYhrqXvl1Ya2/R7zxSFrb/gBVDTgbZiVkJ+Dg==", - "requires": { - "@chakra-ui/utils": "^1.9.1" - } - }, - "@chakra-ui/select": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-1.2.2.tgz", - "integrity": "sha512-EchJW3St1DtSWHe//DHwKjGsQYL2zbKcNCLnJWQKGMPZsQhAD2wsm4xjowFrV8AkY7jbVM/U2v68puN7YTC3hg==", - "requires": { - "@chakra-ui/form-control": "1.5.2", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/skeleton": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-1.2.3.tgz", - "integrity": "sha512-u5ASkzPiBjfvKxKuBienUfmyYDTHziSWQ8Ny6k83LbwLv9IcmBNGsSkmsp7hesgi9cMHGBQ3hY2GTqG9ljndIg==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/media-query": "1.2.2", - "@chakra-ui/system": "1.8.3", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/slider": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-1.5.2.tgz", - "integrity": "sha512-zP07TMew61GkJe47Nu7zEg/SUEwPHpN4alW6VUM6Y8UaVpQaDx7InarbWTc/bXdTP03SfE+hQ6WD9Oy7noe4hQ==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/spinner": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-1.2.1.tgz", - "integrity": "sha512-CQsUJNJWWSot1ku5Se41Nz1dXIDhk+/7FIhTbfRHSjtYZnAab3CPMHBkTGqwbJxQ9oHYgk9Rso3cfG+/ra6aTQ==", - "requires": { - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - } - }, - "@chakra-ui/stat": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-1.2.2.tgz", - "integrity": "sha512-0StsPDC56MjzhdlBl0R8wU0uwj9L1tvhQzge/ELSDn4tQDI7VovrxpFzVH0qsj7EZDwZa0BRQaSrstzWvgmJ/Q==", - "requires": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - } - }, - "@chakra-ui/styled-system": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-1.15.0.tgz", - "integrity": "sha512-LnsKeiYkUuJ+NMTwueiX0Mj8CW9XAMJrJxpQm/X3GY5L5PO7Hv6wW725Ovqdy4mhG3IK7S8444FthpsDv/luHw==", - "requires": { - "@chakra-ui/utils": "1.9.1", - "csstype": "^3.0.9" - } - }, - "@chakra-ui/switch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-1.3.1.tgz", - "integrity": "sha512-92hXJ2/ozj7B3cJNT259mFNoad7Ck892uHTuEQ/GIdXb25doE6F1wCp0TreOnGiEgU5YSaxpdrcZjA0QODP//w==", - "requires": { - "@chakra-ui/checkbox": "1.6.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/system": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-1.8.3.tgz", - "integrity": "sha512-6MaevsT7A2ifgOGQQCQsfvzPVd0kEXqFrX1Oxd842bawaqthmbFdo2bBTdaia/+Ivq/8Xot2uAQSbU+3NuRiUA==", - "requires": { - "@chakra-ui/color-mode": "1.3.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/styled-system": "1.15.0", - "@chakra-ui/utils": "1.9.1", - "react-fast-compare": "3.2.0" - } - }, - "@chakra-ui/table": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-1.3.1.tgz", - "integrity": "sha512-+ia/7zs7AGj01lon301EEx+mK4918yGc0K6e68Kxomex8tnxkwbskFWs6hX+6Kzbj56ZBm99eLlKpo2iGYX0HA==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/tabs": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-1.6.1.tgz", - "integrity": "sha512-p7HdHcleJWNwteWYVPt2KF52YbS5pIIfs/IpgtnYZRsJbqvRVxSwgg5Wsn+vuxFXBKW0cA2rDGbyzsZ+ChtEXQ==", - "requires": { - "@chakra-ui/clickable": "1.2.1", - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/tag": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-1.2.2.tgz", - "integrity": "sha512-H25y9nEyUAUdwQDND9P4mMXKf1wf9UH4A3DyP237qVKIyYBpa4aCH8eJU4dunh2yIzASB0DWcr7lsul/HAHxmg==", - "requires": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/textarea": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-1.2.2.tgz", - "integrity": "sha512-DoLdKxHk0DyrQDnj1la9wjl2AW3/SK62nfWDYLAm0ouFsw1VKPw9nU+Yyj0dPruQTzI19nLaYF26i97rtnT27g==", - "requires": { - "@chakra-ui/form-control": "1.5.2", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/theme": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-1.12.2.tgz", - "integrity": "sha512-LVjSf16yYHD40ILrsDEd3idVQRvJSY7JY8lvTGWo2p6v+JQESWF+zXlYi9Le+TXRpZuFvJuuQ1SEvoqVwdcJ8Q==", - "requires": { - "@chakra-ui/anatomy": "1.2.1", - "@chakra-ui/theme-tools": "1.3.1", - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/theme-tools": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-1.3.1.tgz", - "integrity": "sha512-D8arJ5uFGuYZrrFGpXqgov8FhsJYWRyar5oBZY5TJR9gsVYBlJ8Ai91pwM/NflCFqzerTOgyt7bNSGQMdZ8ghA==", - "requires": { - "@chakra-ui/utils": "1.9.1", - "@ctrl/tinycolor": "^3.4.0" - } - }, - "@chakra-ui/toast": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-1.5.0.tgz", - "integrity": "sha512-rTsFx/Qos5oVPN6aZMbT/wTxwZlFNSXQqrTpJYaRcRFQGzxIDDxmGkKYfPnyJjRP9i6EqynJhXEIyhMA0xO0dw==", - "requires": { - "@chakra-ui/alert": "1.3.2", - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/theme": "1.12.2", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1", - "@reach/alert": "0.13.2" - } - }, - "@chakra-ui/tooltip": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-1.4.2.tgz", - "integrity": "sha512-+wyYXG8qenKkFy2YSFfOBf3rlWADnu6S9EUxP+3Rmm78unOWXDuTJWzqy2QlXs2BwoQoifaz1LVwzmMb7WLVgQ==", - "requires": { - "@chakra-ui/hooks": "1.7.1", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/portal": "1.3.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - } - }, - "@chakra-ui/transition": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-1.4.2.tgz", - "integrity": "sha512-S+BNmpErHlntl//uaqv0sJegzMsQms0OnJapeZaRsvZL4s1SVYrR8kMrXigkdpeh4lAUqGsLpQHPKkzaKGbBOw==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } - }, - "@chakra-ui/utils": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-1.9.1.tgz", - "integrity": "sha512-Tue8JfpzOqeHd8vSqAnX1l/Y3Gg456+BXFP/TH6mCIeqMAMbrvv25vDskds0wlXRjMYdmpqHxCEzkalFrscGHA==", - "requires": { - "@types/lodash.mergewith": "4.6.6", - "css-box-model": "1.2.1", - "framesync": "5.3.0", - "lodash.mergewith": "4.6.2" - } - }, - "@chakra-ui/visually-hidden": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-1.1.1.tgz", - "integrity": "sha512-AGK9YBQS2FW/1e5tfivS8VVXn8y2uTyJ9ACOnGiLm9FNdth9pR0fGil9axlcmhZpEYcSRlnCuma3nkqaCjJnAA==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } - }, - "@ctrl/tinycolor": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", - "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==" - }, - "@emotion/babel-plugin": { - "version": "11.7.2", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz", - "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==", - "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/runtime": "^7.13.10", - "@emotion/hash": "^0.8.0", - "@emotion/memoize": "^0.7.5", - "@emotion/serialize": "^1.0.2", - "babel-plugin-macros": "^2.6.1", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.0.13" - } - }, - "@emotion/cache": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", - "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", - "requires": { - "@emotion/memoize": "^0.7.4", - "@emotion/sheet": "^1.1.0", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", - "stylis": "4.0.13" - } - }, - "@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" - }, - "@emotion/is-prop-valid": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz", - "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==", - "requires": { - "@emotion/memoize": "^0.7.4" - } - }, - "@emotion/memoize": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", - "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" - }, - "@emotion/react": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz", - "integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==", - "requires": { - "@babel/runtime": "^7.13.10", - "@emotion/cache": "^11.7.1", - "@emotion/serialize": "^1.0.2", - "@emotion/sheet": "^1.1.0", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", - "hoist-non-react-statics": "^3.3.1" - } - }, - "@emotion/serialize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", - "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", - "requires": { - "@emotion/hash": "^0.8.0", - "@emotion/memoize": "^0.7.4", - "@emotion/unitless": "^0.7.5", - "@emotion/utils": "^1.0.0", - "csstype": "^3.0.2" - } - }, - "@emotion/sheet": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", - "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" - }, - "@emotion/styled": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.6.0.tgz", - "integrity": "sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==", - "requires": { - "@babel/runtime": "^7.13.10", - "@emotion/babel-plugin": "^11.3.0", - "@emotion/is-prop-valid": "^1.1.1", - "@emotion/serialize": "^1.0.2", - "@emotion/utils": "^1.0.0" - } - }, - "@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" - }, - "@emotion/utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz", - "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==" - }, - "@emotion/weak-memoize": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" - }, - "@graphql-typed-document-node/core": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", - "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==" - }, - "@popperjs/core": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz", - "integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==" - }, - "@reach/alert": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/alert/-/alert-0.13.2.tgz", - "integrity": "sha512-LDz83AXCrClyq/MWe+0vaZfHp1Ytqn+kgL5VxG7rirUvmluWaj/snxzfNPWn0Ma4K2YENmXXRC/iHt5X95SqIg==", - "requires": { - "@reach/utils": "0.13.2", - "@reach/visually-hidden": "0.13.2", - "prop-types": "^15.7.2", - "tslib": "^2.1.0" - } - }, - "@reach/utils": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.13.2.tgz", - "integrity": "sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==", - "requires": { - "@types/warning": "^3.0.0", - "tslib": "^2.1.0", - "warning": "^4.0.3" - } - }, - "@reach/visually-hidden": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.13.2.tgz", - "integrity": "sha512-sPZwNS0/duOuG0mYwE5DmgEAzW9VhgU3aIt1+mrfT/xiT9Cdncqke+kRBQgU708q/Ttm9tWsoHni03nn/SuPTQ==", - "requires": { - "prop-types": "^15.7.2", - "tslib": "^2.1.0" - } - }, - "@types/history": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz", - "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==" - }, - "@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" - }, - "@types/lodash.mergewith": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz", - "integrity": "sha512-RY/8IaVENjG19rxTZu9Nukqh0W2UrYgmBj5sdns4hWRZaV8PqR7wIKHFKzvOTjo4zVRV7sVI+yFhAJql12Kfqg==", - "requires": { - "@types/lodash": "*" - } - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "@types/react": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", - "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", - "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-router": { - "version": "5.1.17", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz", - "integrity": "sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==", - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.2.tgz", - "integrity": "sha512-ELEYRUie2czuJzaZ5+ziIp9Hhw+juEw8b7C11YNA4QdLCVbQ3qLi2l4aq8XnlqM7V31LZX8dxUuFUCrzHm6sqQ==", - "requires": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" - }, - "@urql/core": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz", - "integrity": "sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==", - "requires": { - "@graphql-typed-document-node/core": "^3.1.0", - "wonka": "^4.0.14" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "aria-hidden": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz", - "integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==", - "requires": { - "tslib": "^1.0.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "babel-plugin-macros": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", - "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", - "requires": { - "@babel/runtime": "^7.7.2", - "cosmiconfig": "^6.0.0", - "resolve": "^1.12.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - } - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "compute-scroll-into-view": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz", - "integrity": "sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", - "requires": { - "toggle-selection": "^1.0.6" - } - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "requires": { - "tiny-invariant": "^1.0.6" - } - }, - "csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" - }, - "debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" - }, - "detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "esbuild": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.9.tgz", - "integrity": "sha512-uuT3kFsfUvzNW6I2RKKIHuCvutY/U9KFcAP6emUm98WvBhyhEr5vGkZLeN3r3vXfoykl+7xekAH8Ky09LXBd0Q==", - "requires": { - "esbuild-android-arm64": "0.14.9", - "esbuild-darwin-64": "0.14.9", - "esbuild-darwin-arm64": "0.14.9", - "esbuild-freebsd-64": "0.14.9", - "esbuild-freebsd-arm64": "0.14.9", - "esbuild-linux-32": "0.14.9", - "esbuild-linux-64": "0.14.9", - "esbuild-linux-arm": "0.14.9", - "esbuild-linux-arm64": "0.14.9", - "esbuild-linux-mips64le": "0.14.9", - "esbuild-linux-ppc64le": "0.14.9", - "esbuild-linux-s390x": "0.14.9", - "esbuild-netbsd-64": "0.14.9", - "esbuild-openbsd-64": "0.14.9", - "esbuild-sunos-64": "0.14.9", - "esbuild-windows-32": "0.14.9", - "esbuild-windows-64": "0.14.9", - "esbuild-windows-arm64": "0.14.9" - } - }, - "esbuild-android-arm64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.9.tgz", - "integrity": "sha512-VpSCuUR07G4Re/5QzqtdxS5ZgxkCRyzu4Kf5SH1/EkXzRGeoWQt8xirkOMK58pfmg/FlS/fQNgwl3Txej4LoVg==", - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.9.tgz", - "integrity": "sha512-F/RcRHMG5ccAL8n9VIy8ZC4D0IHZrN/1IhHQbY4qPXrMlh42FucR0TW4lr3vdHF3caaId1jdDSQQJ7jXR+ZC5Q==", - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.9.tgz", - "integrity": "sha512-3ue+1T4FR5TaAu4/V1eFMG8Uwn0pgAwQZb/WwL1X78d5Cy8wOVQ67KNH1lsjU+y/9AcwMKZ9x0GGNxBB4a1Rbw==", - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.9.tgz", - "integrity": "sha512-0YEjWt6ijaf5Y3Q50YS1lZxuWZWMV/T7atQEuQnF8ioq5jamrVr8j1TZ9+rxcLgH1lBMsXj8IwW+6BleXredEg==", - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.9.tgz", - "integrity": "sha512-82w5qMgEeYvf8+vX/2KE5TOZf8rv8VK4TFiK6lDzdgdwwmBU5C8kdT3rO5Llan2K2LKndrou1eyi/fHwFcwPJQ==", - "optional": true - }, - "esbuild-linux-32": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.9.tgz", - "integrity": "sha512-eu8J8HNpco7Mkd7T7djQRzGBeuve41kbXRxFHOwwbZXMNQojXjBqLuradi5i/Vsw+CA4G/yVpmJI2S75Cit2mQ==", - "optional": true - }, - "esbuild-linux-64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.9.tgz", - "integrity": "sha512-WoEI+R6/PLZAxS7XagfQMFgRtLUi5cjqqU9VCfo3tnWmAXh/wt8QtUfCVVCcXVwZLS/RNvI19CtfjlrJU61nOg==", - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.9.tgz", - "integrity": "sha512-d3k1ZPREjaKYyhsS8x3jvc4ekjIZ8SmuihP60mrN1f6p5y07NKWw9i0OWD1p6hy+7g6cjMWq00tstMIikGB9Yg==", - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.9.tgz", - "integrity": "sha512-joUE0yQgWMDkQqBx3+6SdNCVZ10F1O4+WM94moghvhdTzkYpECIc/WvfqMF/w0V8Hecw3QJ7vugO7jsFlXXd4Q==", - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.9.tgz", - "integrity": "sha512-ZAuheiDRo2c4rxx8GUTEwPvos0zUwCYjP9K2WfCSmDL6m3RpaObCQhZghrDuoIUwvc/D6SWuABsKE9VzogsltQ==", - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.9.tgz", - "integrity": "sha512-Pm8FeG5l314k3a2mbu3SAc5E2eLFuGUsGiSlw8V6xtA4whxJ7rit7951w9jBhz+1Vqqtqprg2IYTng3j2CGhVw==", - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.9.tgz", - "integrity": "sha512-G8FNZygV82N1/LOfPD8ZX7Mn1dPpKKPrZc93ebSJ8/VgNIafOAhV5vaeK1lhcx6ZSu+jJU/UyQQMG1CIvHRIaw==", - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.9.tgz", - "integrity": "sha512-b7vPrn5XN0GRtNAQ3w+gq8AwUfWSRBkcPAdA5UUT5rkrw7wKFyMqi2/zREBc/Knu5YOsLmZPQSoM8QL6qy79cg==", - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.9.tgz", - "integrity": "sha512-w95Rt/vmVhZWfzZmeoMIHxbFiOFDmxC7GEdnCbDTXX2vlwKu+CIDIKOgWW+R1T2JqTNo5tu9dRkngKZMfbUo/A==", - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.9.tgz", - "integrity": "sha512-mzgmQZAVGo+uLkQXTY0viqVSEQKesmR5OEMMq1jM/2jucbZUcyaq8dVKRIWJJEzwNgZ6MpeOpshUtOzGxxy8ag==", - "optional": true - }, - "esbuild-windows-32": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.9.tgz", - "integrity": "sha512-sYHEJLwdDJpjjSUyIGqPC1GRXl0Z/YT1K85Tcrv4iqZEXFR0rT7sTV+E0XC911FbTJHfuAdUJixkwAQeLMdrUg==", - "optional": true - }, - "esbuild-windows-64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.9.tgz", - "integrity": "sha512-xJTpyFzpH51LGlVR2C3P+Gpnjujsx5kEtJj5V/x8TyD94VW+EpszyND/pay15CIF64pWywyQt2jmGUDl6kzkEw==", - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.9.tgz", - "integrity": "sha512-NKPPsYVlHqdF0yMuMJrjuAzqS/BHrMXZ8TN1Du+Pgi8KkmxzNXRPDHQV0NPPJ+Z7Lp09joEHSz1zrvQRs1j6jw==", - "optional": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "focus-lock": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.9.2.tgz", - "integrity": "sha512-YtHxjX7a0IC0ZACL5wsX8QdncXofWpGPNoVMuI/nZUrPGp6LmNI6+D5j0pPj+v8Kw5EpweA+T5yImK0rnWf7oQ==", - "requires": { - "tslib": "^2.0.3" - } - }, - "framer-motion": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-5.5.5.tgz", - "integrity": "sha512-+LPAF5ddo02qKh+MK4h1ChwqUFvrLkK1NDWwrHy+MuCVmQDGgiFNHvwqOSklTDGkEtbio3dCOEDy23+ZyNAa9g==", - "requires": { - "@emotion/is-prop-valid": "^0.8.2", - "framesync": "6.0.1", - "hey-listen": "^1.0.8", - "popmotion": "11.0.3", - "react-merge-refs": "^1.1.0", - "react-use-measure": "^2.1.1", - "style-value-types": "5.0.0", - "tslib": "^2.1.0" - }, - "dependencies": { - "@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", - "optional": true, - "requires": { - "@emotion/memoize": "0.7.4" - } - }, - "@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "optional": true - }, - "framesync": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", - "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", - "requires": { - "tslib": "^2.1.0" - } - } - } - }, - "framesync": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz", - "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==", - "requires": { - "tslib": "^2.1.0" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" - }, - "graphql": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.2.0.tgz", - "integrity": "sha512-MuQd7XXrdOcmfwuLwC2jNvx0n3rxIuNYOxUtiee5XOmfrWo613ar2U8pE7aHAKh8VwfpifubpD9IP+EdEAEOsA==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "hey-listen": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", - "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" - }, - "history": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz", - "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==", - "requires": { - "@babel/runtime": "^7.7.6" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "requires": { - "has": "^1.0.3" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "popmotion": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", - "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", - "requires": { - "framesync": "6.0.1", - "hey-listen": "^1.0.8", - "style-value-types": "5.0.0", - "tslib": "^2.1.0" - }, - "dependencies": { - "framesync": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", - "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", - "requires": { - "tslib": "^2.1.0" - } - } - } - }, - "prop-types": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.0.tgz", - "integrity": "sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "react-clientside-effect": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz", - "integrity": "sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA==", - "requires": { - "@babel/runtime": "^7.12.13" - } - }, - "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - } - }, - "react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - }, - "react-focus-lock": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.2.tgz", - "integrity": "sha512-WzpdOnEqjf+/A3EH9opMZWauag7gV0BxFl+EY4ElA4qFqYsUsBLnmo2sELbN5OC30S16GAWMy16B9DLPpdJKAQ==", - "requires": { - "@babel/runtime": "^7.0.0", - "focus-lock": "^0.9.1", - "prop-types": "^15.6.2", - "react-clientside-effect": "^1.2.5", - "use-callback-ref": "^1.2.5", - "use-sidecar": "^1.0.5" - } - }, - "react-icons": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", - "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==" - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-merge-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", - "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==" - }, - "react-remove-scroll": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.1.tgz", - "integrity": "sha512-K7XZySEzOHMTq7dDwcHsZA6Y7/1uX5RsWhRXVYv8rdh+y9Qz2nMwl9RX/Mwnj/j7JstCGmxyfyC0zbVGXYh3mA==", - "requires": { - "react-remove-scroll-bar": "^2.1.0", - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0", - "use-callback-ref": "^1.2.3", - "use-sidecar": "^1.0.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "react-remove-scroll-bar": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz", - "integrity": "sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==", - "requires": { - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "react-router": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", - "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", - "requires": { - "history": "^5.2.0" - } - }, - "react-router-dom": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", - "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", - "requires": { - "history": "^5.2.0", - "react-router": "6.2.1" - } - }, - "react-style-singleton": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz", - "integrity": "sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==", - "requires": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^1.0.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "react-use-measure": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", - "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", - "requires": { - "debounce": "^1.2.1" - } - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "style-value-types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", - "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", - "requires": { - "hey-listen": "^1.0.8", - "tslib": "^2.1.0" - } - }, - "stylis": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", - "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "tiny-invariant": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", - "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==" - }, - "urql": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz", - "integrity": "sha512-ovK9mx7YxD/CKUwVZGbEDBzZjbCcNsr1990nIhDCKe3Ij/0gNcIa+0EIyXKceOPuYDYKavIoaNQV2kOZjQxFcw==", - "requires": { - "@urql/core": "^2.3.6", - "wonka": "^4.0.14" - } - }, - "use-callback-ref": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz", - "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==" - }, - "use-sidecar": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz", - "integrity": "sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==", - "requires": { - "detect-node-es": "^1.1.0", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "wonka": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", - "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==" - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - } - } + "name": "dashboard", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "requires": { + "@babel/highlight": "^7.16.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", + "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==" + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" + }, + "@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz", + "integrity": "sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.16.5" + } + }, + "@babel/runtime": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz", + "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" + } + }, + "@chakra-ui/accordion": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-1.4.2.tgz", + "integrity": "sha512-BAGMvcm2sFE5Ft7jwC9nF03/Yv7qztuhzwKBBy4iL0p1nCPh6kV54RBXUcoj3VWe+yrmNiAVYKRTdqQBTJFwOw==", + "requires": { + "@chakra-ui/descendant": "2.1.1", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/transition": "1.4.2", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/alert": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-1.3.2.tgz", + "integrity": "sha512-+OMeVeGtydpj6nry0zH7qFDt36zEaxckRnufx1BGiCfWdUg6ahVwKXl8qX93Q8w82od7eAoBKMgGJz7IVL5NPw==", + "requires": { + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/anatomy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-1.2.1.tgz", + "integrity": "sha512-kNS+FiEDTSnwpQUW4dEjZ5745xhkvB0XtmqjY1wpclUSpFfptLZM9QIHPTnBt2bzM9R+idmRRP+WkTt6kyTrLw==", + "requires": { + "@chakra-ui/theme-tools": "^1.3.1" + } + }, + "@chakra-ui/avatar": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-1.3.1.tgz", + "integrity": "sha512-WI0/kcpTJViOH093V0bz8EB+e/rc+gjF+T5DkOuh1YWFxRRG5v+4Yd3PdEJtQgzWtBVhlbGWmE7WvBizyKwFCA==", + "requires": { + "@chakra-ui/image": "1.1.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/breadcrumb": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-1.3.1.tgz", + "integrity": "sha512-b1IoBmtr5FcP2fn5NRbdOdQo2c866OQ/WhcTcZ6UKae1jjik+36/qWE+X+RKzxC6FLfqo5qayV5zSgsnZym7Pg==", + "requires": { + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/button": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-1.5.1.tgz", + "integrity": "sha512-BvP29quEhP6OTgDiRsugD6adgkeOTEQpoDsZUVEmHnNVrbFfdsICEKKQTtDJ2iPf+hmpFrtnpN50vCLdAANKcw==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/spinner": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/checkbox": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-1.6.1.tgz", + "integrity": "sha512-Z5ZMeUYIRjRbi/knhYhSQshZH7OnROA7ezl9a9oVSKRF7iLMNMibQSlQLXmqUWaTKSgrS37cpKAzfgEuemyiUQ==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1", + "@chakra-ui/visually-hidden": "1.1.1" + } + }, + "@chakra-ui/clickable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-1.2.1.tgz", + "integrity": "sha512-B0CIbKzDMwzG1APeTpW9H2Jl8dkarI1Qstb3hDOy23O+N5TU6lpDdVnXQ7fpFJS6mu5JjFqtkwzGAVZnkkv1rw==", + "requires": { + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/close-button": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-1.2.2.tgz", + "integrity": "sha512-SqeLib0qgMjK3OsO1g5OnAHUmdCC8GMjToNEea7TeSrA44bH9EXVhFTkMMu2PnDVHbQmi4Ee1cuulNJt0UhQ3g==", + "requires": { + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/color-mode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-1.3.2.tgz", + "integrity": "sha512-/rWcbrzbaWCyyUnT07Qjz0xf/ltHS31CHOKtVCWr2uTgfn2gOQpdxsKRbjrLYPOYZGTMdINUHNiAsqQjLoAoTQ==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-env": "1.1.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/control-box": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-1.1.1.tgz", + "integrity": "sha512-ZFbh85pzzZoiSjGnvLUzMB5BoA8Xm6TBMWvMtzLY5xiFGb9/mBeRDH2KFjr1GJzoqleWKkQwvFD6JM0kXcekpg==", + "requires": { + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/counter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-1.2.1.tgz", + "integrity": "sha512-Gm4njMzEsDyAzdQtExn40TvmupzkPBrT5DiCu0DlxYqpLqCfqV49HgJHEG5oW3WV+WaC9mzg7VV+idKYh/d+Gg==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/css-reset": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz", + "integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==" + }, + "@chakra-ui/descendant": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-2.1.1.tgz", + "integrity": "sha512-JasdVaN4MjL7QFo1vMnADy6EtFAlPKT1kTJ1LwMtl9AaF9VFLBsfGxm0L+WQK+3NJMuCSDBXWJB8mV4AQ11Edg==", + "requires": { + "@chakra-ui/react-utils": "^1.2.1" + } + }, + "@chakra-ui/editable": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-1.3.1.tgz", + "integrity": "sha512-MwyTtsnHNqmKmHv9SH3KIHWa06D4gBwcuTawTiSnYBUJL6My8ry/Wdca1to9So2tD6hcjz3TPTzOJOlyv0eiZg==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/focus-lock": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-1.2.1.tgz", + "integrity": "sha512-HYu39nvfaXUrBx+dIDJkFgebNCGEi9oZTfLUKzIJC+zPkmReTDSXV0dzSb/8vCAOq5fph1gFKsdbGy2U98P8GQ==", + "requires": { + "@chakra-ui/utils": "1.9.1", + "react-focus-lock": "2.5.2" + } + }, + "@chakra-ui/form-control": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-1.5.2.tgz", + "integrity": "sha512-uWv0/f+JEM0ZE5Hnj3TzCnJ09EB+A+DSs9QgyECOuxx9Ju6gnns2uaRki2BfxksQL9ZZomPCkMtXazY9Wa81ag==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/hooks": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-1.7.1.tgz", + "integrity": "sha512-hgN19X6GUKQYAHczmFY+GAT8vl9h+X+nGWrIAnmvZ6BgUXxDajnTNhZeWhj0ZkR+7A7dCE6Y/3X44GafUgChMw==", + "requires": { + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1", + "compute-scroll-into-view": "1.0.14", + "copy-to-clipboard": "3.3.1" + } + }, + "@chakra-ui/icon": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-2.0.0.tgz", + "integrity": "sha512-/GuU+xIcOIy9uSUUUCu249ZJB/nLDbjWGkfpoSdBwqT4+ytJrKt+0Ckh3Ub14sz3BJD+Z6IiIt6ySOA9+7lbsA==", + "requires": { + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/image": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-1.1.1.tgz", + "integrity": "sha512-bz1pn08XlXcO3r1KnpdjQgN3R2soiTx10sG2d5Pw9BdGdySf7Y73wiLh+Tan1xJHp6p2KH1hz4f7uKXXDn7Qmw==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/input": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-1.3.2.tgz", + "integrity": "sha512-VMxmQgFiQ2UnBlkgLX/336G0IfYfw8YWF2ZoEFj5WL9kDSrrL1FXSBgjFGxrper74G4W20tESBCfU1S891y6cg==", + "requires": { + "@chakra-ui/form-control": "1.5.2", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/layout": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-1.6.0.tgz", + "integrity": "sha512-WUfQ104y1wNueU33/hPlZsMzYJGjO0dXMpVkQf5ZNhNX3IGDO+5+MO2x2xloP+j45yNPi3p8ti/HBnm3dXI+3Q==", + "requires": { + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/live-region": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-1.1.1.tgz", + "integrity": "sha512-BSdI5gLIffNRETEp6W18kBNg9tL0ZLLzfWGRnuO9tEbox7NrcgqIeLF8mNKwhDOZz88NKHtUOPVzjAUKW1SryQ==", + "requires": { + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/media-query": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-1.2.2.tgz", + "integrity": "sha512-xSmDVleE1drWiGH/MX3RqyVm29x/8Vf6G0UGaI2kCpbNmon+Q1zHW/yDHvptIuctLrPHYO8LOBxuUjfnIXwC2g==", + "requires": { + "@chakra-ui/react-env": "1.1.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/menu": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-1.8.2.tgz", + "integrity": "sha512-u2GfkwTqbWa8L/7i/kOFbU3JANiT2HStR+gsYKuiuOPiuBcUb8OlgfJfP70OtVKegNKmVEMjvzXtld3wCCo/1g==", + "requires": { + "@chakra-ui/clickable": "1.2.1", + "@chakra-ui/descendant": "2.1.1", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/popper": "2.4.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/transition": "1.4.2", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/modal": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-1.10.2.tgz", + "integrity": "sha512-ZlmYetPHwHW4CAM09j4/+Ui54dXR1nzU6mOwhWe4/IzLvEyoEU6fHJeKyGxVUpYTG/7wltG/wKFRJpYa77tiBg==", + "requires": { + "@chakra-ui/close-button": "1.2.2", + "@chakra-ui/focus-lock": "1.2.1", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/portal": "1.3.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/transition": "1.4.2", + "@chakra-ui/utils": "1.9.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.4.1" + } + }, + "@chakra-ui/number-input": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-1.3.2.tgz", + "integrity": "sha512-7x7AoqwPXU1odyDcqIwjBwf0MJUwYMM2fa+6YZ52F941GKlvkDiiJOhK6xfhhBzkLUQD6DN8zgAmmGhaZ6UQXw==", + "requires": { + "@chakra-ui/counter": "1.2.1", + "@chakra-ui/form-control": "1.5.2", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/pin-input": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-1.7.1.tgz", + "integrity": "sha512-eFFc5sofiyion+NxELWfCzD23XHIBDrJcfKKbNxt8jdXg9Ek4mFpmvnxBVrK0DIz6cVYgKY8c364OmxNUf4IyA==", + "requires": { + "@chakra-ui/descendant": "2.1.1", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/popover": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-1.11.0.tgz", + "integrity": "sha512-cCHXAfhIRir+M0ehlYIjDw3mHpiCxDTJ9WV0H1zHQV8nDYVIlZw3nEntaq8oJrv0wpIzq2WCW5ss+bBR7nLZ1A==", + "requires": { + "@chakra-ui/close-button": "1.2.2", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/popper": "2.4.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/popper": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-2.4.1.tgz", + "integrity": "sha512-cuwnwXx6RUXZGGynVOGG8fEIiMNBXUCy3UqWQD1eEd8200eWQobgNk4Z0YwzKuSzJwp0Auy+j5iKefi5FSkyog==", + "requires": { + "@chakra-ui/react-utils": "1.2.1", + "@popperjs/core": "^2.9.3" + } + }, + "@chakra-ui/portal": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-1.3.1.tgz", + "integrity": "sha512-6UOGZCfujgdijcPs/JTEY5IB5WtKvUbfrSQYsG5CDa+guIwvnoP5qZ+rH6BR6DSSM8Wr/1n+WrtanhfFZShHKA==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/progress": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-1.2.1.tgz", + "integrity": "sha512-213nN8nbODvD/A23vAtg+r3bRKKatWQHafgmLzeznUcxa/+ac0eVurIS8XSYLRkY4EXQ505re3ZkLhDd98a7QA==", + "requires": { + "@chakra-ui/theme-tools": "1.3.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/provider": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-1.7.3.tgz", + "integrity": "sha512-D1SrQ7do4yzAv9/OTF3yj/BkLm7kFo5DdeuOCyvXGpVJumnvbtjltRmC7rFQH4R+y9qXPvfQP4LKMNBqSxPNng==", + "requires": { + "@chakra-ui/css-reset": "1.1.1", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/portal": "1.3.1", + "@chakra-ui/react-env": "1.1.1", + "@chakra-ui/system": "1.8.3", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/radio": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-1.4.3.tgz", + "integrity": "sha512-TQdyfdUD3BLklOP67n82JN8ksQv1BYjvaYsK0m6WCa0LDJr9aCC+XtUPgVq/1L2t4HqHdiGOrGBooF4vvy/+BA==", + "requires": { + "@chakra-ui/form-control": "1.5.2", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1", + "@chakra-ui/visually-hidden": "1.1.1" + } + }, + "@chakra-ui/react": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-1.7.3.tgz", + "integrity": "sha512-6mrfDUOa9MoQ44Xvi7xgdDq48jTTTjW9BupCGf2R3DI+z6RbUKIHzbcoDJZt2HGY6j9EarMVNRoQJzvzGUKpoQ==", + "requires": { + "@chakra-ui/accordion": "1.4.2", + "@chakra-ui/alert": "1.3.2", + "@chakra-ui/avatar": "1.3.1", + "@chakra-ui/breadcrumb": "1.3.1", + "@chakra-ui/button": "1.5.1", + "@chakra-ui/checkbox": "1.6.1", + "@chakra-ui/close-button": "1.2.2", + "@chakra-ui/control-box": "1.1.1", + "@chakra-ui/counter": "1.2.1", + "@chakra-ui/css-reset": "1.1.1", + "@chakra-ui/editable": "1.3.1", + "@chakra-ui/form-control": "1.5.2", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/image": "1.1.1", + "@chakra-ui/input": "1.3.2", + "@chakra-ui/layout": "1.6.0", + "@chakra-ui/live-region": "1.1.1", + "@chakra-ui/media-query": "1.2.2", + "@chakra-ui/menu": "1.8.2", + "@chakra-ui/modal": "1.10.2", + "@chakra-ui/number-input": "1.3.2", + "@chakra-ui/pin-input": "1.7.1", + "@chakra-ui/popover": "1.11.0", + "@chakra-ui/popper": "2.4.1", + "@chakra-ui/portal": "1.3.1", + "@chakra-ui/progress": "1.2.1", + "@chakra-ui/provider": "1.7.3", + "@chakra-ui/radio": "1.4.3", + "@chakra-ui/react-env": "1.1.1", + "@chakra-ui/select": "1.2.2", + "@chakra-ui/skeleton": "1.2.3", + "@chakra-ui/slider": "1.5.2", + "@chakra-ui/spinner": "1.2.1", + "@chakra-ui/stat": "1.2.2", + "@chakra-ui/switch": "1.3.1", + "@chakra-ui/system": "1.8.3", + "@chakra-ui/table": "1.3.1", + "@chakra-ui/tabs": "1.6.1", + "@chakra-ui/tag": "1.2.2", + "@chakra-ui/textarea": "1.2.2", + "@chakra-ui/theme": "1.12.2", + "@chakra-ui/toast": "1.5.0", + "@chakra-ui/tooltip": "1.4.2", + "@chakra-ui/transition": "1.4.2", + "@chakra-ui/utils": "1.9.1", + "@chakra-ui/visually-hidden": "1.1.1" + } + }, + "@chakra-ui/react-env": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-1.1.1.tgz", + "integrity": "sha512-Lgmb0y4kv0ffsGMelAOaYOd4tYZAv4FYWgV86ckGMjmYQWA8drv4v/lHTNltixxWMmBEpjcHALpJuS6yAZYHug==", + "requires": { + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/react-utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-1.2.1.tgz", + "integrity": "sha512-bV8FRaXiOgGxOg03iTNin/B02I+tHH9PQtqUTl3U7cJaoI+5AUYhrqXvl1Ya2/R7zxSFrb/gBVDTgbZiVkJ+Dg==", + "requires": { + "@chakra-ui/utils": "^1.9.1" + } + }, + "@chakra-ui/select": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-1.2.2.tgz", + "integrity": "sha512-EchJW3St1DtSWHe//DHwKjGsQYL2zbKcNCLnJWQKGMPZsQhAD2wsm4xjowFrV8AkY7jbVM/U2v68puN7YTC3hg==", + "requires": { + "@chakra-ui/form-control": "1.5.2", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/skeleton": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-1.2.3.tgz", + "integrity": "sha512-u5ASkzPiBjfvKxKuBienUfmyYDTHziSWQ8Ny6k83LbwLv9IcmBNGsSkmsp7hesgi9cMHGBQ3hY2GTqG9ljndIg==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/media-query": "1.2.2", + "@chakra-ui/system": "1.8.3", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/slider": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-1.5.2.tgz", + "integrity": "sha512-zP07TMew61GkJe47Nu7zEg/SUEwPHpN4alW6VUM6Y8UaVpQaDx7InarbWTc/bXdTP03SfE+hQ6WD9Oy7noe4hQ==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/spinner": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-1.2.1.tgz", + "integrity": "sha512-CQsUJNJWWSot1ku5Se41Nz1dXIDhk+/7FIhTbfRHSjtYZnAab3CPMHBkTGqwbJxQ9oHYgk9Rso3cfG+/ra6aTQ==", + "requires": { + "@chakra-ui/utils": "1.9.1", + "@chakra-ui/visually-hidden": "1.1.1" + } + }, + "@chakra-ui/stat": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-1.2.2.tgz", + "integrity": "sha512-0StsPDC56MjzhdlBl0R8wU0uwj9L1tvhQzge/ELSDn4tQDI7VovrxpFzVH0qsj7EZDwZa0BRQaSrstzWvgmJ/Q==", + "requires": { + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/utils": "1.9.1", + "@chakra-ui/visually-hidden": "1.1.1" + } + }, + "@chakra-ui/styled-system": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-1.15.0.tgz", + "integrity": "sha512-LnsKeiYkUuJ+NMTwueiX0Mj8CW9XAMJrJxpQm/X3GY5L5PO7Hv6wW725Ovqdy4mhG3IK7S8444FthpsDv/luHw==", + "requires": { + "@chakra-ui/utils": "1.9.1", + "csstype": "^3.0.9" + } + }, + "@chakra-ui/switch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-1.3.1.tgz", + "integrity": "sha512-92hXJ2/ozj7B3cJNT259mFNoad7Ck892uHTuEQ/GIdXb25doE6F1wCp0TreOnGiEgU5YSaxpdrcZjA0QODP//w==", + "requires": { + "@chakra-ui/checkbox": "1.6.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/system": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-1.8.3.tgz", + "integrity": "sha512-6MaevsT7A2ifgOGQQCQsfvzPVd0kEXqFrX1Oxd842bawaqthmbFdo2bBTdaia/+Ivq/8Xot2uAQSbU+3NuRiUA==", + "requires": { + "@chakra-ui/color-mode": "1.3.2", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/styled-system": "1.15.0", + "@chakra-ui/utils": "1.9.1", + "react-fast-compare": "3.2.0" + } + }, + "@chakra-ui/table": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-1.3.1.tgz", + "integrity": "sha512-+ia/7zs7AGj01lon301EEx+mK4918yGc0K6e68Kxomex8tnxkwbskFWs6hX+6Kzbj56ZBm99eLlKpo2iGYX0HA==", + "requires": { + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/tabs": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-1.6.1.tgz", + "integrity": "sha512-p7HdHcleJWNwteWYVPt2KF52YbS5pIIfs/IpgtnYZRsJbqvRVxSwgg5Wsn+vuxFXBKW0cA2rDGbyzsZ+ChtEXQ==", + "requires": { + "@chakra-ui/clickable": "1.2.1", + "@chakra-ui/descendant": "2.1.1", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/tag": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-1.2.2.tgz", + "integrity": "sha512-H25y9nEyUAUdwQDND9P4mMXKf1wf9UH4A3DyP237qVKIyYBpa4aCH8eJU4dunh2yIzASB0DWcr7lsul/HAHxmg==", + "requires": { + "@chakra-ui/icon": "2.0.0", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/textarea": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-1.2.2.tgz", + "integrity": "sha512-DoLdKxHk0DyrQDnj1la9wjl2AW3/SK62nfWDYLAm0ouFsw1VKPw9nU+Yyj0dPruQTzI19nLaYF26i97rtnT27g==", + "requires": { + "@chakra-ui/form-control": "1.5.2", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/theme": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-1.12.2.tgz", + "integrity": "sha512-LVjSf16yYHD40ILrsDEd3idVQRvJSY7JY8lvTGWo2p6v+JQESWF+zXlYi9Le+TXRpZuFvJuuQ1SEvoqVwdcJ8Q==", + "requires": { + "@chakra-ui/anatomy": "1.2.1", + "@chakra-ui/theme-tools": "1.3.1", + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/theme-tools": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-1.3.1.tgz", + "integrity": "sha512-D8arJ5uFGuYZrrFGpXqgov8FhsJYWRyar5oBZY5TJR9gsVYBlJ8Ai91pwM/NflCFqzerTOgyt7bNSGQMdZ8ghA==", + "requires": { + "@chakra-ui/utils": "1.9.1", + "@ctrl/tinycolor": "^3.4.0" + } + }, + "@chakra-ui/toast": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-1.5.0.tgz", + "integrity": "sha512-rTsFx/Qos5oVPN6aZMbT/wTxwZlFNSXQqrTpJYaRcRFQGzxIDDxmGkKYfPnyJjRP9i6EqynJhXEIyhMA0xO0dw==", + "requires": { + "@chakra-ui/alert": "1.3.2", + "@chakra-ui/close-button": "1.2.2", + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/theme": "1.12.2", + "@chakra-ui/transition": "1.4.2", + "@chakra-ui/utils": "1.9.1", + "@reach/alert": "0.13.2" + } + }, + "@chakra-ui/tooltip": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-1.4.2.tgz", + "integrity": "sha512-+wyYXG8qenKkFy2YSFfOBf3rlWADnu6S9EUxP+3Rmm78unOWXDuTJWzqy2QlXs2BwoQoifaz1LVwzmMb7WLVgQ==", + "requires": { + "@chakra-ui/hooks": "1.7.1", + "@chakra-ui/popper": "2.4.1", + "@chakra-ui/portal": "1.3.1", + "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/utils": "1.9.1", + "@chakra-ui/visually-hidden": "1.1.1" + } + }, + "@chakra-ui/transition": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-1.4.2.tgz", + "integrity": "sha512-S+BNmpErHlntl//uaqv0sJegzMsQms0OnJapeZaRsvZL4s1SVYrR8kMrXigkdpeh4lAUqGsLpQHPKkzaKGbBOw==", + "requires": { + "@chakra-ui/utils": "1.9.1" + } + }, + "@chakra-ui/utils": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-1.9.1.tgz", + "integrity": "sha512-Tue8JfpzOqeHd8vSqAnX1l/Y3Gg456+BXFP/TH6mCIeqMAMbrvv25vDskds0wlXRjMYdmpqHxCEzkalFrscGHA==", + "requires": { + "@types/lodash.mergewith": "4.6.6", + "css-box-model": "1.2.1", + "framesync": "5.3.0", + "lodash.mergewith": "4.6.2" + } + }, + "@chakra-ui/visually-hidden": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-1.1.1.tgz", + "integrity": "sha512-AGK9YBQS2FW/1e5tfivS8VVXn8y2uTyJ9ACOnGiLm9FNdth9pR0fGil9axlcmhZpEYcSRlnCuma3nkqaCjJnAA==", + "requires": { + "@chakra-ui/utils": "1.9.1" + } + }, + "@ctrl/tinycolor": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", + "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==" + }, + "@emotion/babel-plugin": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz", + "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==", + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + } + }, + "@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "@emotion/is-prop-valid": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz", + "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==", + "requires": { + "@emotion/memoize": "^0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "@emotion/react": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz", + "integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.2", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "@emotion/styled": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.6.0.tgz", + "integrity": "sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.3.0", + "@emotion/is-prop-valid": "^1.1.1", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.0.0" + } + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@emotion/utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz", + "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==" + }, + "@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, + "@graphql-typed-document-node/core": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==" + }, + "@popperjs/core": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz", + "integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==" + }, + "@reach/alert": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@reach/alert/-/alert-0.13.2.tgz", + "integrity": "sha512-LDz83AXCrClyq/MWe+0vaZfHp1Ytqn+kgL5VxG7rirUvmluWaj/snxzfNPWn0Ma4K2YENmXXRC/iHt5X95SqIg==", + "requires": { + "@reach/utils": "0.13.2", + "@reach/visually-hidden": "0.13.2", + "prop-types": "^15.7.2", + "tslib": "^2.1.0" + } + }, + "@reach/utils": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.13.2.tgz", + "integrity": "sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==", + "requires": { + "@types/warning": "^3.0.0", + "tslib": "^2.1.0", + "warning": "^4.0.3" + } + }, + "@reach/visually-hidden": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.13.2.tgz", + "integrity": "sha512-sPZwNS0/duOuG0mYwE5DmgEAzW9VhgU3aIt1+mrfT/xiT9Cdncqke+kRBQgU708q/Ttm9tWsoHni03nn/SuPTQ==", + "requires": { + "prop-types": "^15.7.2", + "tslib": "^2.1.0" + } + }, + "@types/history": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz", + "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==" + }, + "@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + }, + "@types/lodash.mergewith": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz", + "integrity": "sha512-RY/8IaVENjG19rxTZu9Nukqh0W2UrYgmBj5sdns4hWRZaV8PqR7wIKHFKzvOTjo4zVRV7sVI+yFhAJql12Kfqg==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" + }, + "@types/react": { + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-router": { + "version": "5.1.17", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz", + "integrity": "sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==", + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.2.tgz", + "integrity": "sha512-ELEYRUie2czuJzaZ5+ziIp9Hhw+juEw8b7C11YNA4QdLCVbQ3qLi2l4aq8XnlqM7V31LZX8dxUuFUCrzHm6sqQ==", + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + }, + "@urql/core": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz", + "integrity": "sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==", + "requires": { + "@graphql-typed-document-node/core": "^3.1.0", + "wonka": "^4.0.14" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "aria-hidden": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz", + "integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==", + "requires": { + "tslib": "^1.0.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "compute-scroll-into-view": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz", + "integrity": "sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==" + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "requires": { + "tiny-invariant": "^1.0.6" + } + }, + "csstype": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, + "detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "esbuild": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.9.tgz", + "integrity": "sha512-uuT3kFsfUvzNW6I2RKKIHuCvutY/U9KFcAP6emUm98WvBhyhEr5vGkZLeN3r3vXfoykl+7xekAH8Ky09LXBd0Q==", + "requires": { + "esbuild-android-arm64": "0.14.9", + "esbuild-darwin-64": "0.14.9", + "esbuild-darwin-arm64": "0.14.9", + "esbuild-freebsd-64": "0.14.9", + "esbuild-freebsd-arm64": "0.14.9", + "esbuild-linux-32": "0.14.9", + "esbuild-linux-64": "0.14.9", + "esbuild-linux-arm": "0.14.9", + "esbuild-linux-arm64": "0.14.9", + "esbuild-linux-mips64le": "0.14.9", + "esbuild-linux-ppc64le": "0.14.9", + "esbuild-linux-s390x": "0.14.9", + "esbuild-netbsd-64": "0.14.9", + "esbuild-openbsd-64": "0.14.9", + "esbuild-sunos-64": "0.14.9", + "esbuild-windows-32": "0.14.9", + "esbuild-windows-64": "0.14.9", + "esbuild-windows-arm64": "0.14.9" + } + }, + "esbuild-android-arm64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.9.tgz", + "integrity": "sha512-VpSCuUR07G4Re/5QzqtdxS5ZgxkCRyzu4Kf5SH1/EkXzRGeoWQt8xirkOMK58pfmg/FlS/fQNgwl3Txej4LoVg==", + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.9.tgz", + "integrity": "sha512-F/RcRHMG5ccAL8n9VIy8ZC4D0IHZrN/1IhHQbY4qPXrMlh42FucR0TW4lr3vdHF3caaId1jdDSQQJ7jXR+ZC5Q==", + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.9.tgz", + "integrity": "sha512-3ue+1T4FR5TaAu4/V1eFMG8Uwn0pgAwQZb/WwL1X78d5Cy8wOVQ67KNH1lsjU+y/9AcwMKZ9x0GGNxBB4a1Rbw==", + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.9.tgz", + "integrity": "sha512-0YEjWt6ijaf5Y3Q50YS1lZxuWZWMV/T7atQEuQnF8ioq5jamrVr8j1TZ9+rxcLgH1lBMsXj8IwW+6BleXredEg==", + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.9.tgz", + "integrity": "sha512-82w5qMgEeYvf8+vX/2KE5TOZf8rv8VK4TFiK6lDzdgdwwmBU5C8kdT3rO5Llan2K2LKndrou1eyi/fHwFcwPJQ==", + "optional": true + }, + "esbuild-linux-32": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.9.tgz", + "integrity": "sha512-eu8J8HNpco7Mkd7T7djQRzGBeuve41kbXRxFHOwwbZXMNQojXjBqLuradi5i/Vsw+CA4G/yVpmJI2S75Cit2mQ==", + "optional": true + }, + "esbuild-linux-64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.9.tgz", + "integrity": "sha512-WoEI+R6/PLZAxS7XagfQMFgRtLUi5cjqqU9VCfo3tnWmAXh/wt8QtUfCVVCcXVwZLS/RNvI19CtfjlrJU61nOg==", + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.9.tgz", + "integrity": "sha512-d3k1ZPREjaKYyhsS8x3jvc4ekjIZ8SmuihP60mrN1f6p5y07NKWw9i0OWD1p6hy+7g6cjMWq00tstMIikGB9Yg==", + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.9.tgz", + "integrity": "sha512-joUE0yQgWMDkQqBx3+6SdNCVZ10F1O4+WM94moghvhdTzkYpECIc/WvfqMF/w0V8Hecw3QJ7vugO7jsFlXXd4Q==", + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.9.tgz", + "integrity": "sha512-ZAuheiDRo2c4rxx8GUTEwPvos0zUwCYjP9K2WfCSmDL6m3RpaObCQhZghrDuoIUwvc/D6SWuABsKE9VzogsltQ==", + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.9.tgz", + "integrity": "sha512-Pm8FeG5l314k3a2mbu3SAc5E2eLFuGUsGiSlw8V6xtA4whxJ7rit7951w9jBhz+1Vqqtqprg2IYTng3j2CGhVw==", + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.9.tgz", + "integrity": "sha512-G8FNZygV82N1/LOfPD8ZX7Mn1dPpKKPrZc93ebSJ8/VgNIafOAhV5vaeK1lhcx6ZSu+jJU/UyQQMG1CIvHRIaw==", + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.9.tgz", + "integrity": "sha512-b7vPrn5XN0GRtNAQ3w+gq8AwUfWSRBkcPAdA5UUT5rkrw7wKFyMqi2/zREBc/Knu5YOsLmZPQSoM8QL6qy79cg==", + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.9.tgz", + "integrity": "sha512-w95Rt/vmVhZWfzZmeoMIHxbFiOFDmxC7GEdnCbDTXX2vlwKu+CIDIKOgWW+R1T2JqTNo5tu9dRkngKZMfbUo/A==", + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.9.tgz", + "integrity": "sha512-mzgmQZAVGo+uLkQXTY0viqVSEQKesmR5OEMMq1jM/2jucbZUcyaq8dVKRIWJJEzwNgZ6MpeOpshUtOzGxxy8ag==", + "optional": true + }, + "esbuild-windows-32": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.9.tgz", + "integrity": "sha512-sYHEJLwdDJpjjSUyIGqPC1GRXl0Z/YT1K85Tcrv4iqZEXFR0rT7sTV+E0XC911FbTJHfuAdUJixkwAQeLMdrUg==", + "optional": true + }, + "esbuild-windows-64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.9.tgz", + "integrity": "sha512-xJTpyFzpH51LGlVR2C3P+Gpnjujsx5kEtJj5V/x8TyD94VW+EpszyND/pay15CIF64pWywyQt2jmGUDl6kzkEw==", + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.9.tgz", + "integrity": "sha512-NKPPsYVlHqdF0yMuMJrjuAzqS/BHrMXZ8TN1Du+Pgi8KkmxzNXRPDHQV0NPPJ+Z7Lp09joEHSz1zrvQRs1j6jw==", + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "focus-lock": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.9.2.tgz", + "integrity": "sha512-YtHxjX7a0IC0ZACL5wsX8QdncXofWpGPNoVMuI/nZUrPGp6LmNI6+D5j0pPj+v8Kw5EpweA+T5yImK0rnWf7oQ==", + "requires": { + "tslib": "^2.0.3" + } + }, + "framer-motion": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-5.5.5.tgz", + "integrity": "sha512-+LPAF5ddo02qKh+MK4h1ChwqUFvrLkK1NDWwrHy+MuCVmQDGgiFNHvwqOSklTDGkEtbio3dCOEDy23+ZyNAa9g==", + "requires": { + "@emotion/is-prop-valid": "^0.8.2", + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "popmotion": "11.0.3", + "react-merge-refs": "^1.1.0", + "react-use-measure": "^2.1.1", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, + "framesync": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", + "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", + "requires": { + "tslib": "^2.1.0" + } + } + } + }, + "framesync": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz", + "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" + }, + "graphql": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.2.0.tgz", + "integrity": "sha512-MuQd7XXrdOcmfwuLwC2jNvx0n3rxIuNYOxUtiee5XOmfrWo613ar2U8pE7aHAKh8VwfpifubpD9IP+EdEAEOsA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + }, + "history": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz", + "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==", + "requires": { + "@babel/runtime": "^7.7.6" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "requires": { + "has": "^1.0.3" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "popmotion": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", + "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", + "requires": { + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "framesync": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", + "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", + "requires": { + "tslib": "^2.1.0" + } + } + } + }, + "prop-types": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.0.tgz", + "integrity": "sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-clientside-effect": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz", + "integrity": "sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA==", + "requires": { + "@babel/runtime": "^7.12.13" + } + }, + "react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, + "react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + }, + "react-focus-lock": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.2.tgz", + "integrity": "sha512-WzpdOnEqjf+/A3EH9opMZWauag7gV0BxFl+EY4ElA4qFqYsUsBLnmo2sELbN5OC30S16GAWMy16B9DLPpdJKAQ==", + "requires": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^0.9.1", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.5", + "use-callback-ref": "^1.2.5", + "use-sidecar": "^1.0.5" + } + }, + "react-icons": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", + "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==" + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-merge-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==" + }, + "react-remove-scroll": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.1.tgz", + "integrity": "sha512-K7XZySEzOHMTq7dDwcHsZA6Y7/1uX5RsWhRXVYv8rdh+y9Qz2nMwl9RX/Mwnj/j7JstCGmxyfyC0zbVGXYh3mA==", + "requires": { + "react-remove-scroll-bar": "^2.1.0", + "react-style-singleton": "^2.1.0", + "tslib": "^1.0.0", + "use-callback-ref": "^1.2.3", + "use-sidecar": "^1.0.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "react-remove-scroll-bar": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz", + "integrity": "sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==", + "requires": { + "react-style-singleton": "^2.1.0", + "tslib": "^1.0.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "react-router": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", + "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", + "requires": { + "history": "^5.2.0" + } + }, + "react-router-dom": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", + "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", + "requires": { + "history": "^5.2.0", + "react-router": "6.2.1" + } + }, + "react-style-singleton": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz", + "integrity": "sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==", + "requires": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^1.0.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "requires": { + "debounce": "^1.2.1" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "style-value-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", + "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", + "requires": { + "hey-listen": "^1.0.8", + "tslib": "^2.1.0" + } + }, + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==" + }, + "urql": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz", + "integrity": "sha512-ovK9mx7YxD/CKUwVZGbEDBzZjbCcNsr1990nIhDCKe3Ij/0gNcIa+0EIyXKceOPuYDYKavIoaNQV2kOZjQxFcw==", + "requires": { + "@urql/core": "^2.3.6", + "wonka": "^4.0.14" + } + }, + "use-callback-ref": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz", + "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==" + }, + "use-sidecar": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz", + "integrity": "sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==", + "requires": { + "detect-node-es": "^1.1.0", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "wonka": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", + "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==" + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + } + } } diff --git a/dashboard/src/Router.tsx b/dashboard/src/Router.tsx new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/src/components/layouts/AuthLayout.tsx b/dashboard/src/components/layouts/AuthLayout.tsx new file mode 100644 index 0000000..0ba7800 --- /dev/null +++ b/dashboard/src/components/layouts/AuthLayout.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function AuthLayout() { + return

Auth Layout

; +} diff --git a/dashboard/src/components/layouts/DefaultLayout.tsx b/dashboard/src/components/layouts/DefaultLayout.tsx new file mode 100644 index 0000000..505b6c5 --- /dev/null +++ b/dashboard/src/components/layouts/DefaultLayout.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function DefaultLayout() { + return

Default Layout

; +} diff --git a/dashboard/src/contexts/AuthContext.tsx b/dashboard/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..e69de29 diff --git a/server/__test__/admin_logout_test.go b/server/__test__/admin_logout_test.go deleted file mode 100644 index c548368..0000000 --- a/server/__test__/admin_logout_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package test - -import ( - "fmt" - "testing" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" - "github.com/stretchr/testify/assert" -) - -func adminLogoutTests(s TestSetup, t *testing.T) { - t.Run(`should get admin session`, func(t *testing.T) { - req, ctx := createContext(s) - _, err := resolvers.AdminLogout(ctx) - assert.NotNil(t, err) - - h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) - assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) - _, err = resolvers.AdminLogout(ctx) - - assert.Nil(t, err) - }) -} diff --git a/server/__test__/admin_session_test.go b/server/__test__/admin_session_test.go deleted file mode 100644 index afc0179..0000000 --- a/server/__test__/admin_session_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package test - -import ( - "fmt" - "log" - "testing" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/authorizerdev/authorizer/server/utils" - "github.com/stretchr/testify/assert" -) - -func adminSessionTests(s TestSetup, t *testing.T) { - t.Run(`should get admin session`, func(t *testing.T) { - req, ctx := createContext(s) - _, err := resolvers.AdminSession(ctx) - log.Println("error:", err) - assert.NotNil(t, err) - - h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) - assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) - _, err = resolvers.AdminSession(ctx) - - assert.Nil(t, err) - }) -} diff --git a/server/__test__/env_test.go b/server/__test__/env_test.go deleted file mode 100644 index 328d5f6..0000000 --- a/server/__test__/env_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package test - -import ( - "testing" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/stretchr/testify/assert" -) - -func TestEnvs(t *testing.T) { - constants.EnvData.ENV_PATH = "../../.env.sample" - - assert.Equal(t, constants.EnvData.ADMIN_SECRET, "admin") - assert.Equal(t, constants.EnvData.ENV, "production") - assert.False(t, constants.EnvData.DISABLE_EMAIL_VERIFICATION) - assert.False(t, constants.EnvData.DISABLE_MAGIC_LINK_LOGIN) - assert.False(t, constants.EnvData.DISABLE_BASIC_AUTHENTICATION) - assert.Equal(t, constants.EnvData.JWT_TYPE, "HS256") - assert.Equal(t, constants.EnvData.JWT_SECRET, "random_string") - assert.Equal(t, constants.EnvData.JWT_ROLE_CLAIM, "role") - assert.EqualValues(t, constants.EnvData.ROLES, []string{"user"}) - assert.EqualValues(t, constants.EnvData.DEFAULT_ROLES, []string{"user"}) - assert.EqualValues(t, constants.EnvData.PROTECTED_ROLES, []string{"admin"}) - assert.EqualValues(t, constants.EnvData.ALLOWED_ORIGINS, []string{"*"}) -} diff --git a/server/__test__/resolvers_test.go b/server/__test__/resolvers_test.go deleted file mode 100644 index e756672..0000000 --- a/server/__test__/resolvers_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package test - -import ( - "log" - "testing" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" - "github.com/authorizerdev/authorizer/server/env" -) - -func TestResolvers(t *testing.T) { - databases := map[string]string{ - enum.Sqlite.String(): "../../data.db", - enum.Arangodb.String(): "http://localhost:8529", - enum.Mongodb.String(): "mongodb://localhost:27017", - } - - for dbType, dbURL := range databases { - constants.EnvData.DATABASE_URL = dbURL - constants.EnvData.DATABASE_TYPE = dbType - db.InitDB() - - // clean the persisted config for test to use fresh config - config, err := db.Mgr.GetConfig() - if err == nil { - config.Config = []byte{} - db.Mgr.UpdateConfig(config) - } - env.PersistEnv() - - s := testSetup() - defer s.Server.Close() - - log.Println("EnvData:", constants.EnvData) - t.Run("should pass tests for "+dbType, func(t *testing.T) { - // admin tests - adminSignupTests(s, t) - verificationRequestsTest(s, t) - usersTest(s, t) - deleteUserTest(s, t) - updateUserTest(s, t) - adminLoginTests(s, t) - adminLogoutTests(s, t) - adminSessionTests(s, t) - updateConfigTests(s, t) - configTests(s, t) - - // user tests - loginTests(s, t) - signupTests(s, t) - forgotPasswordTest(s, t) - resendVerifyEmailTests(s, t) - resetPasswordTest(s, t) - verifyEmailTest(s, t) - sessionTests(s, t) - profileTests(s, t) - updateProfileTests(s, t) - magicLinkLoginTests(s, t) - logoutTests(s, t) - metaTests(s, t) - }) - } -} diff --git a/server/constants/constants.go b/server/constants/constants.go deleted file mode 100644 index a2bc497..0000000 --- a/server/constants/constants.go +++ /dev/null @@ -1,62 +0,0 @@ -package constants - -type EnvConst struct { - ADMIN_SECRET string - ENV string - ENV_PATH string - VERSION string - DATABASE_TYPE string - DATABASE_URL string - DATABASE_NAME string - SMTP_HOST string - SMTP_PORT string - SMTP_PASSWORD string - SMTP_USERNAME string - SENDER_EMAIL string - JWT_TYPE string - JWT_SECRET string - ALLOWED_ORIGINS []string - AUTHORIZER_URL string - APP_URL string - PORT string - REDIS_URL string - COOKIE_NAME string - ADMIN_COOKIE_NAME string - RESET_PASSWORD_URL string - ENCRYPTION_KEY string `json:"-"` - IS_PROD bool - DISABLE_EMAIL_VERIFICATION bool - DISABLE_BASIC_AUTHENTICATION bool - DISABLE_MAGIC_LINK_LOGIN bool - DISABLE_LOGIN_PAGE bool - - // ROLES - ROLES []string - PROTECTED_ROLES []string - DEFAULT_ROLES []string - JWT_ROLE_CLAIM string - - // OAuth login - GOOGLE_CLIENT_ID string - GOOGLE_CLIENT_SECRET string - GITHUB_CLIENT_ID string - GITHUB_CLIENT_SECRET string - FACEBOOK_CLIENT_ID string - FACEBOOK_CLIENT_SECRET string - - // Org envs - ORGANIZATION_NAME string - ORGANIZATION_LOGO string -} - -var EnvData = EnvConst{ - ADMIN_COOKIE_NAME: "authorizer-admin", - JWT_ROLE_CLAIM: "role", - ORGANIZATION_NAME: "Authorizer", - ORGANIZATION_LOGO: "https://authorizer.dev/images/logo.png", - DISABLE_EMAIL_VERIFICATION: false, - DISABLE_BASIC_AUTHENTICATION: false, - DISABLE_MAGIC_LINK_LOGIN: false, - DISABLE_LOGIN_PAGE: false, - IS_PROD: false, -} diff --git a/server/constants/db_types.go b/server/constants/db_types.go new file mode 100644 index 0000000..9cdef99 --- /dev/null +++ b/server/constants/db_types.go @@ -0,0 +1,16 @@ +package constants + +const ( + // DbTypePostgres is the postgres database type + DbTypePostgres = "postgres" + // DbTypeSqlite is the sqlite database type + DbTypeSqlite = "sqlite" + // DbTypeMysql is the mysql database type + DbTypeMysql = "mysql" + // DbTypeSqlserver is the sqlserver database type + DbTypeSqlserver = "sqlserver" + // DbTypeArangodb is the arangodb database type + DbTypeArangodb = "arangodb" + // DbTypeMongodb is the mongodb database type + DbTypeMongodb = "mongodb" +) diff --git a/server/constants/env.go b/server/constants/env.go new file mode 100644 index 0000000..ddff7b4 --- /dev/null +++ b/server/constants/env.go @@ -0,0 +1,87 @@ +package constants + +const ( + // EnvKeyEnv key for env variable ENV + EnvKeyEnv = "ENV" + // EnvKeyEnvPath key for cli arg variable ENV_PATH + EnvKeyEnvPath = "ENV_PATH" + // EnvKeyVersion key for build arg version + EnvKeyVersion = "VERSION" + // EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL + EnvKeyAuthorizerURL = "AUTHORIZER_URL" + // EnvKeyPort key for env variable PORT + EnvKeyPort = "PORT" + + // EnvKeyAdminSecret key for env variable ADMIN_SECRET + EnvKeyAdminSecret = "ADMIN_SECRET" + // EnvKeyDatabaseType key for env variable DATABASE_TYPE + EnvKeyDatabaseType = "DATABASE_TYPE" + // EnvKeyDatabaseURL key for env variable DATABASE_URL + EnvKeyDatabaseURL = "DATABASE_URL" + // EnvKeyDatabaseName key for env variable DATABASE_NAME + EnvKeyDatabaseName = "DATABASE_NAME" + // EnvKeySmtpHost key for env variable SMTP_HOST + EnvKeySmtpHost = "SMTP_HOST" + // EnvKeySmtpPort key for env variable SMTP_PORT + EnvKeySmtpPort = "SMTP_PORT" + // EnvKeySmtpUsername key for env variable SMTP_USERNAME + EnvKeySmtpUsername = "SMTP_USERNAME" + // EnvKeySmtpPassword key for env variable SMTP_PASSWORD + EnvKeySmtpPassword = "SMTP_PASSWORD" + // EnvKeySenderEmail key for env variable SENDER_EMAIL + EnvKeySenderEmail = "SENDER_EMAIL" + // EnvKeyJwtType key for env variable JWT_TYPE + EnvKeyJwtType = "JWT_TYPE" + // EnvKeyJwtSecret key for env variable JWT_SECRET + EnvKeyJwtSecret = "JWT_SECRET" + // EnvKeyAllowedOrigins key for env variable ALLOWED_ORIGINS + EnvKeyAllowedOrigins = "ALLOWED_ORIGINS" + // EnvKeyAppURL key for env variable APP_URL + EnvKeyAppURL = "APP_URL" + // EnvKeyRedisURL key for env variable REDIS_URL + EnvKeyRedisURL = "REDIS_URL" + // EnvKeyCookieName key for env variable COOKIE_NAME + EnvKeyCookieName = "COOKIE_NAME" + // EnvKeyAdminCookieName key for env variable ADMIN_COOKIE_NAME + EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME" + // EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL + EnvKeyResetPasswordURL = "RESET_PASSWORD_URL" + // EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY + EnvKeyEncryptionKey = "ENCRYPTION_KEY" + // EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION + EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION" + // EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH + EnvKeyDisableBasicAuthentication = "DISABLE_BASIC_AUTHENTICATION" + // EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN + EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN" + // EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE + EnvKeyDisableLoginPage = "DISABLE_LOGIN_PAGE" + // EnvKeyRoles key for env variable ROLES + EnvKeyRoles = "ROLES" + // EnvKeyProtectedRoles key for env variable PROTECTED_ROLES + EnvKeyProtectedRoles = "PROTECTED_ROLES" + // EnvKeyDefaultRoles key for env variable DEFAULT_ROLES + EnvKeyDefaultRoles = "DEFAULT_ROLES" + // EnvKeyJwtRoleClaim key for env variable JWT_ROLE_CLAIM + EnvKeyJwtRoleClaim = "JWT_ROLE_CLAIM" + // EnvKeyGoogleClientID key for env variable GOOGLE_CLIENT_ID + EnvKeyGoogleClientID = "GOOGLE_CLIENT_ID" + // EnvKeyGoogleClientSecret key for env variable GOOGLE_CLIENT_SECRET + EnvKeyGoogleClientSecret = "GOOGLE_CLIENT_SECRET" + // EnvKeyGithubClientID key for env variable GITHUB_CLIENT_ID + EnvKeyGithubClientID = "GITHUB_CLIENT_ID" + // EnvKeyGithubClientSecret key for env variable GITHUB_CLIENT_SECRET + EnvKeyGithubClientSecret = "GITHUB_CLIENT_SECRET" + // EnvKeyFacebookClientID key for env variable FACEBOOK_CLIENT_ID + EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID" + // EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET + EnvKeyFacebookClientSecret = "FACEBOOK_CLIENT_SECRET" + // EnvKeyOrganizationName key for env variable ORGANIZATION_NAME + EnvKeyOrganizationName = "ORGANIZATION_NAME" + // EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO + EnvKeyOrganizationLogo = "ORGANIZATION_LOGO" + // EnvKeyIsProd key for env variable IS_PROD + EnvKeyIsProd = "IS_PROD" + // EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT + EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT" +) diff --git a/server/constants/oauth_info_urls.go b/server/constants/oauth_info_urls.go index 220fb95..4944e3d 100644 --- a/server/constants/oauth_info_urls.go +++ b/server/constants/oauth_info_urls.go @@ -1,6 +1,6 @@ package constants -var ( +const ( // Ref: https://github.com/qor/auth/blob/master/providers/google/google.go // deprecated and not used. instead we follow open id approach for google login GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo" diff --git a/server/constants/signup_methods.go b/server/constants/signup_methods.go new file mode 100644 index 0000000..3ca373c --- /dev/null +++ b/server/constants/signup_methods.go @@ -0,0 +1,14 @@ +package constants + +const ( + // SignupMethodBasicAuth is the basic_auth signup method + SignupMethodBasicAuth = "basic_auth" + // SignupMethodMagicLinkLogin is the magic_link_login signup method + SignupMethodMagicLinkLogin = "magic_link_login" + // SignupMethodGoogle is the google signup method + SignupMethodGoogle = "google" + // SignupMethodGithub is the github signup method + SignupMethodGithub = "github" + // SignupMethodFacebook is the facebook signup method + SignupMethodFacebook = "facebook" +) diff --git a/server/constants/token_types.go b/server/constants/token_types.go new file mode 100644 index 0000000..993d4ea --- /dev/null +++ b/server/constants/token_types.go @@ -0,0 +1,8 @@ +package constants + +const ( + // TokenTypeRefreshToken is the refresh_token token type + TokenTypeRefreshToken = "refresh_token" + // TokenTypeAccessToken is the access_token token type + TokenTypeAccessToken = "access_token" +) diff --git a/server/constants/verification_types.go b/server/constants/verification_types.go new file mode 100644 index 0000000..de64dbb --- /dev/null +++ b/server/constants/verification_types.go @@ -0,0 +1,12 @@ +package constants + +const ( + // VerificationTypeBasicAuthSignup is the basic_auth_signup verification type + VerificationTypeBasicAuthSignup = "basic_auth_signup" + // VerificationTypeMagicLinkLogin is the magic_link_login verification type + VerificationTypeMagicLinkLogin = "magic_link_login" + // VerificationTypeUpdateEmail is the update_email verification type + VerificationTypeUpdateEmail = "update_email" + // VerificationTypeForgotPassword is the forgot_password verification type + VerificationTypeForgotPassword = "forgot_password" +) diff --git a/server/db/arangodb.go b/server/db/arangodb.go index f6cdb06..d7cfd64 100644 --- a/server/db/arangodb.go +++ b/server/db/arangodb.go @@ -8,6 +8,7 @@ import ( arangoDriver "github.com/arangodb/go-driver" "github.com/arangodb/go-driver/http" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" ) // for this we need arangodb instance up and running @@ -17,7 +18,7 @@ import ( func initArangodb() (arangoDriver.Database, error) { ctx := context.Background() conn, err := http.NewConnection(http.ConnectionConfig{ - Endpoints: []string{constants.EnvData.DATABASE_URL}, + Endpoints: []string{envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseURL).(string)}, }) if err != nil { return nil, err @@ -32,16 +33,16 @@ func initArangodb() (arangoDriver.Database, error) { var arangodb driver.Database - arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.EnvData.DATABASE_NAME) + arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseName).(string)) if arangodb_exists { - log.Println(constants.EnvData.DATABASE_NAME + " db exists already") - arangodb, err = arangoClient.Database(nil, constants.EnvData.DATABASE_NAME) + log.Println(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseName).(string) + " db exists already") + arangodb, err = arangoClient.Database(nil, envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseName).(string)) if err != nil { return nil, err } } else { - arangodb, err = arangoClient.CreateDatabase(nil, constants.EnvData.DATABASE_NAME, nil) + arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseName).(string), nil) if err != nil { return nil, err } diff --git a/server/db/db.go b/server/db/db.go index 8727da5..5ba6da2 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -5,7 +5,7 @@ import ( arangoDriver "github.com/arangodb/go-driver" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "go.mongodb.org/mongo-driver/mongo" "gorm.io/driver/mysql" "gorm.io/driver/postgres" @@ -66,9 +66,9 @@ func InitDB() { var sqlDB *gorm.DB var err error - IsORMSupported = constants.EnvData.DATABASE_TYPE != enum.Arangodb.String() && constants.EnvData.DATABASE_TYPE != enum.Mongodb.String() - IsArangoDB = constants.EnvData.DATABASE_TYPE == enum.Arangodb.String() - IsMongoDB = constants.EnvData.DATABASE_TYPE == enum.Mongodb.String() + IsORMSupported = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseType).(string) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseType).(string) != constants.DbTypeMongodb + IsArangoDB = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseType).(string) == constants.DbTypeArangodb + IsMongoDB = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseType).(string) == constants.DbTypeMongodb // sql db orm config ormConfig := &gorm.Config{ @@ -77,22 +77,22 @@ func InitDB() { }, } - log.Println("db type:", constants.EnvData.DATABASE_TYPE) + log.Println("db type:", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseType).(string)) - switch constants.EnvData.DATABASE_TYPE { - case enum.Postgres.String(): - sqlDB, err = gorm.Open(postgres.Open(constants.EnvData.DATABASE_URL), ormConfig) + switch envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseType).(string) { + case constants.DbTypePostgres: + sqlDB, err = gorm.Open(postgres.Open(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseURL).(string)), ormConfig) break - case enum.Sqlite.String(): - sqlDB, err = gorm.Open(sqlite.Open(constants.EnvData.DATABASE_URL), ormConfig) + case constants.DbTypeSqlite: + sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseURL).(string)), ormConfig) break - case enum.Mysql.String(): - sqlDB, err = gorm.Open(mysql.Open(constants.EnvData.DATABASE_URL), ormConfig) + case constants.DbTypeMysql: + sqlDB, err = gorm.Open(mysql.Open(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseURL).(string)), ormConfig) break - case enum.SQLServer.String(): - sqlDB, err = gorm.Open(sqlserver.Open(constants.EnvData.DATABASE_URL), ormConfig) + case constants.DbTypeSqlserver: + sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseURL).(string)), ormConfig) break - case enum.Arangodb.String(): + case constants.DbTypeArangodb: arangodb, err := initArangodb() if err != nil { log.Fatal("error initializing arangodb:", err) @@ -105,7 +105,7 @@ func InitDB() { } break - case enum.Mongodb.String(): + case constants.DbTypeMongodb: mongodb, err := initMongodb() if err != nil { log.Fatal("error initializing mongodb connection:", err) diff --git a/server/db/mongodb.go b/server/db/mongodb.go index 21d9320..971b5ec 100644 --- a/server/db/mongodb.go +++ b/server/db/mongodb.go @@ -5,6 +5,7 @@ import ( "time" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -12,7 +13,7 @@ import ( ) func initMongodb() (*mongo.Database, error) { - mongodbOptions := options.Client().ApplyURI(constants.EnvData.DATABASE_URL) + mongodbOptions := options.Client().ApplyURI(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseURL).(string)) maxWait := time.Duration(5 * time.Second) mongodbOptions.ConnectTimeout = &maxWait mongoClient, err := mongo.NewClient(mongodbOptions) @@ -30,7 +31,7 @@ func initMongodb() (*mongo.Database, error) { return nil, err } - mongodb := mongoClient.Database(constants.EnvData.DATABASE_NAME, options.Database()) + mongodb := mongoClient.Database(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDatabaseName).(string), options.Database()) mongodb.CreateCollection(ctx, Collections.User, options.CreateCollection()) userCollection := mongodb.Collection(Collections.User, options.Collection()) diff --git a/server/db/user.go b/server/db/user.go index f020dcf..48c0015 100644 --- a/server/db/user.go +++ b/server/db/user.go @@ -3,11 +3,13 @@ package db import ( "fmt" "log" + "strings" "time" "github.com/arangodb/go-driver" arangoDriver "github.com/arangodb/go-driver" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/google/uuid" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" @@ -43,7 +45,7 @@ func (mgr *manager) AddUser(user User) (User, error) { } if user.Roles == "" { - user.Roles = constants.EnvData.DEFAULT_ROLES[0] + user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDefaultRoles).([]string), ",") } if IsORMSupported { @@ -185,6 +187,7 @@ func (mgr *manager) GetUsers() ([]User, error) { return users, nil } +// GetUserByEmail function to get user by email func (mgr *manager) GetUserByEmail(email string) (User, error) { var user User @@ -233,6 +236,7 @@ func (mgr *manager) GetUserByEmail(email string) (User, error) { return user, nil } +// GetUserByID function to get user by ID func (mgr *manager) GetUserByID(id string) (User, error) { var user User @@ -281,6 +285,7 @@ func (mgr *manager) GetUserByID(id string) (User, error) { return user, nil } +// DeleteUser function to delete user func (mgr *manager) DeleteUser(user User) error { if IsORMSupported { result := mgr.sqlDB.Delete(&user) diff --git a/server/email/email.go b/server/email/email.go index 843bf0f..adf1ac5 100644 --- a/server/email/email.go +++ b/server/email/email.go @@ -1,23 +1,44 @@ package email import ( + "bytes" "crypto/tls" + "encoding/json" "log" "strconv" + "text/template" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" gomail "gopkg.in/mail.v2" ) +// addEmailTemplate is used to add html template in email body +func addEmailTemplate(a string, b map[string]interface{}, templateName string) string { + tmpl, err := template.New(templateName).Parse(a) + if err != nil { + output, _ := json.Marshal(b) + return string(output) + } + buf := &bytes.Buffer{} + err = tmpl.Execute(buf, b) + if err != nil { + panic(err) + } + s := buf.String() + return s +} + +// SendMail function to send mail func SendMail(to []string, Subject, bodyMessage string) error { m := gomail.NewMessage() - m.SetHeader("From", constants.EnvData.SENDER_EMAIL) + m.SetHeader("From", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeySenderEmail).(string)) m.SetHeader("To", to...) m.SetHeader("Subject", Subject) m.SetBody("text/html", bodyMessage) - port, _ := strconv.Atoi(constants.EnvData.SMTP_PORT) - d := gomail.NewDialer(constants.EnvData.SMTP_HOST, port, constants.EnvData.SMTP_USERNAME, constants.EnvData.SMTP_PASSWORD) - if constants.EnvData.ENV == "development" { + port, _ := strconv.Atoi(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeySmtpPort).(string)) + d := gomail.NewDialer(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeySmtpHost).(string), port, envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeySmtpUsername).(string), envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeySmtpPassword).(string)) + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyEnv).(string) == "development" { d.TLSConfig = &tls.Config{InsecureSkipVerify: true} } if err := d.DialAndSend(m); err != nil { diff --git a/server/email/forgot_password_email.go b/server/email/forgot_password_email.go new file mode 100644 index 0000000..3320315 --- /dev/null +++ b/server/email/forgot_password_email.go @@ -0,0 +1,112 @@ +package email + +import ( + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" +) + +// SendForgotPasswordMail to send forgot password email +func SendForgotPasswordMail(toEmail, token, host string) error { + resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyResetPasswordURL).(string) + if resetPasswordUrl == "" { + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string)+"/app/reset-password") + } + + // The receiver needs to be in slice as the receive supports multiple receiver + Receiver := []string{toEmail} + + Subject := "Reset Password" + + message := ` + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + +
+
+
+ + + ` + + data := make(map[string]interface{}, 3) + data["org_logo"] = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyOrganizationLogo).(string) + data["org_name"] = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyOrganizationName).(string) + data["verification_url"] = resetPasswordUrl + "?token=" + token + message = addEmailTemplate(message, data, "reset_password_email.tmpl") + + return SendMail(Receiver, Subject, message) +} diff --git a/server/email/verification_email.go b/server/email/verification_email.go new file mode 100644 index 0000000..1c4fd23 --- /dev/null +++ b/server/email/verification_email.go @@ -0,0 +1,107 @@ +package email + +import ( + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" +) + +// SendVerificationMail to send verification email +func SendVerificationMail(toEmail, token string) error { + // The receiver needs to be in slice as the receive supports multiple receiver + Receiver := []string{toEmail} + + Subject := "Please verify your email" + message := ` + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + +
+
+
+ + + ` + data := make(map[string]interface{}, 3) + data["org_logo"] = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyOrganizationLogo).(string) + data["org_name"] = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyOrganizationName).(string) + data["verification_url"] = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + "/verify_email?token=" + token + message = addEmailTemplate(message, data, "verify_email.tmpl") + // bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) + + return SendMail(Receiver, Subject, message) +} diff --git a/server/enum/db_types.go b/server/enum/db_types.go deleted file mode 100644 index d94a332..0000000 --- a/server/enum/db_types.go +++ /dev/null @@ -1,23 +0,0 @@ -package enum - -type DbType int - -const ( - Postgres DbType = iota - Sqlite - Mysql - SQLServer - Arangodb - Mongodb -) - -func (d DbType) String() string { - return [...]string{ - "postgres", - "sqlite", - "mysql", - "sqlserver", - "arangodb", - "mongodb", - }[d] -} diff --git a/server/enum/oauth_providers.go b/server/enum/oauth_providers.go deleted file mode 100644 index 3c3755c..0000000 --- a/server/enum/oauth_providers.go +++ /dev/null @@ -1,15 +0,0 @@ -package enum - -type OAuthProvider int - -const ( - GoogleProvider OAuthProvider = iota - GithubProvider -) - -func (d OAuthProvider) String() string { - return [...]string{ - "google_provider", - "github_provider", - }[d] -} diff --git a/server/enum/signup_methods.go b/server/enum/signup_methods.go deleted file mode 100644 index 1c82704..0000000 --- a/server/enum/signup_methods.go +++ /dev/null @@ -1,21 +0,0 @@ -package enum - -type SignupMethod int - -const ( - BasicAuth SignupMethod = iota - MagicLinkLogin - Google - Github - Facebook -) - -func (d SignupMethod) String() string { - return [...]string{ - "basic_auth", - "magic_link_login", - "google", - "github", - "facebook", - }[d] -} diff --git a/server/enum/token_types.go b/server/enum/token_types.go deleted file mode 100644 index f97e1e2..0000000 --- a/server/enum/token_types.go +++ /dev/null @@ -1,15 +0,0 @@ -package enum - -type TokenType int - -const ( - RefreshToken TokenType = iota - AccessToken -) - -func (d TokenType) String() string { - return [...]string{ - "refresh_token", - "access_token", - }[d] -} diff --git a/server/enum/verification_types.go b/server/enum/verification_types.go deleted file mode 100644 index fb409f9..0000000 --- a/server/enum/verification_types.go +++ /dev/null @@ -1,17 +0,0 @@ -package enum - -type VerificationType int - -const ( - BasicAuthSignup VerificationType = iota - UpdateEmail - ForgotPassword -) - -func (d VerificationType) String() string { - return [...]string{ - "basic_auth_signup", - "update_email", - "forgot_password", - }[d] -} diff --git a/server/env/env.go b/server/env/env.go index 66a632d..cd9ba0a 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -6,188 +6,197 @@ import ( "strings" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/google/uuid" "github.com/joho/godotenv" ) -// build variables +// TODO move this to env store var ( - ARG_DB_URL *string - ARG_DB_TYPE *string - ARG_AUTHORIZER_URL *string - ARG_ENV_FILE *string + // ARG_DB_URL is the cli arg variable for the database url + ARG_DB_URL *string + // ARG_DB_TYPE is the cli arg variable for the database type + ARG_DB_TYPE *string + // ARG_ENV_FILE is the cli arg variable for the env file + ARG_ENV_FILE *string ) -// InitEnv -> to initialize env and through error if required env are not present +// InitEnv to initialize EnvData and through error if required env are not present func InitEnv() { - if constants.EnvData.ENV_PATH == "" { - constants.EnvData.ENV_PATH = `.env` + // get clone of current store + envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + + if envData[constants.EnvKeyEnv] == nil || envData[constants.EnvKeyEnv] == "" { + envData[constants.EnvKeyEnv] = os.Getenv("ENV") + if envData[constants.EnvKeyEnv] == "" { + envData[constants.EnvKeyEnv] = "production" + } + + if envData[constants.EnvKeyEnv] == "production" { + envData[constants.EnvKeyIsProd] = true + os.Setenv("GIN_MODE", "release") + } else { + envData[constants.EnvKeyIsProd] = false + } + } + + // set authorizer url to empty string so that fresh url is obtained with every server start + envData[constants.EnvKeyAuthorizerURL] = "" + if envData[constants.EnvKeyAppURL] == nil || envData[constants.EnvKeyAppURL] == "" { + envData[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL) + } + + if envData[constants.EnvKeyEnvPath] == nil || envData[constants.EnvKeyEnvPath].(string) == "" { + envData[constants.EnvKeyEnvPath] = `.env` } if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" { - constants.EnvData.ENV_PATH = *ARG_ENV_FILE + envData[constants.EnvKeyEnvPath] = *ARG_ENV_FILE } - err := godotenv.Load(constants.EnvData.ENV_PATH) + err := godotenv.Load(envData[constants.EnvKeyEnvPath].(string)) if err != nil { - log.Printf("error loading %s file", constants.EnvData.ENV_PATH) + log.Printf("error loading %s file", envData[constants.EnvKeyEnvPath]) } - if constants.EnvData.ADMIN_SECRET == "" { - constants.EnvData.ADMIN_SECRET = os.Getenv("ADMIN_SECRET") + if envData[constants.EnvKeyPort] == nil || envData[constants.EnvKeyPort].(string) == "" { + envData[constants.EnvKeyPort] = os.Getenv("PORT") + if envData[constants.EnvKeyPort].(string) == "" { + envData[constants.EnvKeyPort] = "8080" + } } - if constants.EnvData.DATABASE_TYPE == "" { - constants.EnvData.DATABASE_TYPE = os.Getenv("DATABASE_TYPE") - log.Println(constants.EnvData.DATABASE_TYPE) + if envData[constants.EnvKeyAdminSecret] == nil || envData[constants.EnvKeyAdminSecret].(string) == "" { + envData[constants.EnvKeyAdminSecret] = os.Getenv("ADMIN_SECRET") + } + + if envData[constants.EnvKeyDatabaseType] == nil || envData[constants.EnvKeyDatabaseType].(string) == "" { + envData[constants.EnvKeyDatabaseType] = os.Getenv("DATABASE_TYPE") + log.Println(envData[constants.EnvKeyDatabaseType].(string)) if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" { - constants.EnvData.DATABASE_TYPE = *ARG_DB_TYPE + envData[constants.EnvKeyDatabaseType] = *ARG_DB_TYPE } - if constants.EnvData.DATABASE_TYPE == "" { + if envData[constants.EnvKeyDatabaseType].(string) == "" { panic("DATABASE_TYPE is required") } } - if constants.EnvData.DATABASE_URL == "" { - constants.EnvData.DATABASE_URL = os.Getenv("DATABASE_URL") + if envData[constants.EnvKeyDatabaseURL] == nil || envData[constants.EnvKeyDatabaseURL].(string) == "" { + envData[constants.EnvKeyDatabaseURL] = os.Getenv("DATABASE_URL") if ARG_DB_URL != nil && *ARG_DB_URL != "" { - constants.EnvData.DATABASE_URL = *ARG_DB_URL + envData[constants.EnvKeyDatabaseURL] = *ARG_DB_URL } - if constants.EnvData.DATABASE_URL == "" { + if envData[constants.EnvKeyDatabaseURL] == "" { panic("DATABASE_URL is required") } } - if constants.EnvData.DATABASE_NAME == "" { - constants.EnvData.DATABASE_NAME = os.Getenv("DATABASE_NAME") - if constants.EnvData.DATABASE_NAME == "" { - constants.EnvData.DATABASE_NAME = "authorizer" + if envData[constants.EnvKeyDatabaseName] == nil || envData[constants.EnvKeyDatabaseName].(string) == "" { + envData[constants.EnvKeyDatabaseName] = os.Getenv("DATABASE_NAME") + if envData[constants.EnvKeyDatabaseName].(string) == "" { + envData[constants.EnvKeyDatabaseName] = "authorizer" } } - if constants.EnvData.ENV == "" { - constants.EnvData.ENV = os.Getenv("ENV") - if constants.EnvData.ENV == "" { - constants.EnvData.ENV = "production" - } + if envData[constants.EnvKeySmtpHost] == nil || envData[constants.EnvKeySmtpHost].(string) == "" { + envData[constants.EnvKeySmtpHost] = os.Getenv("SMTP_HOST") + } - if constants.EnvData.ENV == "production" { - constants.EnvData.IS_PROD = true - os.Setenv("GIN_MODE", "release") - } else { - constants.EnvData.IS_PROD = false + if envData[constants.EnvKeySmtpPort] == nil || envData[constants.EnvKeySmtpPort].(string) == "" { + envData[constants.EnvKeySmtpPort] = os.Getenv("SMTP_PORT") + } + + if envData[constants.EnvKeySmtpUsername] == nil || envData[constants.EnvKeySmtpUsername].(string) == "" { + envData[constants.EnvKeySmtpUsername] = os.Getenv("SMTP_USERNAME") + } + + if envData[constants.EnvKeySmtpPassword] == nil || envData[constants.EnvKeySmtpPassword].(string) == "" { + envData[constants.EnvKeySmtpPassword] = os.Getenv("SMTP_PASSWORD") + } + + if envData[constants.EnvKeySenderEmail] == nil || envData[constants.EnvKeySenderEmail].(string) == "" { + envData[constants.EnvKeySenderEmail] = os.Getenv("SENDER_EMAIL") + } + + if envData[constants.EnvKeyJwtSecret] == nil || envData[constants.EnvKeyJwtSecret].(string) == "" { + envData[constants.EnvKeyJwtSecret] = os.Getenv("JWT_SECRET") + if envData[constants.EnvKeyJwtSecret].(string) == "" { + envData[constants.EnvKeyJwtSecret] = uuid.New().String() } } - if constants.EnvData.SMTP_HOST == "" { - constants.EnvData.SMTP_HOST = os.Getenv("SMTP_HOST") - } - - if constants.EnvData.SMTP_PORT == "" { - constants.EnvData.SMTP_PORT = os.Getenv("SMTP_PORT") - } - - if constants.EnvData.SMTP_USERNAME == "" { - constants.EnvData.SMTP_USERNAME = os.Getenv("SMTP_USERNAME") - } - - if constants.EnvData.SMTP_PASSWORD == "" { - constants.EnvData.SMTP_PASSWORD = os.Getenv("SMTP_PASSWORD") - } - - if constants.EnvData.SENDER_EMAIL == "" { - constants.EnvData.SENDER_EMAIL = os.Getenv("SENDER_EMAIL") - } - - if constants.EnvData.JWT_SECRET == "" { - constants.EnvData.JWT_SECRET = os.Getenv("JWT_SECRET") - if constants.EnvData.JWT_SECRET == "" { - constants.EnvData.JWT_SECRET = uuid.New().String() + if envData[constants.EnvKeyJwtType] == nil || envData[constants.EnvKeyJwtType].(string) == "" { + envData[constants.EnvKeyJwtType] = os.Getenv("JWT_TYPE") + if envData[constants.EnvKeyJwtType].(string) == "" { + envData[constants.EnvKeyJwtType] = "HS256" } } - if constants.EnvData.JWT_TYPE == "" { - constants.EnvData.JWT_TYPE = os.Getenv("JWT_TYPE") - if constants.EnvData.JWT_TYPE == "" { - constants.EnvData.JWT_TYPE = "HS256" + if envData[constants.EnvKeyJwtRoleClaim] == nil || envData[constants.EnvKeyJwtRoleClaim].(string) == "" { + envData[constants.EnvKeyJwtRoleClaim] = os.Getenv("JWT_ROLE_CLAIM") + + if envData[constants.EnvKeyJwtRoleClaim].(string) == "" { + envData[constants.EnvKeyJwtRoleClaim] = "role" } } - if constants.EnvData.JWT_ROLE_CLAIM == "" { - constants.EnvData.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM") + if envData[constants.EnvKeyRedisURL] == nil || envData[constants.EnvKeyRedisURL].(string) == "" { + envData[constants.EnvKeyRedisURL] = os.Getenv("REDIS_URL") + } - if constants.EnvData.JWT_ROLE_CLAIM == "" { - constants.EnvData.JWT_ROLE_CLAIM = "role" + if envData[constants.EnvKeyCookieName] == nil || envData[constants.EnvKeyCookieName].(string) == "" { + envData[constants.EnvKeyCookieName] = os.Getenv("COOKIE_NAME") + if envData[constants.EnvKeyCookieName].(string) == "" { + envData[constants.EnvKeyCookieName] = "authorizer" } } - if constants.EnvData.AUTHORIZER_URL == "" { - constants.EnvData.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/") - - if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" { - constants.EnvData.AUTHORIZER_URL = *ARG_AUTHORIZER_URL - } + if envData[constants.EnvKeyGoogleClientID] == nil || envData[constants.EnvKeyGoogleClientID].(string) == "" { + envData[constants.EnvKeyGoogleClientID] = os.Getenv("GOOGLE_CLIENT_ID") } - if constants.EnvData.PORT == "" { - constants.EnvData.PORT = os.Getenv("PORT") - if constants.EnvData.PORT == "" { - constants.EnvData.PORT = "8080" - } + if envData[constants.EnvKeyGoogleClientSecret] == nil || envData[constants.EnvKeyGoogleClientSecret].(string) == "" { + envData[constants.EnvKeyGoogleClientSecret] = os.Getenv("GOOGLE_CLIENT_SECRET") } - if constants.EnvData.REDIS_URL == "" { - constants.EnvData.REDIS_URL = os.Getenv("REDIS_URL") + if envData[constants.EnvKeyGithubClientID] == nil || envData[constants.EnvKeyGithubClientID].(string) == "" { + envData[constants.EnvKeyGithubClientID] = os.Getenv("GITHUB_CLIENT_ID") } - if constants.EnvData.COOKIE_NAME == "" { - constants.EnvData.COOKIE_NAME = os.Getenv("COOKIE_NAME") - if constants.EnvData.COOKIE_NAME == "" { - constants.EnvData.COOKIE_NAME = "authorizer" - } + if envData[constants.EnvKeyGithubClientSecret] == nil || envData[constants.EnvKeyGithubClientSecret].(string) == "" { + envData[constants.EnvKeyGithubClientSecret] = os.Getenv("GITHUB_CLIENT_SECRET") } - if constants.EnvData.GOOGLE_CLIENT_ID == "" { - constants.EnvData.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID") + if envData[constants.EnvKeyFacebookClientID] == nil || envData[constants.EnvKeyFacebookClientID].(string) == "" { + envData[constants.EnvKeyFacebookClientID] = os.Getenv("FACEBOOK_CLIENT_ID") } - if constants.EnvData.GOOGLE_CLIENT_SECRET == "" { - constants.EnvData.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET") + if envData[constants.EnvKeyFacebookClientSecret] == nil || envData[constants.EnvKeyFacebookClientSecret].(string) == "" { + envData[constants.EnvKeyFacebookClientSecret] = os.Getenv("FACEBOOK_CLIENT_SECRET") } - if constants.EnvData.GITHUB_CLIENT_ID == "" { - constants.EnvData.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID") + if envData[constants.EnvKeyResetPasswordURL] == nil || envData[constants.EnvKeyResetPasswordURL].(string) == "" { + envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") } - if constants.EnvData.GITHUB_CLIENT_SECRET == "" { - constants.EnvData.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET") + envData[constants.EnvKeyDisableBasicAuthentication] = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true" + envData[constants.EnvKeyDisableEmailVerification] = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true" + envData[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true" + envData[constants.EnvKeyDisableLoginPage] = os.Getenv("DISABLE_LOGIN_PAGE") == "true" + + // no need to add nil check as its already done above + if envData[constants.EnvKeySmtpHost].(string) == "" || envData[constants.EnvKeySmtpUsername].(string) == "" || envData[constants.EnvKeySmtpPassword].(string) == "" || envData[constants.EnvKeySenderEmail].(string) == "" { + envData[constants.EnvKeyDisableEmailVerification] = true + envData[constants.EnvKeyDisableMagicLinkLogin] = true } - if constants.EnvData.FACEBOOK_CLIENT_ID == "" { - constants.EnvData.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID") - } - - if constants.EnvData.FACEBOOK_CLIENT_SECRET == "" { - constants.EnvData.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET") - } - - if constants.EnvData.RESET_PASSWORD_URL == "" { - constants.EnvData.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") - } - - constants.EnvData.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true" - constants.EnvData.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true" - constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true" - constants.EnvData.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true" - - if constants.EnvData.SMTP_HOST == "" || constants.EnvData.SMTP_USERNAME == "" || constants.EnvData.SMTP_PASSWORD == "" || constants.EnvData.SENDER_EMAIL == "" { - constants.EnvData.DISABLE_EMAIL_VERIFICATION = true - constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true + if envData[constants.EnvKeyDisableEmailVerification].(bool) { + envData[constants.EnvKeyDisableMagicLinkLogin] = true } allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",") @@ -216,11 +225,7 @@ func InitEnv() { allowedOrigins = []string{"*"} } - constants.EnvData.ALLOWED_ORIGINS = allowedOrigins - - if constants.EnvData.DISABLE_EMAIL_VERIFICATION { - constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true - } + envData[constants.EnvKeyAllowedOrigins] = allowedOrigins rolesEnv := strings.TrimSpace(os.Getenv("ROLES")) rolesSplit := strings.Split(rolesEnv, ",") @@ -263,15 +268,17 @@ func InitEnv() { panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) } - constants.EnvData.ROLES = roles - constants.EnvData.DEFAULT_ROLES = defaultRoles - constants.EnvData.PROTECTED_ROLES = protectedRoles + envData[constants.EnvKeyRoles] = roles + envData[constants.EnvKeyDefaultRoles] = defaultRoles + envData[constants.EnvKeyProtectedRoles] = protectedRoles if os.Getenv("ORGANIZATION_NAME") != "" { - constants.EnvData.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME") + envData[constants.EnvKeyOrganizationName] = os.Getenv("ORGANIZATION_NAME") } if os.Getenv("ORGANIZATION_LOGO") != "" { - constants.EnvData.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO") + envData[constants.EnvKeyOrganizationLogo] = os.Getenv("ORGANIZATION_LOGO") } + + envstore.EnvInMemoryStoreObj.UpdateEnvStore(envData) } diff --git a/server/env/persist_env.go b/server/env/persist_env.go index 23c3560..fda443f 100644 --- a/server/env/persist_env.go +++ b/server/env/persist_env.go @@ -9,20 +9,22 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/google/uuid" ) +// PersistEnv persists the environment variables to the database func PersistEnv() error { config, err := db.Mgr.GetConfig() // config not found in db if err != nil { // AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid hash := uuid.New().String()[:36-4] - constants.EnvData.ENCRYPTION_KEY = hash + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyEncryptionKey, hash) encodedHash := utils.EncryptB64(hash) - configData, err := json.Marshal(constants.EnvData) + configData, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) if err != nil { return err } @@ -45,7 +47,8 @@ func PersistEnv() error { if err != nil { return err } - constants.EnvData.ENCRYPTION_KEY = decryptedEncryptionKey + + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyEncryptionKey, decryptedEncryptionKey) decryptedConfigs, err := utils.DecryptAES(config.Config) if err != nil { return err @@ -119,7 +122,7 @@ func PersistEnv() error { } if hasChanged { - encryptedConfig, err := utils.EncryptConfig(jsonData) + encryptedConfig, err := utils.EncryptEnvData(jsonData) if err != nil { return err } diff --git a/server/envstore/store.go b/server/envstore/store.go new file mode 100644 index 0000000..c676c2b --- /dev/null +++ b/server/envstore/store.go @@ -0,0 +1,65 @@ +package envstore + +import ( + "sync" + + "github.com/authorizerdev/authorizer/server/constants" +) + +// EnvInMemoryStore struct +type EnvInMemoryStore struct { + mutex sync.Mutex + store map[string]interface{} +} + +// EnvInMemoryStoreObj global variable for EnvInMemoryStore +var EnvInMemoryStoreObj = &EnvInMemoryStore{ + store: map[string]interface{}{ + constants.EnvKeyAdminCookieName: "authorizer-admin", + constants.EnvKeyJwtRoleClaim: "role", + constants.EnvKeyOrganizationName: "Authorizer", + constants.EnvKeyOrganizationLogo: "https://www.authorizer.io/images/logo.png", + constants.EnvKeyDisableBasicAuthentication: false, + constants.EnvKeyDisableMagicLinkLogin: false, + constants.EnvKeyDisableEmailVerification: false, + constants.EnvKeyDisableLoginPage: false, + }, +} + +// UpdateEnvStore to update the whole env store object +func (e *EnvInMemoryStore) UpdateEnvStore(data map[string]interface{}) { + e.mutex.Lock() + defer e.mutex.Unlock() + // just override the keys + new keys + for key, value := range data { + e.store[key] = value + } +} + +// UpdateEnvVariable to update the particular env variable +func (e *EnvInMemoryStore) UpdateEnvVariable(key string, value interface{}) map[string]interface{} { + e.mutex.Lock() + defer e.mutex.Unlock() + e.store[key] = value + return e.store +} + +// GetEnvStore to get the env variable from env store object +func (e *EnvInMemoryStore) GetEnvVariable(key string) interface{} { + // e.mutex.Lock() + // defer e.mutex.Unlock() + return e.store[key] +} + +// GetEnvStoreClone to get clone of current env store object +func (e *EnvInMemoryStore) GetEnvStoreClone() map[string]interface{} { + e.mutex.Lock() + defer e.mutex.Unlock() + + result := make(map[string]interface{}) + for key, value := range e.store { + result[key] = value + } + + return result +} diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 3a336d9..d501a01 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -1182,6 +1182,7 @@ input UpdateProfileInput { input UpdateUserInput { id: ID! email: String + email_verified: Boolean given_name: String family_name: String middle_name: String @@ -6646,6 +6647,14 @@ func (ec *executionContext) unmarshalInputUpdateUserInput(ctx context.Context, o if err != nil { return it, err } + case "email_verified": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email_verified")) + it.EmailVerified, err = ec.unmarshalOBoolean2áš–bool(ctx, v) + if err != nil { + return it, err + } case "given_name": var err error diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index acde6b3..1b2a722 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -168,17 +168,18 @@ type UpdateProfileInput struct { } type UpdateUserInput struct { - ID string `json:"id"` - Email *string `json:"email"` - GivenName *string `json:"given_name"` - FamilyName *string `json:"family_name"` - MiddleName *string `json:"middle_name"` - Nickname *string `json:"nickname"` - Gender *string `json:"gender"` - Birthdate *string `json:"birthdate"` - PhoneNumber *string `json:"phone_number"` - Picture *string `json:"picture"` - Roles []*string `json:"roles"` + ID string `json:"id"` + Email *string `json:"email"` + EmailVerified *bool `json:"email_verified"` + GivenName *string `json:"given_name"` + FamilyName *string `json:"family_name"` + MiddleName *string `json:"middle_name"` + Nickname *string `json:"nickname"` + Gender *string `json:"gender"` + Birthdate *string `json:"birthdate"` + PhoneNumber *string `json:"phone_number"` + Picture *string `json:"picture"` + Roles []*string `json:"roles"` } type User struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 741e9dd..6c0b65d 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -189,6 +189,7 @@ input UpdateProfileInput { input UpdateUserInput { id: ID! email: String + email_verified: Boolean given_name: String family_name: String middle_name: String diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 52aa7c4..94e2b1a 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -12,47 +12,47 @@ import ( ) func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error) { - return resolvers.Signup(ctx, params) + return resolvers.SignupResolver(ctx, params) } func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) { - return resolvers.Login(ctx, params) + return resolvers.LoginResolver(ctx, params) } func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) { - return resolvers.MagicLinkLogin(ctx, params) + return resolvers.MagicLinkLoginResolver(ctx, params) } func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) { - return resolvers.Logout(ctx) + return resolvers.LogoutResolver(ctx) } func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) { - return resolvers.UpdateProfile(ctx, params) + return resolvers.UpdateProfileResolver(ctx, params) } func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) { - return resolvers.VerifyEmail(ctx, params) + return resolvers.VerifyEmailResolver(ctx, params) } func (r *mutationResolver) ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) { - return resolvers.ResendVerifyEmail(ctx, params) + return resolvers.ResendVerifyEmailResolver(ctx, params) } func (r *mutationResolver) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) { - return resolvers.ForgotPassword(ctx, params) + return resolvers.ForgotPasswordResolver(ctx, params) } func (r *mutationResolver) ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { - return resolvers.ResetPassword(ctx, params) + return resolvers.ResetPasswordResolver(ctx, params) } func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { - return resolvers.DeleteUser(ctx, params) + return resolvers.DeleteUserResolver(ctx, params) } func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) { - return resolvers.UpdateUser(ctx, params) + return resolvers.UpdateUserResolver(ctx, params) } func (r *mutationResolver) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) { @@ -64,7 +64,7 @@ func (r *mutationResolver) AdminLogin(ctx context.Context, params model.AdminLog } func (r *mutationResolver) AdminLogout(ctx context.Context) (*model.Response, error) { - return resolvers.AdminLogout(ctx) + return resolvers.AdminLogoutResolver(ctx) } func (r *mutationResolver) UpdateConfig(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) { @@ -72,27 +72,27 @@ func (r *mutationResolver) UpdateConfig(ctx context.Context, params model.Update } func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) { - return resolvers.Meta(ctx) + return resolvers.MetaResolver(ctx) } func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { - return resolvers.Session(ctx, roles) + return resolvers.SessionResolver(ctx, roles) } func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) { - return resolvers.Profile(ctx) + return resolvers.ProfileResolver(ctx) } func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) { - return resolvers.Users(ctx) + return resolvers.UsersResolver(ctx) } func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) { - return resolvers.VerificationRequests(ctx) + return resolvers.VerificationRequestsResolver(ctx) } func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) { - return resolvers.AdminSession(ctx) + return resolvers.AdminSessionResolver(ctx) } func (r *queryResolver) Config(ctx context.Context) (*model.Config, error) { @@ -105,5 +105,7 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -type mutationResolver struct{ *Resolver } -type queryResolver struct{ *Resolver } +type ( + mutationResolver struct{ *Resolver } + queryResolver struct{ *Resolver } +) diff --git a/server/handlers/app.go b/server/handlers/app.go index e2569e4..4ad5a49 100644 --- a/server/handlers/app.go +++ b/server/handlers/app.go @@ -7,15 +7,19 @@ import ( "strings" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" ) +// State is the struct that holds authorizer url and redirect url +// They are provided via query string in the request type State struct { AuthorizerURL string `json:"authorizerURL"` RedirectURL string `json:"redirectURL"` } +// AppHandler is the handler for the /app route func AppHandler() gin.HandlerFunc { return func(c *gin.Context) { state := c.Query("state") @@ -23,14 +27,8 @@ func AppHandler() gin.HandlerFunc { var stateObj State if state == "" { - // cookie, err := utils.GetAuthToken(c) - // if err != nil { - // c.JSON(400, gin.H{"error": "invalid state"}) - // return - // } - - stateObj.AuthorizerURL = constants.EnvData.AUTHORIZER_URL - stateObj.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/app" + stateObj.AuthorizerURL = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + stateObj.RedirectURL = stateObj.AuthorizerURL + "/app" } else { decodedState, err := utils.DecryptB64(state) @@ -59,7 +57,7 @@ func AppHandler() gin.HandlerFunc { } // validate host and domain of authorizer url - if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.EnvData.AUTHORIZER_URL { + if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) { c.JSON(400, gin.H{"error": "invalid host url"}) return } @@ -76,8 +74,8 @@ func AppHandler() gin.HandlerFunc { "data": map[string]string{ "authorizerURL": stateObj.AuthorizerURL, "redirectURL": stateObj.RedirectURL, - "organizationName": constants.EnvData.ORGANIZATION_NAME, - "organizationLogo": constants.EnvData.ORGANIZATION_LOGO, + "organizationName": envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyOrganizationName).(string), + "organizationLogo": envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyOrganizationLogo).(string), }, }) } diff --git a/server/handlers/dashboard.go b/server/handlers/dashboard.go index f16e442..002b30d 100644 --- a/server/handlers/dashboard.go +++ b/server/handlers/dashboard.go @@ -4,14 +4,16 @@ import ( "net/http" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/gin-gonic/gin" ) +// DashboardHandler is the handler for the /dashboard route func DashboardHandler() gin.HandlerFunc { return func(c *gin.Context) { isOnboardingCompleted := false - if constants.EnvData.ADMIN_SECRET != "" { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret) != nil && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string) != "" { isOnboardingCompleted = true } diff --git a/server/handlers/graphql.go b/server/handlers/graphql.go index e55323f..367a956 100644 --- a/server/handlers/graphql.go +++ b/server/handlers/graphql.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" ) -// Defining the Graphql handler +// GraphqlHandler is the main handler that handels all the graphql requests func GraphqlHandler() gin.HandlerFunc { // NewExecutableSchema and Config are in the generated.go file // Resolver is in the resolver.go file diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index a6c922a..aac9d25 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -12,7 +12,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" @@ -21,6 +21,131 @@ import ( "golang.org/x/oauth2" ) +// OAuthCallbackHandler handles the OAuth callback for various oauth providers +func OAuthCallbackHandler() gin.HandlerFunc { + return func(c *gin.Context) { + provider := c.Param("oauth_provider") + state := c.Request.FormValue("state") + + sessionState := session.GetSocailLoginState(state) + if sessionState == "" { + c.JSON(400, gin.H{"error": "invalid oauth state"}) + } + session.RemoveSocialLoginState(state) + // contains random token, redirect url, role + sessionSplit := strings.Split(state, "___") + + // TODO validate redirect url + if len(sessionSplit) < 2 { + c.JSON(400, gin.H{"error": "invalid redirect url"}) + return + } + + inputRoles := strings.Split(sessionSplit[2], ",") + redirectURL := sessionSplit[1] + + var err error + user := db.User{} + code := c.Request.FormValue("code") + switch provider { + case constants.SignupMethodGoogle: + user, err = processGoogleUserInfo(code) + case constants.SignupMethodGithub: + user, err = processGithubUserInfo(code) + case constants.SignupMethodFacebook: + user, err = processFacebookUserInfo(code) + default: + err = fmt.Errorf(`invalid oauth provider`) + } + + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + existingUser, err := db.Mgr.GetUserByEmail(user.Email) + + if err != nil { + // user not registered, register user and generate session token + user.SignupMethods = provider + // make sure inputRoles don't include protected roles + hasProtectedRole := false + for _, ir := range inputRoles { + if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyProtectedRoles).([]string), ir) { + hasProtectedRole = true + } + } + + if hasProtectedRole { + c.JSON(400, gin.H{"error": "invalid role"}) + return + } + + user.Roles = strings.Join(inputRoles, ",") + now := time.Now().Unix() + user.EmailVerifiedAt = &now + user, _ = db.Mgr.AddUser(user) + } else { + // user exists in db, check if method was google + // if not append google to existing signup method and save it + + signupMethod := existingUser.SignupMethods + if !strings.Contains(signupMethod, provider) { + signupMethod = signupMethod + "," + provider + } + user.SignupMethods = signupMethod + user.Password = existingUser.Password + + // There multiple scenarios with roles here in social login + // 1. user has access to protected roles + roles and trying to login + // 2. user has not signed up for one of the available role but trying to signup. + // Need to modify roles in this case + + // find the unassigned roles + existingRoles := strings.Split(existingUser.Roles, ",") + unasignedRoles := []string{} + for _, ir := range inputRoles { + if !utils.StringSliceContains(existingRoles, ir) { + unasignedRoles = append(unasignedRoles, ir) + } + } + + if len(unasignedRoles) > 0 { + // check if it contains protected unassigned role + hasProtectedRole := false + for _, ur := range unasignedRoles { + if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyProtectedRoles).([]string), ur) { + hasProtectedRole = true + } + } + + if hasProtectedRole { + c.JSON(400, gin.H{"error": "invalid role"}) + return + } else { + user.Roles = existingUser.Roles + "," + strings.Join(unasignedRoles, ",") + } + } else { + user.Roles = existingUser.Roles + } + user.Key = existingUser.Key + user.ID = existingUser.ID + user, err = db.Mgr.UpdateUser(user) + } + + user, _ = db.Mgr.GetUserByEmail(user.Email) + userIdStr := fmt.Sprintf("%v", user.ID) + refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, inputRoles) + + accessToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, inputRoles) + utils.SetCookie(c, accessToken) + session.SetUserSession(userIdStr, accessToken, refreshToken) + utils.SaveSessionInDB(user.ID, c) + + c.Redirect(http.StatusTemporaryRedirect, redirectURL) + } +} + func processGoogleUserInfo(code string) (db.User, error) { user := db.User{} ctx := context.Background() @@ -145,127 +270,3 @@ func processFacebookUserInfo(code string) (db.User, error) { return user, nil } - -func OAuthCallbackHandler() gin.HandlerFunc { - return func(c *gin.Context) { - provider := c.Param("oauth_provider") - state := c.Request.FormValue("state") - - sessionState := session.GetSocailLoginState(state) - if sessionState == "" { - c.JSON(400, gin.H{"error": "invalid oauth state"}) - } - session.RemoveSocialLoginState(state) - // contains random token, redirect url, role - sessionSplit := strings.Split(state, "___") - - // TODO validate redirect url - if len(sessionSplit) < 2 { - c.JSON(400, gin.H{"error": "invalid redirect url"}) - return - } - - inputRoles := strings.Split(sessionSplit[2], ",") - redirectURL := sessionSplit[1] - - var err error - user := db.User{} - code := c.Request.FormValue("code") - switch provider { - case enum.Google.String(): - user, err = processGoogleUserInfo(code) - case enum.Github.String(): - user, err = processGithubUserInfo(code) - case enum.Facebook.String(): - user, err = processFacebookUserInfo(code) - default: - err = fmt.Errorf(`invalid oauth provider`) - } - - if err != nil { - c.JSON(400, gin.H{"error": err.Error()}) - return - } - - existingUser, err := db.Mgr.GetUserByEmail(user.Email) - - if err != nil { - // user not registered, register user and generate session token - user.SignupMethods = provider - // make sure inputRoles don't include protected roles - hasProtectedRole := false - for _, ir := range inputRoles { - if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ir) { - hasProtectedRole = true - } - } - - if hasProtectedRole { - c.JSON(400, gin.H{"error": "invalid role"}) - return - } - - user.Roles = strings.Join(inputRoles, ",") - now := time.Now().Unix() - user.EmailVerifiedAt = &now - user, _ = db.Mgr.AddUser(user) - } else { - // user exists in db, check if method was google - // if not append google to existing signup method and save it - - signupMethod := existingUser.SignupMethods - if !strings.Contains(signupMethod, provider) { - signupMethod = signupMethod + "," + provider - } - user.SignupMethods = signupMethod - user.Password = existingUser.Password - - // There multiple scenarios with roles here in social login - // 1. user has access to protected roles + roles and trying to login - // 2. user has not signed up for one of the available role but trying to signup. - // Need to modify roles in this case - - // find the unassigned roles - existingRoles := strings.Split(existingUser.Roles, ",") - unasignedRoles := []string{} - for _, ir := range inputRoles { - if !utils.StringSliceContains(existingRoles, ir) { - unasignedRoles = append(unasignedRoles, ir) - } - } - - if len(unasignedRoles) > 0 { - // check if it contains protected unassigned role - hasProtectedRole := false - for _, ur := range unasignedRoles { - if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) { - hasProtectedRole = true - } - } - - if hasProtectedRole { - c.JSON(400, gin.H{"error": "invalid role"}) - return - } else { - user.Roles = existingUser.Roles + "," + strings.Join(unasignedRoles, ",") - } - } else { - user.Roles = existingUser.Roles - } - user.Key = existingUser.Key - user.ID = existingUser.ID - user, err = db.Mgr.UpdateUser(user) - } - - user, _ = db.Mgr.GetUserByEmail(user.Email) - userIdStr := fmt.Sprintf("%v", user.ID) - refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, inputRoles) - - accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, inputRoles) - utils.SetCookie(c, accessToken) - session.SetToken(userIdStr, accessToken, refreshToken) - utils.CreateSession(user.ID, c) - - c.Redirect(http.StatusTemporaryRedirect, redirectURL) - } -} diff --git a/server/handlers/oauth_login.go b/server/handlers/oauth_login.go index 0363f70..733a60b 100644 --- a/server/handlers/oauth_login.go +++ b/server/handlers/oauth_login.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" @@ -13,8 +13,7 @@ import ( "github.com/google/uuid" ) -// set host in the oauth state that is useful for redirecting - +// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback func OAuthLoginHandler() gin.HandlerFunc { return func(c *gin.Context) { // TODO validate redirect URL @@ -34,14 +33,14 @@ func OAuthLoginHandler() gin.HandlerFunc { // use protected roles verification for admin login only. // though if not associated with user, it will be rejected from oauth_callback - if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), rolesSplit) { + if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyRoles).([]string), envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyProtectedRoles).([]string)...)...), rolesSplit) { c.JSON(400, gin.H{ "error": "invalid role", }) return } } else { - roles = strings.Join(constants.EnvData.DEFAULT_ROLES, ",") + roles = strings.Join(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDefaultRoles).([]string), ",") } uuid := uuid.New() @@ -50,32 +49,32 @@ func OAuthLoginHandler() gin.HandlerFunc { provider := c.Param("oauth_provider") isProviderConfigured := true switch provider { - case enum.Google.String(): + case constants.SignupMethodGoogle: if oauth.OAuthProviders.GoogleConfig == nil { isProviderConfigured = false break } - session.SetSocailLoginState(oauthStateString, enum.Google.String()) + session.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle) // during the init of OAuthProvider authorizer url might be empty - oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google" + oauth.OAuthProviders.GoogleConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + "/oauth_callback/google" url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) - case enum.Github.String(): + case constants.SignupMethodGithub: if oauth.OAuthProviders.GithubConfig == nil { isProviderConfigured = false break } - session.SetSocailLoginState(oauthStateString, enum.Github.String()) - oauth.OAuthProviders.GithubConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github" + session.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub) + oauth.OAuthProviders.GithubConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + "/oauth_callback/github" url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) - case enum.Facebook.String(): + case constants.SignupMethodFacebook: if oauth.OAuthProviders.FacebookConfig == nil { isProviderConfigured = false break } - session.SetSocailLoginState(oauthStateString, enum.Facebook.String()) - oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook" + session.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook) + oauth.OAuthProviders.FacebookConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + "/oauth_callback/facebook" url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) default: diff --git a/server/handlers/playground.go b/server/handlers/playground.go index 00f3990..71eb77a 100644 --- a/server/handlers/playground.go +++ b/server/handlers/playground.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" ) +// PlaygroundHandler is the handler for the /playground route func PlaygroundHandler() gin.HandlerFunc { h := playground.Handler("GraphQL", "/graphql") diff --git a/server/handlers/root.go b/server/handlers/root.go new file mode 100644 index 0000000..70cfc30 --- /dev/null +++ b/server/handlers/root.go @@ -0,0 +1,14 @@ +package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// RootHandler is the handler for / root route. +func RootHandler() gin.HandlerFunc { + return func(c *gin.Context) { + c.Redirect(http.StatusTemporaryRedirect, "/dashboard") + } +} diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go index b8eabf5..3a80541 100644 --- a/server/handlers/verify_email.go +++ b/server/handlers/verify_email.go @@ -5,13 +5,15 @@ import ( "strings" "time" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" ) +// VerifyEmailHandler handles the verify email route. +// It verifies email based on JWT token in query string func VerifyEmailHandler() gin.HandlerFunc { return func(c *gin.Context) { errorRes := gin.H{ @@ -54,12 +56,12 @@ func VerifyEmailHandler() gin.HandlerFunc { db.Mgr.DeleteVerificationRequest(verificationRequest) roles := strings.Split(user.Roles, ",") - refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles) + refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles) - accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) + accessToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles) - session.SetToken(user.ID, accessToken, refreshToken) - utils.CreateSession(user.ID, c) + session.SetUserSession(user.ID, accessToken, refreshToken) + utils.SaveSessionInDB(user.ID, c) utils.SetCookie(c, accessToken) c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL) } diff --git a/server/main.go b/server/main.go index 7e7c88e..6b4c1bd 100644 --- a/server/main.go +++ b/server/main.go @@ -6,11 +6,10 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/env" - "github.com/authorizerdev/authorizer/server/handlers" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/oauth" - "github.com/authorizerdev/authorizer/server/router" + "github.com/authorizerdev/authorizer/server/routes" "github.com/authorizerdev/authorizer/server/session" - "github.com/authorizerdev/authorizer/server/utils" ) var VERSION string @@ -18,11 +17,10 @@ var VERSION string func main() { env.ARG_DB_URL = flag.String("database_url", "", "Database connection string") env.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite") - env.ARG_AUTHORIZER_URL = flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com") env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path") flag.Parse() - constants.EnvData.VERSION = VERSION + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyVersion, VERSION) env.InitEnv() db.InitDB() @@ -30,27 +28,8 @@ func main() { session.InitSession() oauth.InitOAuth() - utils.InitServer() - router := router.InitRouter() + router := routes.InitRouter() - router.LoadHTMLGlob("templates/*") - // login page app related routes. - // if we put them in router file then tests would fail as templates or build path will be different - if !constants.EnvData.DISABLE_LOGIN_PAGE { - app := router.Group("/app") - { - app.Static("/build", "app/build") - app.GET("/", handlers.AppHandler()) - app.GET("/reset-password", handlers.AppHandler()) - } - } - - app := router.Group("/dashboard") - { - app.Static("/build", "dashboard/build") - app.GET("/", handlers.DashboardHandler()) - } - - router.Run(":" + constants.EnvData.PORT) + router.Run(":" + envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyPort).(string)) } diff --git a/server/middlewares/context.go b/server/middlewares/context.go index fd205ac..86bf34a 100644 --- a/server/middlewares/context.go +++ b/server/middlewares/context.go @@ -2,19 +2,19 @@ package middlewares import ( "context" - "log" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/gin-contrib/location" "github.com/gin-gonic/gin" ) +// GinContextToContextMiddleware is a middleware to add gin context in context func GinContextToContextMiddleware() gin.HandlerFunc { return func(c *gin.Context) { - if constants.EnvData.AUTHORIZER_URL == "" { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) == "" { url := location.Get(c) - constants.EnvData.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host - log.Println("authorizer url:", constants.EnvData.AUTHORIZER_URL) + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyAuthorizerURL, url.Scheme+"://"+c.Request.Host) } ctx := context.WithValue(c.Request.Context(), "GinContextKey", c) c.Request = c.Request.WithContext(ctx) diff --git a/server/middlewares/cors.go b/server/middlewares/cors.go index f093e8a..0b85a62 100644 --- a/server/middlewares/cors.go +++ b/server/middlewares/cors.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" ) +// CORSMiddleware is a middleware to add cors headers func CORSMiddleware() gin.HandlerFunc { return func(c *gin.Context) { origin := c.Request.Header.Get("Origin") diff --git a/server/oauth/oauth.go b/server/oauth/oauth.go index 5c6d943..d5ac082 100644 --- a/server/oauth/oauth.go +++ b/server/oauth/oauth.go @@ -5,56 +5,62 @@ import ( "log" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" facebookOAuth2 "golang.org/x/oauth2/facebook" githubOAuth2 "golang.org/x/oauth2/github" ) +// OAuthProviders is a struct that contains reference all the OAuth providers type OAuthProvider struct { GoogleConfig *oauth2.Config GithubConfig *oauth2.Config FacebookConfig *oauth2.Config } +// OIDCProviders is a struct that contains reference all the OpenID providers type OIDCProvider struct { GoogleOIDC *oidc.Provider } var ( + // OAuthProviders is a global variable that contains instance for all enabled the OAuth providers OAuthProviders OAuthProvider - OIDCProviders OIDCProvider + // OIDCProviders is a global variable that contains instance for all enabled the OpenID providers + OIDCProviders OIDCProvider ) +// InitOAuth initializes the OAuth providers based on EnvData func InitOAuth() { ctx := context.Background() - if constants.EnvData.GOOGLE_CLIENT_ID != "" && constants.EnvData.GOOGLE_CLIENT_SECRET != "" { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGoogleClientID).(string) != "" && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGoogleClientSecret).(string) != "" { p, err := oidc.NewProvider(ctx, "https://accounts.google.com") if err != nil { log.Fatalln("error creating oidc provider for google:", err) } OIDCProviders.GoogleOIDC = p OAuthProviders.GoogleConfig = &oauth2.Config{ - ClientID: constants.EnvData.GOOGLE_CLIENT_ID, - ClientSecret: constants.EnvData.GOOGLE_CLIENT_SECRET, - RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google", + ClientID: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGoogleClientID).(string), + ClientSecret: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGoogleClientSecret).(string), + RedirectURL: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + "/oauth_callback/google", Endpoint: OIDCProviders.GoogleOIDC.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } } - if constants.EnvData.GITHUB_CLIENT_ID != "" && constants.EnvData.GITHUB_CLIENT_SECRET != "" { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGithubClientID).(string) != "" && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGithubClientSecret).(string) != "" { OAuthProviders.GithubConfig = &oauth2.Config{ - ClientID: constants.EnvData.GITHUB_CLIENT_ID, - ClientSecret: constants.EnvData.GITHUB_CLIENT_SECRET, - RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github", + ClientID: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGithubClientID).(string), + ClientSecret: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGithubClientSecret).(string), + RedirectURL: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + "/oauth_callback/github", Endpoint: githubOAuth2.Endpoint, } } - if constants.EnvData.FACEBOOK_CLIENT_ID != "" && constants.EnvData.FACEBOOK_CLIENT_SECRET != "" { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyFacebookClientID).(string) != "" && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGoogleClientID).(string) != "" { OAuthProviders.FacebookConfig = &oauth2.Config{ - ClientID: constants.EnvData.FACEBOOK_CLIENT_ID, - ClientSecret: constants.EnvData.FACEBOOK_CLIENT_SECRET, - RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook", + ClientID: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyFacebookClientID).(string), + ClientSecret: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyFacebookClientSecret).(string), + RedirectURL: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string) + "/oauth_callback/facebook", Endpoint: facebookOAuth2.Endpoint, Scopes: []string{"public_profile", "email"}, } diff --git a/server/resolvers/admin_login.go b/server/resolvers/admin_login.go index e829454..df66226 100644 --- a/server/resolvers/admin_login.go +++ b/server/resolvers/admin_login.go @@ -5,10 +5,12 @@ import ( "fmt" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) +// AdminLoginResolver is a resolver for admin login mutation func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response @@ -17,11 +19,12 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod return res, err } - if params.AdminSecret != constants.EnvData.ADMIN_SECRET { + adminSecret := envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string) + if params.AdminSecret != adminSecret { return res, fmt.Errorf(`invalid admin secret`) } - hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + hashedKey, err := utils.EncryptPassword(adminSecret) if err != nil { return res, err } diff --git a/server/resolvers/admin_logout.go b/server/resolvers/admin_logout.go index d6bb2f8..e4f38e5 100644 --- a/server/resolvers/admin_logout.go +++ b/server/resolvers/admin_logout.go @@ -8,7 +8,8 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -func AdminLogout(ctx context.Context) (*model.Response, error) { +// AdminLogoutResolver is a resolver for admin logout mutation +func AdminLogoutResolver(ctx context.Context) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response diff --git a/server/resolvers/admin_session.go b/server/resolvers/admin_session.go index 50a5ab6..25a7c28 100644 --- a/server/resolvers/admin_session.go +++ b/server/resolvers/admin_session.go @@ -5,11 +5,13 @@ import ( "fmt" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) -func AdminSession(ctx context.Context) (*model.Response, error) { +// AdminSessionResolver is a resolver for admin session query +func AdminSessionResolver(ctx context.Context) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response @@ -21,7 +23,7 @@ func AdminSession(ctx context.Context) (*model.Response, error) { return res, fmt.Errorf("unauthorized") } - hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) if err != nil { return res, err } diff --git a/server/resolvers/admin_signup.go b/server/resolvers/admin_signup.go index 2a943de..fd35fc7 100644 --- a/server/resolvers/admin_signup.go +++ b/server/resolvers/admin_signup.go @@ -8,10 +8,12 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) +// AdminSignupResolver is a resolver for admin signup mutation func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response @@ -30,17 +32,18 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m return res, err } - if constants.EnvData.ADMIN_SECRET != "" { + adminSecret := envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string) + + if adminSecret != "" { err = fmt.Errorf("admin sign up already completed") return res, err } - constants.EnvData.ADMIN_SECRET = params.AdminSecret - + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyAdminSecret, params.AdminSecret) // consvert EnvData to JSON var jsonData map[string]interface{} - jsonBytes, err := json.Marshal(constants.EnvData) + jsonBytes, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) if err != nil { return res, err } @@ -54,7 +57,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m return res, err } - configData, err := utils.EncryptConfig(jsonData) + configData, err := utils.EncryptEnvData(jsonData) if err != nil { return res, err } @@ -64,7 +67,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m return res, err } - hashedKey, err := utils.HashPassword(params.AdminSecret) + hashedKey, err := utils.EncryptPassword(params.AdminSecret) if err != nil { return res, err } diff --git a/server/resolvers/config.go b/server/resolvers/config.go index 10bdd64..5d663d7 100644 --- a/server/resolvers/config.go +++ b/server/resolvers/config.go @@ -5,10 +5,15 @@ import ( "fmt" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) +// TODO rename to env_data + +// ConfigResolver is a resolver for config query +// This is admin only query func ConfigResolver(ctx context.Context) (*model.Config, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Config @@ -21,40 +26,76 @@ func ConfigResolver(ctx context.Context) (*model.Config, error) { return res, fmt.Errorf("unauthorized") } + // get clone of store + store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + adminSecret := store[constants.EnvKeyAdminSecret].(string) + databaseType := store[constants.EnvKeyDatabaseType].(string) + databaseURL := store[constants.EnvKeyDatabaseURL].(string) + databaseName := store[constants.EnvKeyDatabaseName].(string) + smtpHost := store[constants.EnvKeySmtpHost].(string) + smtpPort := store[constants.EnvKeySmtpPort].(string) + smtpUsername := store[constants.EnvKeySmtpUsername].(string) + smtpPassword := store[constants.EnvKeySmtpPassword].(string) + senderEmail := store[constants.EnvKeySenderEmail].(string) + jwtType := store[constants.EnvKeyJwtType].(string) + jwtSecret := store[constants.EnvKeyJwtSecret].(string) + jwtRoleClaim := store[constants.EnvKeyJwtRoleClaim].(string) + allowedOrigins := store[constants.EnvKeyAllowedOrigins].([]string) + authorizerURL := store[constants.EnvKeyAuthorizerURL].(string) + appURL := store[constants.EnvKeyAppURL].(string) + redisURL := store[constants.EnvKeyRedisURL].(string) + cookieName := store[constants.EnvKeyCookieName].(string) + resetPasswordURL := store[constants.EnvKeyResetPasswordURL].(string) + disableEmailVerification := store[constants.EnvKeyDisableEmailVerification].(bool) + disableBasicAuthentication := store[constants.EnvKeyDisableBasicAuthentication].(bool) + disableMagicLinkLogin := store[constants.EnvKeyDisableMagicLinkLogin].(bool) + disableLoginPage := store[constants.EnvKeyDisableLoginPage].(bool) + roles := store[constants.EnvKeyRoles].([]string) + defaultRoles := store[constants.EnvKeyDefaultRoles].([]string) + protectedRoles := store[constants.EnvKeyProtectedRoles].([]string) + googleClientID := store[constants.EnvKeyGoogleClientID].(string) + googleClientSecret := store[constants.EnvKeyGoogleClientSecret].(string) + facebookClientID := store[constants.EnvKeyFacebookClientID].(string) + facebookClientSecret := store[constants.EnvKeyFacebookClientSecret].(string) + githubClientID := store[constants.EnvKeyGithubClientID].(string) + githubClientSecret := store[constants.EnvKeyGithubClientSecret].(string) + organizationName := store[constants.EnvKeyOrganizationName].(string) + organizationLogo := store[constants.EnvKeyOrganizationLogo].(string) + res = &model.Config{ - AdminSecret: &constants.EnvData.ADMIN_SECRET, - DatabaseType: &constants.EnvData.DATABASE_TYPE, - DatabaseURL: &constants.EnvData.DATABASE_URL, - DatabaseName: &constants.EnvData.DATABASE_NAME, - SMTPHost: &constants.EnvData.SMTP_HOST, - SMTPPort: &constants.EnvData.SMTP_PORT, - SMTPPassword: &constants.EnvData.SMTP_PASSWORD, - SMTPUsername: &constants.EnvData.SMTP_USERNAME, - SenderEmail: &constants.EnvData.SENDER_EMAIL, - JwtType: &constants.EnvData.JWT_TYPE, - JwtSecret: &constants.EnvData.JWT_SECRET, - AllowedOrigins: constants.EnvData.ALLOWED_ORIGINS, - AuthorizerURL: &constants.EnvData.AUTHORIZER_URL, - AppURL: &constants.EnvData.APP_URL, - RedisURL: &constants.EnvData.REDIS_URL, - CookieName: &constants.EnvData.COOKIE_NAME, - ResetPasswordURL: &constants.EnvData.RESET_PASSWORD_URL, - DisableEmailVerification: &constants.EnvData.DISABLE_EMAIL_VERIFICATION, - DisableBasicAuthentication: &constants.EnvData.DISABLE_BASIC_AUTHENTICATION, - DisableMagicLinkLogin: &constants.EnvData.DISABLE_MAGIC_LINK_LOGIN, - DisableLoginPage: &constants.EnvData.DISABLE_LOGIN_PAGE, - Roles: constants.EnvData.ROLES, - ProtectedRoles: constants.EnvData.PROTECTED_ROLES, - DefaultRoles: constants.EnvData.DEFAULT_ROLES, - JwtRoleClaim: &constants.EnvData.JWT_ROLE_CLAIM, - GoogleClientID: &constants.EnvData.GOOGLE_CLIENT_ID, - GoogleClientSecret: &constants.EnvData.GOOGLE_CLIENT_SECRET, - GithubClientID: &constants.EnvData.GITHUB_CLIENT_ID, - GithubClientSecret: &constants.EnvData.GITHUB_CLIENT_SECRET, - FacebookClientID: &constants.EnvData.FACEBOOK_CLIENT_ID, - FacebookClientSecret: &constants.EnvData.FACEBOOK_CLIENT_SECRET, - OrganizationName: &constants.EnvData.ORGANIZATION_NAME, - OrganizationLogo: &constants.EnvData.ORGANIZATION_LOGO, + AdminSecret: &adminSecret, + DatabaseType: &databaseType, + DatabaseURL: &databaseURL, + DatabaseName: &databaseName, + SMTPHost: &smtpHost, + SMTPPort: &smtpPort, + SMTPPassword: &smtpPassword, + SMTPUsername: &smtpUsername, + SenderEmail: &senderEmail, + JwtType: &jwtType, + JwtSecret: &jwtSecret, + JwtRoleClaim: &jwtRoleClaim, + AllowedOrigins: allowedOrigins, + AuthorizerURL: &authorizerURL, + AppURL: &appURL, + RedisURL: &redisURL, + CookieName: &cookieName, + ResetPasswordURL: &resetPasswordURL, + DisableEmailVerification: &disableEmailVerification, + DisableBasicAuthentication: &disableBasicAuthentication, + DisableMagicLinkLogin: &disableMagicLinkLogin, + DisableLoginPage: &disableLoginPage, + Roles: roles, + ProtectedRoles: protectedRoles, + DefaultRoles: defaultRoles, + GoogleClientID: &googleClientID, + GoogleClientSecret: &googleClientSecret, + GithubClientID: &githubClientID, + GithubClientSecret: &githubClientSecret, + FacebookClientID: &facebookClientID, + FacebookClientSecret: &facebookClientSecret, + OrganizationName: &organizationName, + OrganizationLogo: &organizationLogo, } return res, nil } diff --git a/server/resolvers/delete_user.go b/server/resolvers/delete_user.go index 6037c75..db1a31f 100644 --- a/server/resolvers/delete_user.go +++ b/server/resolvers/delete_user.go @@ -11,7 +11,8 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -func DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { +// DeleteUserResolver is a resolver for delete user mutation +func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response if err != nil { @@ -27,7 +28,7 @@ func DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Respo return res, err } - session.DeleteUserSession(fmt.Sprintf("%x", user.ID)) + session.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) err = db.Mgr.DeleteUser(user) if err != nil { diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index 8c5c583..5fdf3c4 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -9,18 +9,20 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/email" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) -func ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) { +// ForgotPasswordResolver is a resolver for forgot password mutation +func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response if err != nil { return res, err } - if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableBasicAuthentication).(bool) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } host := gc.Request.Host @@ -35,20 +37,20 @@ func ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*mod return res, fmt.Errorf(`user with this email not found`) } - token, err := utils.CreateVerificationToken(params.Email, enum.ForgotPassword.String()) + token, err := utils.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword) if err != nil { log.Println(`error generating token`, err) } db.Mgr.AddVerification(db.VerificationRequest{ Token: token, - Identifier: enum.ForgotPassword.String(), + Identifier: constants.VerificationTypeForgotPassword, ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), Email: params.Email, }) // exec it as go routin so that we can reduce the api latency go func() { - utils.SendForgotPasswordMail(params.Email, token, host) + email.SendForgotPasswordMail(params.Email, token, host) }() res = &model.Response{ diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 482cf4b..80d4c4e 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -8,21 +8,22 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" "golang.org/x/crypto/bcrypt" ) -func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) { +// LoginResolver is a resolver for login mutation +func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.AuthResponse if err != nil { return res, err } - if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableBasicAuthentication).(bool) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } @@ -32,7 +33,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e return res, fmt.Errorf(`user with this email not found`) } - if !strings.Contains(user.SignupMethods, enum.BasicAuth.String()) { + if !strings.Contains(user.SignupMethods, constants.SignupMethodBasicAuth) { return res, fmt.Errorf(`user has not signed up email & password`) } @@ -46,7 +47,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e log.Println("compare password error:", err) return res, fmt.Errorf(`invalid password`) } - roles := constants.EnvData.DEFAULT_ROLES + roles := envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDefaultRoles).([]string) currentRoles := strings.Split(user.Roles, ",") if len(params.Roles) > 0 { if !utils.IsValidRoles(currentRoles, params.Roles) { @@ -55,12 +56,12 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e roles = params.Roles } - refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles) + refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles) - accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) + accessToken, expiresAt, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles) - session.SetToken(user.ID, accessToken, refreshToken) - utils.CreateSession(user.ID, gc) + session.SetUserSession(user.ID, accessToken, refreshToken) + utils.SaveSessionInDB(user.ID, gc) res = &model.AuthResponse{ Message: `Logged in successfully`, diff --git a/server/resolvers/logout.go b/server/resolvers/logout.go index 5e79300..d13e168 100644 --- a/server/resolvers/logout.go +++ b/server/resolvers/logout.go @@ -9,7 +9,8 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -func Logout(ctx context.Context) (*model.Response, error) { +// LogoutResolver is a resolver for logout mutation +func LogoutResolver(ctx context.Context) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response if err != nil { @@ -27,7 +28,7 @@ func Logout(ctx context.Context) (*model.Response, error) { } userId := fmt.Sprintf("%v", claim["id"]) - session.DeleteVerificationRequest(userId, token) + session.DeleteUserSession(userId, token) res = &model.Response{ Message: "Logged out successfully", } diff --git a/server/resolvers/magic_link_login.go b/server/resolvers/magic_link_login.go index 8ba726a..6aaa81b 100644 --- a/server/resolvers/magic_link_login.go +++ b/server/resolvers/magic_link_login.go @@ -9,15 +9,17 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/email" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) -func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) { +// MagicLinkLoginResolver is a resolver for magic link login mutation +func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) { var res *model.Response - if constants.EnvData.DISABLE_MAGIC_LINK_LOGIN { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableMagicLinkLogin).(bool) { return res, fmt.Errorf(`magic link login is disabled for this instance`) } @@ -37,17 +39,17 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod existingUser, err := db.Mgr.GetUserByEmail(params.Email) if err != nil { - user.SignupMethods = enum.MagicLinkLogin.String() + user.SignupMethods = constants.SignupMethodMagicLinkLogin // define roles for new user if len(params.Roles) > 0 { // check if roles exists - if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) { + if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyRoles).([]string), params.Roles) { return res, fmt.Errorf(`invalid roles`) } else { inputRoles = params.Roles } } else { - inputRoles = constants.EnvData.DEFAULT_ROLES + inputRoles = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDefaultRoles).([]string) } user.Roles = strings.Join(inputRoles, ",") @@ -72,7 +74,7 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod // check if it contains protected unassigned role hasProtectedRole := false for _, ur := range unasignedRoles { - if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) { + if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyProtectedRoles).([]string), ur) { hasProtectedRole = true } } @@ -87,8 +89,8 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod } signupMethod := existingUser.SignupMethods - if !strings.Contains(signupMethod, enum.MagicLinkLogin.String()) { - signupMethod = signupMethod + "," + enum.MagicLinkLogin.String() + if !strings.Contains(signupMethod, constants.SignupMethodMagicLinkLogin) { + signupMethod = signupMethod + "," + constants.SignupMethodMagicLinkLogin } user.SignupMethods = signupMethod @@ -98,9 +100,9 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod } } - if !constants.EnvData.DISABLE_EMAIL_VERIFICATION { + if !envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableEmailVerification).(bool) { // insert verification request - verificationType := enum.MagicLinkLogin.String() + verificationType := constants.VerificationTypeMagicLinkLogin token, err := utils.CreateVerificationToken(params.Email, verificationType) if err != nil { log.Println(`error generating token`, err) @@ -114,7 +116,7 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod // exec it as go routin so that we can reduce the api latency go func() { - utils.SendVerificationMail(params.Email, token) + email.SendVerificationMail(params.Email, token) }() } diff --git a/server/resolvers/meta.go b/server/resolvers/meta.go index 9873ab6..eab3846 100644 --- a/server/resolvers/meta.go +++ b/server/resolvers/meta.go @@ -7,7 +7,8 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -func Meta(ctx context.Context) (*model.Meta, error) { +// MetaResolver is a resolver for meta query +func MetaResolver(ctx context.Context) (*model.Meta, error) { metaInfo := utils.GetMetaInfo() return &metaInfo, nil } diff --git a/server/resolvers/profile.go b/server/resolvers/profile.go index 97d28e0..1861527 100644 --- a/server/resolvers/profile.go +++ b/server/resolvers/profile.go @@ -10,7 +10,8 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -func Profile(ctx context.Context) (*model.User, error) { +// ProfileResolver is a resolver for profile query +func ProfileResolver(ctx context.Context) (*model.User, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.User if err != nil { @@ -29,7 +30,7 @@ func Profile(ctx context.Context) (*model.User, error) { userID := fmt.Sprintf("%v", claim["id"]) email := fmt.Sprintf("%v", claim["email"]) - sessionToken := session.GetToken(userID, token) + sessionToken := session.GetUserSession(userID, token) if sessionToken == "" { return res, fmt.Errorf(`unauthorized`) diff --git a/server/resolvers/resend_verify_email.go b/server/resolvers/resend_verify_email.go index 0838a40..1636434 100644 --- a/server/resolvers/resend_verify_email.go +++ b/server/resolvers/resend_verify_email.go @@ -8,11 +8,13 @@ import ( "time" "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) -func ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) { +// ResendVerifyEmailResolver is a resolver for resend verify email mutation +func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) { var res *model.Response params.Email = strings.ToLower(params.Email) @@ -48,7 +50,7 @@ func ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) // exec it as go routin so that we can reduce the api latency go func() { - utils.SendVerificationMail(params.Email, token) + email.SendVerificationMail(params.Email, token) }() res = &model.Response{ diff --git a/server/resolvers/reset_password.go b/server/resolvers/reset_password.go index a83cf52..f933ca4 100644 --- a/server/resolvers/reset_password.go +++ b/server/resolvers/reset_password.go @@ -8,14 +8,15 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) -func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { +// ResetPasswordResolver is a resolver for reset password mutation +func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { var res *model.Response - if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableBasicAuthentication).(bool) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } @@ -39,12 +40,12 @@ func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model return res, err } - password, _ := utils.HashPassword(params.Password) + password, _ := utils.EncryptPassword(params.Password) user.Password = &password signupMethod := user.SignupMethods - if !strings.Contains(signupMethod, enum.BasicAuth.String()) { - signupMethod = signupMethod + "," + enum.BasicAuth.String() + if !strings.Contains(signupMethod, constants.SignupMethodBasicAuth) { + signupMethod = signupMethod + "," + constants.SignupMethodBasicAuth } user.SignupMethods = signupMethod diff --git a/server/resolvers/session.go b/server/resolvers/session.go index a587deb..eb02ae6 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -7,13 +7,14 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" ) -func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { +// SessionResolver is a resolver for session query +func SessionResolver(ctx context.Context, roles []string) (*model.AuthResponse, error) { var res *model.AuthResponse gc, err := utils.GinContextFromContext(ctx) @@ -36,7 +37,7 @@ func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { userIdStr := fmt.Sprintf("%v", user.ID) - sessionToken := session.GetToken(userIdStr, token) + sessionToken := session.GetUserSession(userIdStr, token) if sessionToken == "" { return res, fmt.Errorf(`unauthorized`) @@ -45,7 +46,7 @@ func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { expiresTimeObj := time.Unix(expiresAt, 0) currentTimeObj := time.Now() - claimRoleInterface := claim[constants.EnvData.JWT_ROLE_CLAIM].([]interface{}) + claimRoleInterface := claim[envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtRoleClaim).(string)].([]interface{}) claimRoles := make([]string, len(claimRoleInterface)) for i, v := range claimRoleInterface { claimRoles[i] = v.(string) @@ -59,14 +60,15 @@ func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { } } + // TODO change this logic to make it more secure if accessTokenErr != nil || expiresTimeObj.Sub(currentTimeObj).Minutes() <= 5 { // if access token has expired and refresh/session token is valid // generate new accessToken - currentRefreshToken := session.GetToken(userIdStr, token) - session.DeleteVerificationRequest(userIdStr, token) - token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles) - session.SetToken(userIdStr, token, currentRefreshToken) - utils.CreateSession(user.ID, gc) + currentRefreshToken := session.GetUserSession(userIdStr, token) + session.DeleteUserSession(userIdStr, token) + token, expiresAt, _ = utils.CreateAuthToken(user, constants.TokenTypeAccessToken, claimRoles) + session.SetUserSession(userIdStr, token, currentRefreshToken) + utils.SaveSessionInDB(user.ID, gc) } utils.SetCookie(gc, token) diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index ba19b45..0459fcc 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -9,20 +9,23 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/email" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" ) -func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error) { +// SignupResolver is a resolver for signup mutation +func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error) { + log.Println(envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) gc, err := utils.GinContextFromContext(ctx) var res *model.AuthResponse if err != nil { return res, err } - if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableBasicAuthentication).(bool) { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } if params.ConfirmPassword != params.Password { @@ -52,13 +55,13 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, if len(params.Roles) > 0 { // check if roles exists - if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) { + if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyRoles).([]string), params.Roles) { return res, fmt.Errorf(`invalid roles`) } else { inputRoles = params.Roles } } else { - inputRoles = constants.EnvData.DEFAULT_ROLES + inputRoles = envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDefaultRoles).([]string) } user := db.User{ @@ -67,7 +70,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, user.Roles = strings.Join(inputRoles, ",") - password, _ := utils.HashPassword(params.Password) + password, _ := utils.EncryptPassword(params.Password) user.Password = &password if params.GivenName != nil { @@ -102,8 +105,8 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, user.Picture = params.Picture } - user.SignupMethods = enum.BasicAuth.String() - if constants.EnvData.DISABLE_EMAIL_VERIFICATION { + user.SignupMethods = constants.SignupMethodBasicAuth + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableEmailVerification).(bool) { now := time.Now().Unix() user.EmailVerifiedAt = &now } @@ -115,9 +118,9 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, roles := strings.Split(user.Roles, ",") userToReturn := utils.GetResponseUserData(user) - if !constants.EnvData.DISABLE_EMAIL_VERIFICATION { + if !envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableEmailVerification).(bool) { // insert verification request - verificationType := enum.BasicAuthSignup.String() + verificationType := constants.VerificationTypeBasicAuthSignup token, err := utils.CreateVerificationToken(params.Email, verificationType) if err != nil { log.Println(`error generating token`, err) @@ -131,7 +134,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, // exec it as go routin so that we can reduce the api latency go func() { - utils.SendVerificationMail(params.Email, token) + email.SendVerificationMail(params.Email, token) }() res = &model.AuthResponse{ @@ -140,12 +143,12 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, } } else { - refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles) + refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles) - accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) + accessToken, expiresAt, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles) - session.SetToken(userIdStr, accessToken, refreshToken) - utils.CreateSession(user.ID, gc) + session.SetUserSession(userIdStr, accessToken, refreshToken) + utils.SaveSessionInDB(user.ID, gc) res = &model.AuthResponse{ Message: `Signed up successfully.`, AccessToken: &accessToken, diff --git a/server/resolvers/update_config.go b/server/resolvers/update_config.go index b1a407c..bb32d3a 100644 --- a/server/resolvers/update_config.go +++ b/server/resolvers/update_config.go @@ -9,10 +9,15 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/utils" ) +// TODO rename to UpdateEnvDataResolver + +// UpdateConfigResolver is a resolver for update config mutation +// This is admin only mutation func UpdateConfigResolver(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response @@ -57,13 +62,13 @@ func UpdateConfigResolver(ctx context.Context, params model.UpdateConfigInput) ( // handle derivative cases like disabling email verification & magic login // in case SMTP is off but env is set to true - if updatedData["SMTP_HOST"] == "" || updatedData["SENDER_EMAIL"] == "" || updatedData["SENDER_PASSWORD"] == "" { - if !updatedData["DISABLE_EMAIL_VERIFICATION"].(bool) { - updatedData["DISABLE_EMAIL_VERIFICATION"] = true + if updatedData[constants.EnvKeySmtpHost] == "" || updatedData[constants.EnvKeySenderEmail] == "" || updatedData[constants.EnvKeySmtpPort] == "" || updatedData[constants.EnvKeySmtpUsername] == "" || updatedData[constants.EnvKeySmtpPassword] == "" { + if !updatedData[constants.EnvKeyDisableEmailVerification].(bool) { + updatedData[constants.EnvKeyDisableEmailVerification] = true } - if !updatedData["DISABLE_MAGIC_LINK_LOGIN"].(bool) { - updatedData["DISABLE_MAGIC_LINK_LOGIN"] = true + if !updatedData[constants.EnvKeyDisableMagicLinkLogin].(bool) { + updatedData[constants.EnvKeyDisableMagicLinkLogin] = true } } @@ -72,7 +77,9 @@ func UpdateConfigResolver(ctx context.Context, params model.UpdateConfigInput) ( return res, err } - encryptedConfig, err := utils.EncryptConfig(updatedData) + envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData) + + encryptedConfig, err := utils.EncryptEnvData(updatedData) if err != nil { return res, err } @@ -84,7 +91,7 @@ func UpdateConfigResolver(ctx context.Context, params model.UpdateConfigInput) ( // in case of admin secret change update the cookie with new hash if params.AdminSecret != nil { - hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) if err != nil { return res, err } diff --git a/server/resolvers/update_profile.go b/server/resolvers/update_profile.go index f1c01f2..c077aca 100644 --- a/server/resolvers/update_profile.go +++ b/server/resolvers/update_profile.go @@ -7,15 +7,17 @@ import ( "strings" "time" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" "golang.org/x/crypto/bcrypt" ) -func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) { +// UpdateProfileResolver is resolver for update profile mutation +func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.Response if err != nil { @@ -33,7 +35,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model } id := fmt.Sprintf("%v", claim["id"]) - sessionToken := session.GetToken(id, token) + sessionToken := session.GetUserSession(id, token) if sessionToken == "" { return res, fmt.Errorf(`unauthorized`) @@ -44,8 +46,8 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model return res, fmt.Errorf("please enter atleast one param to update") } - email := fmt.Sprintf("%v", claim["email"]) - user, err := db.Mgr.GetUserByEmail(email) + userEmail := fmt.Sprintf("%v", claim["email"]) + user, err := db.Mgr.GetUserByEmail(userEmail) if err != nil { return res, err } @@ -99,7 +101,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model return res, fmt.Errorf(`password and confirm password does not match`) } - password, _ := utils.HashPassword(*params.NewPassword) + password, _ := utils.EncryptPassword(*params.NewPassword) user.Password = &password } @@ -120,14 +122,14 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model return res, fmt.Errorf("user with this email address already exists") } - session.DeleteUserSession(fmt.Sprintf("%v", user.ID)) + session.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) utils.DeleteCookie(gc) user.Email = newEmail user.EmailVerifiedAt = nil hasEmailChanged = true // insert verification request - verificationType := enum.UpdateEmail.String() + verificationType := constants.VerificationTypeUpdateEmail token, err := utils.CreateVerificationToken(newEmail, verificationType) if err != nil { log.Println(`error generating token`, err) @@ -141,7 +143,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model // exec it as go routin so that we can reduce the api latency go func() { - utils.SendVerificationMail(newEmail, token) + email.SendVerificationMail(newEmail, token) }() } diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index c67021b..6c84fe7 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -9,13 +9,16 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/email" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" ) -func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) { +// UpdateUserResolver is a resolver for update user mutation +// This is admin only mutation +func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*model.User, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.User if err != nil { @@ -67,6 +70,15 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, user.Picture = params.Picture } + if params.EmailVerified != nil { + if *params.EmailVerified { + now := time.Now().Unix() + user.EmailVerifiedAt = &now + } else { + user.EmailVerifiedAt = nil + } + } + if params.Email != nil && user.Email != *params.Email { // check if valid email if !utils.IsValidEmail(*params.Email) { @@ -80,13 +92,13 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, return res, fmt.Errorf("user with this email address already exists") } - session.DeleteUserSession(fmt.Sprintf("%v", user.ID)) + session.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) utils.DeleteCookie(gc) user.Email = newEmail user.EmailVerifiedAt = nil // insert verification request - verificationType := enum.UpdateEmail.String() + verificationType := constants.VerificationTypeUpdateEmail token, err := utils.CreateVerificationToken(newEmail, verificationType) if err != nil { log.Println(`error generating token`, err) @@ -100,7 +112,7 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, // exec it as go routin so that we can reduce the api latency go func() { - utils.SendVerificationMail(newEmail, token) + email.SendVerificationMail(newEmail, token) }() } @@ -112,7 +124,7 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, inputRoles = append(inputRoles, *item) } - if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), inputRoles) { + if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyRoles).([]string), envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyProtectedRoles).([]string)...)...), inputRoles) { return res, fmt.Errorf("invalid list of roles") } @@ -120,7 +132,7 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, rolesToSave = strings.Join(inputRoles, ",") } - session.DeleteUserSession(fmt.Sprintf("%v", user.ID)) + session.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) utils.DeleteCookie(gc) } diff --git a/server/resolvers/users.go b/server/resolvers/users.go index ce8d8ac..d5fd5f1 100644 --- a/server/resolvers/users.go +++ b/server/resolvers/users.go @@ -9,7 +9,9 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -func Users(ctx context.Context) ([]*model.User, error) { +// UsersResolver is a resolver for users query +// This is admin only query +func UsersResolver(ctx context.Context) ([]*model.User, error) { gc, err := utils.GinContextFromContext(ctx) var res []*model.User if err != nil { diff --git a/server/resolvers/verification_requests.go b/server/resolvers/verification_requests.go index bb2865a..e21d827 100644 --- a/server/resolvers/verification_requests.go +++ b/server/resolvers/verification_requests.go @@ -9,7 +9,9 @@ import ( "github.com/authorizerdev/authorizer/server/utils" ) -func VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) { +// VerificationRequestsResolver is a resolver for verification requests query +// This is admin only query +func VerificationRequestsResolver(ctx context.Context) ([]*model.VerificationRequest, error) { gc, err := utils.GinContextFromContext(ctx) var res []*model.VerificationRequest if err != nil { diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index 423766e..6a53190 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -6,14 +6,15 @@ import ( "strings" "time" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/utils" ) -func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) { +// VerifyEmailResolver is a resolver for verify email mutation +func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) { gc, err := utils.GinContextFromContext(ctx) var res *model.AuthResponse if err != nil { @@ -44,12 +45,11 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut db.Mgr.DeleteVerificationRequest(verificationRequest) roles := strings.Split(user.Roles, ",") - refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles) + refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles) + accessToken, expiresAt, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles) - accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) - - session.SetToken(user.ID, accessToken, refreshToken) - utils.CreateSession(user.ID, gc) + session.SetUserSession(user.ID, accessToken, refreshToken) + utils.SaveSessionInDB(user.ID, gc) res = &model.AuthResponse{ Message: `Email verified successfully.`, diff --git a/server/router/router.go b/server/router/router.go deleted file mode 100644 index 4fb8165..0000000 --- a/server/router/router.go +++ /dev/null @@ -1,23 +0,0 @@ -package router - -import ( - "github.com/authorizerdev/authorizer/server/handlers" - "github.com/authorizerdev/authorizer/server/middlewares" - "github.com/gin-contrib/location" - "github.com/gin-gonic/gin" -) - -func InitRouter() *gin.Engine { - router := gin.Default() - router.Use(location.Default()) - router.Use(middlewares.GinContextToContextMiddleware()) - router.Use(middlewares.CORSMiddleware()) - - router.GET("/", handlers.PlaygroundHandler()) - router.POST("/graphql", handlers.GraphqlHandler()) - router.GET("/verify_email", handlers.VerifyEmailHandler()) - router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler()) - router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler()) - - return router -} diff --git a/server/routes/routes.go b/server/routes/routes.go new file mode 100644 index 0000000..fdab544 --- /dev/null +++ b/server/routes/routes.go @@ -0,0 +1,44 @@ +package routes + +import ( + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/handlers" + "github.com/authorizerdev/authorizer/server/middlewares" + "github.com/gin-contrib/location" + "github.com/gin-gonic/gin" +) + +// InitRouter initializes gin router +func InitRouter() *gin.Engine { + router := gin.Default() + router.Use(location.Default()) + router.Use(middlewares.GinContextToContextMiddleware()) + router.Use(middlewares.CORSMiddleware()) + + router.GET("/", handlers.RootHandler()) + router.POST("/graphql", handlers.GraphqlHandler()) + router.GET("/playground", handlers.PlaygroundHandler()) + router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler()) + router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler()) + router.GET("/verify_email", handlers.VerifyEmailHandler()) + + router.LoadHTMLGlob("templates/*") + // login page app related routes. + if !envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableLoginPage).(bool) { + app := router.Group("/app") + { + app.Static("/build", "app/build") + app.GET("/", handlers.AppHandler()) + app.GET("/reset-password", handlers.AppHandler()) + } + } + + // dashboard related routes + app := router.Group("/dashboard") + { + app.Static("/build", "dashboard/build") + app.GET("/", handlers.DashboardHandler()) + } + return router +} diff --git a/server/session/in_memory_session.go b/server/session/in_memory_session.go index afd5dda..5c22d22 100644 --- a/server/session/in_memory_session.go +++ b/server/session/in_memory_session.go @@ -4,14 +4,17 @@ import ( "sync" ) +// InMemoryStore is a simple in-memory store for sessions. type InMemoryStore struct { - mu sync.Mutex + mutex sync.Mutex store map[string]map[string]string socialLoginState map[string]string } -func (c *InMemoryStore) AddToken(userId, accessToken, refreshToken string) { - c.mu.Lock() +// AddUserSession adds a user session to the in-memory store. +func (c *InMemoryStore) AddUserSession(userId, accessToken, refreshToken string) { + c.mutex.Lock() + defer c.mutex.Unlock() // delete sessions > 500 // not recommended for production if len(c.store) >= 500 { c.store = map[string]map[string]string{} @@ -28,48 +31,57 @@ func (c *InMemoryStore) AddToken(userId, accessToken, refreshToken string) { } c.store[userId] = tempMap } - - c.mu.Unlock() } -func (c *InMemoryStore) DeleteUserSession(userId string) { - c.mu.Lock() +// DeleteAllUserSession deletes all the user sessions from in-memory store. +func (c *InMemoryStore) DeleteAllUserSession(userId string) { + c.mutex.Lock() + defer c.mutex.Unlock() delete(c.store, userId) - c.mu.Unlock() } -func (c *InMemoryStore) DeleteVerificationRequest(userId, accessToken string) { - c.mu.Lock() +// DeleteUserSession deletes the particular user session from in-memory store. +func (c *InMemoryStore) DeleteUserSession(userId, accessToken string) { + c.mutex.Lock() + defer c.mutex.Unlock() delete(c.store[userId], accessToken) - c.mu.Unlock() } +// ClearStore clears the in-memory store. func (c *InMemoryStore) ClearStore() { - c.mu.Lock() + c.mutex.Lock() + defer c.mutex.Unlock() c.store = map[string]map[string]string{} - c.mu.Unlock() } -func (c *InMemoryStore) GetToken(userId, accessToken string) string { +// GetUserSession returns the user session token from the in-memory store. +func (c *InMemoryStore) GetUserSession(userId, accessToken string) string { + // c.mutex.Lock() + // defer c.mutex.Unlock() + token := "" - c.mu.Lock() if sessionMap, ok := c.store[userId]; ok { if val, ok := sessionMap[accessToken]; ok { token = val } } - c.mu.Unlock() return token } +// SetSocialLoginState sets the social login state in the in-memory store. func (c *InMemoryStore) SetSocialLoginState(key, state string) { - c.mu.Lock() + c.mutex.Lock() + defer c.mutex.Unlock() + c.socialLoginState[key] = state - c.mu.Unlock() } +// GetSocialLoginState gets the social login state from the in-memory store. func (c *InMemoryStore) GetSocialLoginState(key string) string { + c.mutex.Lock() + defer c.mutex.Unlock() + state := "" if stateVal, ok := c.socialLoginState[key]; ok { state = stateVal @@ -78,8 +90,10 @@ func (c *InMemoryStore) GetSocialLoginState(key string) string { return state } +// RemoveSocialLoginState removes the social login state from the in-memory store. func (c *InMemoryStore) RemoveSocialLoginState(key string) { - c.mu.Lock() + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.socialLoginState, key) - c.mu.Unlock() } diff --git a/server/session/redis_store.go b/server/session/redis_store.go index c9d2841..3be99cc 100644 --- a/server/session/redis_store.go +++ b/server/session/redis_store.go @@ -13,7 +13,8 @@ type RedisStore struct { store *redis.Client } -func (c *RedisStore) AddToken(userId, accessToken, refreshToken string) { +// AddUserSession adds the user session to redis +func (c *RedisStore) AddUserSession(userId, accessToken, refreshToken string) { err := c.store.HMSet(c.ctx, "authorizer_"+userId, map[string]string{ accessToken: refreshToken, }).Err() @@ -22,20 +23,23 @@ func (c *RedisStore) AddToken(userId, accessToken, refreshToken string) { } } -func (c *RedisStore) DeleteUserSession(userId string) { +// DeleteAllUserSession deletes all the user session from redis +func (c *RedisStore) DeleteAllUserSession(userId string) { err := c.store.Del(c.ctx, "authorizer_"+userId).Err() if err != nil { log.Fatalln("Error deleting redis token:", err) } } -func (c *RedisStore) DeleteVerificationRequest(userId, accessToken string) { +// DeleteUserSession deletes the particular user session from redis +func (c *RedisStore) DeleteUserSession(userId, accessToken string) { err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err() if err != nil { log.Fatalln("Error deleting redis token:", err) } } +// ClearStore clears the redis store for authorizer related tokens func (c *RedisStore) ClearStore() { err := c.store.Del(c.ctx, "authorizer_*").Err() if err != nil { @@ -43,7 +47,8 @@ func (c *RedisStore) ClearStore() { } } -func (c *RedisStore) GetToken(userId, accessToken string) string { +// GetUserSession returns the user session token from the redis store. +func (c *RedisStore) GetUserSession(userId, accessToken string) string { token := "" res, err := c.store.HMGet(c.ctx, "authorizer_"+userId, accessToken).Result() if err != nil { @@ -55,6 +60,7 @@ func (c *RedisStore) GetToken(userId, accessToken string) string { return token } +// SetSocialLoginState sets the social login state in redis store. func (c *RedisStore) SetSocialLoginState(key, state string) { err := c.store.Set(c.ctx, key, state, 0).Err() if err != nil { @@ -62,6 +68,7 @@ func (c *RedisStore) SetSocialLoginState(key, state string) { } } +// GetSocialLoginState gets the social login state from redis store. func (c *RedisStore) GetSocialLoginState(key string) string { state := "" state, err := c.store.Get(c.ctx, key).Result() @@ -72,6 +79,7 @@ func (c *RedisStore) GetSocialLoginState(key string) string { return state } +// RemoveSocialLoginState removes the social login state from redis store. func (c *RedisStore) RemoveSocialLoginState(key string) { err := c.store.Del(c.ctx, key).Err() if err != nil { diff --git a/server/session/session.go b/server/session/session.go index 39727b3..62331d7 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -5,57 +5,65 @@ import ( "log" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/go-redis/redis/v8" ) +// SessionStore is a struct that defines available session stores +// If redis store is available, higher preference is given to that store. +// Else in memory store is used. type SessionStore struct { InMemoryStoreObj *InMemoryStore RedisMemoryStoreObj *RedisStore } +// SessionStoreObj is a global variable that holds the +// reference to various session store instances var SessionStoreObj SessionStore -func SetToken(userId, accessToken, refreshToken string) { - // TODO: Set session information in db for all the sessions that gets generated - // it should async go function - +// SetUserSession sets the user session in the session store +func SetUserSession(userId, accessToken, refreshToken string) { if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.AddToken(userId, accessToken, refreshToken) + SessionStoreObj.RedisMemoryStoreObj.AddUserSession(userId, accessToken, refreshToken) } if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.AddToken(userId, accessToken, refreshToken) + SessionStoreObj.InMemoryStoreObj.AddUserSession(userId, accessToken, refreshToken) } } -func DeleteVerificationRequest(userId, accessToken string) { +// DeleteUserSession deletes the particular user session from the session store +func DeleteUserSession(userId, accessToken string) { if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.DeleteVerificationRequest(userId, accessToken) + SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId, accessToken) } if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.DeleteVerificationRequest(userId, accessToken) + SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId, accessToken) } } -func DeleteUserSession(userId string) { +// DeleteAllSessions deletes all the sessions from the session store +func DeleteAllUserSession(userId string) { if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId) + SessionStoreObj.RedisMemoryStoreObj.DeleteAllUserSession(userId) } if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId) + SessionStoreObj.InMemoryStoreObj.DeleteAllUserSession(userId) } } -func GetToken(userId, accessToken string) string { +// GetUserSession returns the user session from the session store +func GetUserSession(userId, accessToken string) string { if SessionStoreObj.RedisMemoryStoreObj != nil { - return SessionStoreObj.RedisMemoryStoreObj.GetToken(userId, accessToken) + return SessionStoreObj.RedisMemoryStoreObj.GetUserSession(userId, accessToken) } if SessionStoreObj.InMemoryStoreObj != nil { - return SessionStoreObj.InMemoryStoreObj.GetToken(userId, accessToken) + return SessionStoreObj.InMemoryStoreObj.GetUserSession(userId, accessToken) } return "" } +// ClearStore clears the session store for authorizer tokens func ClearStore() { if SessionStoreObj.RedisMemoryStoreObj != nil { SessionStoreObj.RedisMemoryStoreObj.ClearStore() @@ -65,6 +73,7 @@ func ClearStore() { } } +// SetSocialLoginState sets the social login state in the session store func SetSocailLoginState(key, state string) { if SessionStoreObj.RedisMemoryStoreObj != nil { SessionStoreObj.RedisMemoryStoreObj.SetSocialLoginState(key, state) @@ -74,6 +83,7 @@ func SetSocailLoginState(key, state string) { } } +// GetSocialLoginState returns the social login state from the session store func GetSocailLoginState(key string) string { if SessionStoreObj.RedisMemoryStoreObj != nil { return SessionStoreObj.RedisMemoryStoreObj.GetSocialLoginState(key) @@ -85,6 +95,7 @@ func GetSocailLoginState(key string) string { return "" } +// RemoveSocialLoginState removes the social login state from the session store func RemoveSocialLoginState(key string) { if SessionStoreObj.RedisMemoryStoreObj != nil { SessionStoreObj.RedisMemoryStoreObj.RemoveSocialLoginState(key) @@ -94,10 +105,11 @@ func RemoveSocialLoginState(key string) { } } +// InitializeSessionStore initializes the SessionStoreObj based on environment variables func InitSession() { - if constants.EnvData.REDIS_URL != "" { + if envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyRedisURL).(string) != "" { log.Println("using redis store to save sessions") - opt, err := redis.ParseURL(constants.EnvData.REDIS_URL) + opt, err := redis.ParseURL(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyRedisURL).(string)) if err != nil { log.Fatalln("Error parsing redis url:", err) } diff --git a/server/__test__/admin_login_test.go b/server/test/admin_login_test.go similarity index 72% rename from server/__test__/admin_login_test.go rename to server/test/admin_login_test.go index eefd8b9..4bdb8e8 100644 --- a/server/__test__/admin_login_test.go +++ b/server/test/admin_login_test.go @@ -4,12 +4,14 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func adminLoginTests(s TestSetup, t *testing.T) { +func adminLoginTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should complete admin login`, func(t *testing.T) { _, ctx := createContext(s) _, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{ @@ -19,7 +21,7 @@ func adminLoginTests(s TestSetup, t *testing.T) { assert.NotNil(t, err) _, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{ - AdminSecret: constants.EnvData.ADMIN_SECRET, + AdminSecret: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string), }) assert.Nil(t, err) diff --git a/server/test/admin_logout_test.go b/server/test/admin_logout_test.go new file mode 100644 index 0000000..26b3011 --- /dev/null +++ b/server/test/admin_logout_test.go @@ -0,0 +1,28 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/utils" + "github.com/stretchr/testify/assert" +) + +func adminLogoutTests(t *testing.T, s TestSetup) { + t.Helper() + t.Run(`should get admin session`, func(t *testing.T) { + req, ctx := createContext(s) + _, err := resolvers.AdminLogoutResolver(ctx) + assert.NotNil(t, err) + + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) + assert.Nil(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) + _, err = resolvers.AdminLogoutResolver(ctx) + + assert.Nil(t, err) + }) +} diff --git a/server/test/admin_session_test.go b/server/test/admin_session_test.go new file mode 100644 index 0000000..259f088 --- /dev/null +++ b/server/test/admin_session_test.go @@ -0,0 +1,30 @@ +package test + +import ( + "fmt" + "log" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/utils" + "github.com/stretchr/testify/assert" +) + +func adminSessionTests(t *testing.T, s TestSetup) { + t.Helper() + t.Run(`should get admin session`, func(t *testing.T) { + req, ctx := createContext(s) + _, err := resolvers.AdminSessionResolver(ctx) + log.Println("error:", err) + assert.NotNil(t, err) + + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) + assert.Nil(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) + _, err = resolvers.AdminSessionResolver(ctx) + + assert.Nil(t, err) + }) +} diff --git a/server/__test__/admin_signup_test.go b/server/test/admin_signup_test.go similarity index 76% rename from server/__test__/admin_signup_test.go rename to server/test/admin_signup_test.go index 441533e..1fbd750 100644 --- a/server/__test__/admin_signup_test.go +++ b/server/test/admin_signup_test.go @@ -1,26 +1,27 @@ package test import ( - "log" "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) -func adminSignupTests(s TestSetup, t *testing.T) { +func adminSignupTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should complete admin login`, func(t *testing.T) { _, ctx := createContext(s) _, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ AdminSecret: "admin", }) - log.Println("err", err) + assert.NotNil(t, err) // reset env for test to pass - constants.EnvData.ADMIN_SECRET = "" + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyAdminSecret, "") _, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ AdminSecret: uuid.New().String(), diff --git a/server/__test__/config_test.go b/server/test/config_test.go similarity index 51% rename from server/__test__/config_test.go rename to server/test/config_test.go index 5cf6d2c..2f4ee69 100644 --- a/server/__test__/config_test.go +++ b/server/test/config_test.go @@ -6,24 +6,26 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) -func configTests(s TestSetup, t *testing.T) { +func configTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should get config`, func(t *testing.T) { req, ctx := createContext(s) _, err := resolvers.ConfigResolver(ctx) log.Println("error:", err) assert.NotNil(t, err) - h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) res, err := resolvers.ConfigResolver(ctx) assert.Nil(t, err) - assert.Equal(t, *res.AdminSecret, constants.EnvData.ADMIN_SECRET) + assert.Equal(t, *res.AdminSecret, envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) }) } diff --git a/server/__test__/cors_test.go b/server/test/cors_test.go similarity index 100% rename from server/__test__/cors_test.go rename to server/test/cors_test.go diff --git a/server/__test__/delete_user_test.go b/server/test/delete_user_test.go similarity index 56% rename from server/__test__/delete_user_test.go rename to server/test/delete_user_test.go index ae891d1..45faf58 100644 --- a/server/__test__/delete_user_test.go +++ b/server/test/delete_user_test.go @@ -5,32 +5,34 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) -func deleteUserTest(s TestSetup, t *testing.T) { +func deleteUserTest(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should delete users with admin secret only`, func(t *testing.T) { req, ctx := createContext(s) email := "delete_user." + s.TestInfo.Email - resolvers.Signup(ctx, model.SignUpInput{ + resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - _, err := resolvers.DeleteUser(ctx, model.DeleteUserInput{ + _, err := resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{ Email: email, }) assert.NotNil(t, err, "unauthorized") - h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) - _, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{ + _, err = resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{ Email: email, }) assert.Nil(t, err) diff --git a/server/test/env_test.go b/server/test/env_test.go new file mode 100644 index 0000000..0452239 --- /dev/null +++ b/server/test/env_test.go @@ -0,0 +1,29 @@ +package test + +import ( + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/env" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/stretchr/testify/assert" +) + +func TestEnvs(t *testing.T) { + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyEnvPath, "../../.env.sample") + env.InitEnv() + store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + + assert.Equal(t, store[constants.EnvKeyAdminSecret].(string), "admin") + assert.Equal(t, store[constants.EnvKeyEnv].(string), "production") + assert.False(t, store[constants.EnvKeyDisableEmailVerification].(bool)) + assert.False(t, store[constants.EnvKeyDisableMagicLinkLogin].(bool)) + assert.False(t, store[constants.EnvKeyDisableBasicAuthentication].(bool)) + assert.Equal(t, store[constants.EnvKeyJwtType].(string), "HS256") + assert.Equal(t, store[constants.EnvKeyJwtSecret].(string), "random_string") + assert.Equal(t, store[constants.EnvKeyJwtRoleClaim].(string), "role") + assert.EqualValues(t, store[constants.EnvKeyRoles].([]string), []string{"user"}) + assert.EqualValues(t, store[constants.EnvKeyDefaultRoles].([]string), []string{"user"}) + assert.EqualValues(t, store[constants.EnvKeyProtectedRoles].([]string), []string{"admin"}) + assert.EqualValues(t, store[constants.EnvKeyAllowedOrigins].([]string), []string{"*"}) +} diff --git a/server/__test__/forgot_password_test.go b/server/test/forgot_password_test.go similarity index 63% rename from server/__test__/forgot_password_test.go rename to server/test/forgot_password_test.go index 007ac26..e181b0e 100644 --- a/server/__test__/forgot_password_test.go +++ b/server/test/forgot_password_test.go @@ -3,32 +3,33 @@ package test import ( "testing" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func forgotPasswordTest(s TestSetup, t *testing.T) { +func forgotPasswordTest(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should run forgot password`, func(t *testing.T) { _, ctx := createContext(s) email := "forgot_password." + s.TestInfo.Email - _, err := resolvers.Signup(ctx, model.SignUpInput{ + _, err := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - _, err = resolvers.ForgotPassword(ctx, model.ForgotPasswordInput{ + _, err = resolvers.ForgotPasswordResolver(ctx, model.ForgotPasswordInput{ Email: email, }) assert.Nil(t, err, "no errors for forgot password") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String()) + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeForgotPassword) assert.Nil(t, err) - assert.Equal(t, verificationRequest.Identifier, enum.ForgotPassword.String()) + assert.Equal(t, verificationRequest.Identifier, constants.VerificationTypeForgotPassword) cleanData(email) }) diff --git a/server/__test__/login_test.go b/server/test/login_test.go similarity index 68% rename from server/__test__/login_test.go rename to server/test/login_test.go index b875081..1c40a50 100644 --- a/server/__test__/login_test.go +++ b/server/test/login_test.go @@ -3,49 +3,50 @@ package test import ( "testing" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func loginTests(s TestSetup, t *testing.T) { +func loginTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should login`, func(t *testing.T) { _, ctx := createContext(s) email := "login." + s.TestInfo.Email - _, err := resolvers.Signup(ctx, model.SignUpInput{ + _, err := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - _, err = resolvers.Login(ctx, model.LoginInput{ + _, err = resolvers.LoginResolver(ctx, model.LoginInput{ Email: email, Password: s.TestInfo.Password, }) assert.NotNil(t, err, "should fail because email is not verified") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String()) - resolvers.VerifyEmail(ctx, model.VerifyEmailInput{ + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeBasicAuthSignup) + resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) - _, err = resolvers.Login(ctx, model.LoginInput{ + _, err = resolvers.LoginResolver(ctx, model.LoginInput{ Email: email, Password: s.TestInfo.Password, Roles: []string{"test"}, }) assert.NotNil(t, err, "invalid roles") - _, err = resolvers.Login(ctx, model.LoginInput{ + _, err = resolvers.LoginResolver(ctx, model.LoginInput{ Email: email, Password: s.TestInfo.Password + "s", }) assert.NotNil(t, err, "invalid password") - loginRes, err := resolvers.Login(ctx, model.LoginInput{ + loginRes, err := resolvers.LoginResolver(ctx, model.LoginInput{ Email: email, Password: s.TestInfo.Password, }) diff --git a/server/__test__/logout_test.go b/server/test/logout_test.go similarity index 55% rename from server/__test__/logout_test.go rename to server/test/logout_test.go index 9788279..9c7def5 100644 --- a/server/__test__/logout_test.go +++ b/server/test/logout_test.go @@ -6,31 +6,32 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func logoutTests(s TestSetup, t *testing.T) { +func logoutTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should logout user`, func(t *testing.T) { req, ctx := createContext(s) email := "logout." + s.TestInfo.Email - _, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{ + _, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{ Email: email, }) - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String()) - verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{ + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeMagicLinkLogin) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) - _, err = resolvers.Logout(ctx) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string), token)) + _, err = resolvers.LogoutResolver(ctx) assert.Nil(t, err) - _, err = resolvers.Profile(ctx) + _, err = resolvers.ProfileResolver(ctx) assert.NotNil(t, err, "unauthorized") cleanData(email) }) diff --git a/server/__test__/magic_link_login_test.go b/server/test/magic_link_login_test.go similarity index 57% rename from server/__test__/magic_link_login_test.go rename to server/test/magic_link_login_test.go index 9bc3c0d..9841ad7 100644 --- a/server/__test__/magic_link_login_test.go +++ b/server/test/magic_link_login_test.go @@ -6,30 +6,31 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func magicLinkLoginTests(s TestSetup, t *testing.T) { +func magicLinkLoginTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should login with magic link`, func(t *testing.T) { req, ctx := createContext(s) email := "magic_link_login." + s.TestInfo.Email - _, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{ + _, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{ Email: email, }) assert.Nil(t, err) - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String()) - verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{ + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeMagicLinkLogin) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) - _, err = resolvers.Profile(ctx) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string), token)) + _, err = resolvers.ProfileResolver(ctx) assert.Nil(t, err) cleanData(email) diff --git a/server/__test__/meta_test.go b/server/test/meta_test.go similarity index 82% rename from server/__test__/meta_test.go rename to server/test/meta_test.go index 9989141..130ccc7 100644 --- a/server/__test__/meta_test.go +++ b/server/test/meta_test.go @@ -2,18 +2,17 @@ package test import ( "context" - "log" "testing" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func metaTests(s TestSetup, t *testing.T) { +func metaTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should get meta information`, func(t *testing.T) { ctx := context.Background() - meta, err := resolvers.Meta(ctx) - log.Println("=> meta:", meta) + meta, err := resolvers.MetaResolver(ctx) assert.Nil(t, err) assert.False(t, meta.IsFacebookLoginEnabled) assert.False(t, meta.IsGoogleLoginEnabled) diff --git a/server/__test__/profile_test.go b/server/test/profile_test.go similarity index 62% rename from server/__test__/profile_test.go rename to server/test/profile_test.go index 2c6f853..45d07fd 100644 --- a/server/__test__/profile_test.go +++ b/server/test/profile_test.go @@ -6,34 +6,35 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func profileTests(s TestSetup, t *testing.T) { +func profileTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should get profile only with token`, func(t *testing.T) { req, ctx := createContext(s) email := "profile." + s.TestInfo.Email - resolvers.Signup(ctx, model.SignUpInput{ + resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - _, err := resolvers.Profile(ctx) + _, err := resolvers.ProfileResolver(ctx) assert.NotNil(t, err, "unauthorized") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String()) - verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{ + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeBasicAuthSignup) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) - profileRes, err := resolvers.Profile(ctx) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string), token)) + profileRes, err := resolvers.ProfileResolver(ctx) assert.Nil(t, err) newEmail := *&profileRes.Email diff --git a/server/__test__/resend_verify_email_test.go b/server/test/resend_verify_email_test.go similarity index 61% rename from server/__test__/resend_verify_email_test.go rename to server/test/resend_verify_email_test.go index 0eb733e..b3420b0 100644 --- a/server/__test__/resend_verify_email_test.go +++ b/server/test/resend_verify_email_test.go @@ -3,25 +3,26 @@ package test import ( "testing" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func resendVerifyEmailTests(s TestSetup, t *testing.T) { +func resendVerifyEmailTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should resend verification email`, func(t *testing.T) { _, ctx := createContext(s) email := "resend_verify_email." + s.TestInfo.Email - _, err := resolvers.Signup(ctx, model.SignUpInput{ + _, err := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - _, err = resolvers.ResendVerifyEmail(ctx, model.ResendVerifyEmailInput{ + _, err = resolvers.ResendVerifyEmailResolver(ctx, model.ResendVerifyEmailInput{ Email: email, - Identifier: enum.BasicAuthSignup.String(), + Identifier: constants.VerificationTypeBasicAuthSignup, }) assert.Nil(t, err) diff --git a/server/__test__/reset_password_test.go b/server/test/reset_password_test.go similarity index 69% rename from server/__test__/reset_password_test.go rename to server/test/reset_password_test.go index a9008d0..3aaccbc 100644 --- a/server/__test__/reset_password_test.go +++ b/server/test/reset_password_test.go @@ -3,32 +3,33 @@ package test import ( "testing" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func resetPasswordTest(s TestSetup, t *testing.T) { +func resetPasswordTest(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should reset password`, func(t *testing.T) { email := "reset_password." + s.TestInfo.Email _, ctx := createContext(s) - _, err := resolvers.Signup(ctx, model.SignUpInput{ + _, err := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - _, err = resolvers.ForgotPassword(ctx, model.ForgotPasswordInput{ + _, err = resolvers.ForgotPasswordResolver(ctx, model.ForgotPasswordInput{ Email: email, }) assert.Nil(t, err, "no errors for forgot password") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String()) + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeForgotPassword) assert.Nil(t, err, "should get forgot password request") - _, err = resolvers.ResetPassword(ctx, model.ResetPasswordInput{ + _, err = resolvers.ResetPasswordResolver(ctx, model.ResetPasswordInput{ Token: verificationRequest.Token, Password: "test1", ConfirmPassword: "test", @@ -36,7 +37,7 @@ func resetPasswordTest(s TestSetup, t *testing.T) { assert.NotNil(t, err, "passowrds don't match") - _, err = resolvers.ResetPassword(ctx, model.ResetPasswordInput{ + _, err = resolvers.ResetPasswordResolver(ctx, model.ResetPasswordInput{ Token: verificationRequest.Token, Password: "test1", ConfirmPassword: "test1", diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go new file mode 100644 index 0000000..9651a1c --- /dev/null +++ b/server/test/resolvers_test.go @@ -0,0 +1,65 @@ +package test + +import ( + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/env" + "github.com/authorizerdev/authorizer/server/envstore" +) + +func TestResolvers(t *testing.T) { + databases := map[string]string{ + constants.DbTypeSqlite: "../../data.db", + // constants.DbTypeArangodb: "http://localhost:8529", + // constants.DbTypeMongodb: "mongodb://localhost:27017", + } + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyVersion, "test") + for dbType, dbURL := range databases { + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyDatabaseURL, dbURL) + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyDatabaseType, dbType) + + env.InitEnv() + db.InitDB() + + // clean the persisted config for test to use fresh config + config, err := db.Mgr.GetConfig() + if err == nil { + config.Config = []byte{} + db.Mgr.UpdateConfig(config) + } + env.PersistEnv() + + s := testSetup() + defer s.Server.Close() + + t.Run("should pass tests for "+dbType, func(t *testing.T) { + // admin tests + adminSignupTests(t, s) + verificationRequestsTest(t, s) + usersTest(t, s) + deleteUserTest(t, s) + updateUserTest(t, s) + adminLoginTests(t, s) + adminLogoutTests(t, s) + adminSessionTests(t, s) + updateConfigTests(t, s) + configTests(t, s) + + // user tests + loginTests(t, s) + signupTests(t, s) + forgotPasswordTest(t, s) + resendVerifyEmailTests(t, s) + resetPasswordTest(t, s) + verifyEmailTest(t, s) + sessionTests(t, s) + profileTests(t, s) + updateProfileTests(t, s) + magicLinkLoginTests(t, s) + logoutTests(t, s) + metaTests(t, s) + }) + } +} diff --git a/server/__test__/session_test.go b/server/test/session_test.go similarity index 62% rename from server/__test__/session_test.go rename to server/test/session_test.go index ae764cc..1b4e7eb 100644 --- a/server/__test__/session_test.go +++ b/server/test/session_test.go @@ -6,35 +6,36 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func sessionTests(s TestSetup, t *testing.T) { +func sessionTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should allow access to profile with session only`, func(t *testing.T) { req, ctx := createContext(s) email := "session." + s.TestInfo.Email - resolvers.Signup(ctx, model.SignUpInput{ + resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - _, err := resolvers.Session(ctx, []string{}) + _, err := resolvers.SessionResolver(ctx, []string{}) assert.NotNil(t, err, "unauthorized") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String()) - verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{ + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeBasicAuthSignup) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string), token)) - sessionRes, err := resolvers.Session(ctx, []string{}) + sessionRes, err := resolvers.SessionResolver(ctx, []string{}) assert.Nil(t, err) newToken := *sessionRes.AccessToken diff --git a/server/__test__/signup_test.go b/server/test/signup_test.go similarity index 76% rename from server/__test__/signup_test.go rename to server/test/signup_test.go index a7cda68..92ec18f 100644 --- a/server/__test__/signup_test.go +++ b/server/test/signup_test.go @@ -3,25 +3,26 @@ package test import ( "testing" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func signupTests(s TestSetup, t *testing.T) { +func signupTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should complete the signup and check duplicates`, func(t *testing.T) { _, ctx := createContext(s) email := "signup." + s.TestInfo.Email - res, err := resolvers.Signup(ctx, model.SignUpInput{ + res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password + "s", }) assert.NotNil(t, err, "invalid password errors") - res, err = resolvers.Signup(ctx, model.SignUpInput{ + res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, @@ -31,7 +32,7 @@ func signupTests(s TestSetup, t *testing.T) { assert.Equal(t, email, user.Email) assert.Nil(t, res.AccessToken, "access token should be nil") - res, err = resolvers.Signup(ctx, model.SignUpInput{ + res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, @@ -39,7 +40,7 @@ func signupTests(s TestSetup, t *testing.T) { assert.NotNil(t, err, "should throw duplicate email error") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String()) + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeBasicAuthSignup) assert.Nil(t, err) assert.Equal(t, email, verificationRequest.Email) cleanData(email) diff --git a/server/__test__/test.go b/server/test/test.go similarity index 88% rename from server/__test__/test.go rename to server/test/test.go index 39848f6..898d14e 100644 --- a/server/__test__/test.go +++ b/server/test/test.go @@ -9,8 +9,8 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/env" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/handlers" "github.com/authorizerdev/authorizer/server/middlewares" "github.com/authorizerdev/authorizer/server/session" @@ -32,17 +32,17 @@ type TestSetup struct { } func cleanData(email string) { - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String()) + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeBasicAuthSignup) if err == nil { err = db.Mgr.DeleteVerificationRequest(verificationRequest) } - verificationRequest, err = db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String()) + verificationRequest, err = db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeForgotPassword) if err == nil { err = db.Mgr.DeleteVerificationRequest(verificationRequest) } - verificationRequest, err = db.Mgr.GetVerificationByEmail(email, enum.UpdateEmail.String()) + verificationRequest, err = db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeUpdateEmail) if err == nil { err = db.Mgr.DeleteVerificationRequest(verificationRequest) } @@ -72,8 +72,7 @@ func testSetup() TestSetup { Password: "test", } - constants.EnvData.ENV_PATH = "../../.env.sample" - + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyEnvPath, "../../.env.sample") env.InitEnv() session.InitSession() diff --git a/server/__test__/update_config_test.go b/server/test/update_config_test.go similarity index 59% rename from server/__test__/update_config_test.go rename to server/test/update_config_test.go index 65eda37..357cd97 100644 --- a/server/__test__/update_config_test.go +++ b/server/test/update_config_test.go @@ -6,26 +6,27 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) -func updateConfigTests(s TestSetup, t *testing.T) { +func updateConfigTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should update configs`, func(t *testing.T) { req, ctx := createContext(s) - originalAppURL := constants.EnvData.APP_URL - log.Println("=> originalAppURL:", constants.EnvData.APP_URL) + originalAppURL := envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAppURL).(string) data := model.UpdateConfigInput{} _, err := resolvers.UpdateConfigResolver(ctx, data) log.Println("error:", err) assert.NotNil(t, err) - h, _ := utils.HashPassword(constants.EnvData.ADMIN_SECRET) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) + assert.Nil(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) newURL := "https://test.com" data = model.UpdateConfigInput{ AppURL: &newURL, @@ -33,8 +34,7 @@ func updateConfigTests(s TestSetup, t *testing.T) { _, err = resolvers.UpdateConfigResolver(ctx, data) log.Println("error:", err) assert.Nil(t, err) - assert.Equal(t, constants.EnvData.APP_URL, newURL) - assert.NotEqual(t, constants.EnvData.APP_URL, originalAppURL) + assert.Equal(t, envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAppURL).(string), newURL) data = model.UpdateConfigInput{ AppURL: &originalAppURL, } diff --git a/server/__test__/update_profile_test.go b/server/test/update_profile_test.go similarity index 59% rename from server/__test__/update_profile_test.go rename to server/test/update_profile_test.go index 6a2e3d4..58a23a8 100644 --- a/server/__test__/update_profile_test.go +++ b/server/test/update_profile_test.go @@ -6,47 +6,48 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func updateProfileTests(s TestSetup, t *testing.T) { +func updateProfileTests(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should update the profile with access token only`, func(t *testing.T) { req, ctx := createContext(s) email := "update_profile." + s.TestInfo.Email - resolvers.Signup(ctx, model.SignUpInput{ + resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) fName := "samani" - _, err := resolvers.UpdateProfile(ctx, model.UpdateProfileInput{ + _, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ FamilyName: &fName, }) assert.NotNil(t, err, "unauthorized") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String()) - verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{ + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeBasicAuthSignup) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) token := *verifyRes.AccessToken - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) - _, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{ + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string), token)) + _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ FamilyName: &fName, }) assert.Nil(t, err) newEmail := "new_" + email - _, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{ + _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ Email: &newEmail, }) assert.Nil(t, err) - _, err = resolvers.Profile(ctx) + _, err = resolvers.ProfileResolver(ctx) assert.NotNil(t, err, "unauthorized") cleanData(newEmail) diff --git a/server/__test__/update_user_test.go b/server/test/update_user_test.go similarity index 60% rename from server/__test__/update_user_test.go rename to server/test/update_user_test.go index 26a9583..429df00 100644 --- a/server/__test__/update_user_test.go +++ b/server/test/update_user_test.go @@ -5,17 +5,19 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) -func updateUserTest(s TestSetup, t *testing.T) { +func updateUserTest(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should update the user with admin secret only`, func(t *testing.T) { req, ctx := createContext(s) email := "update_user." + s.TestInfo.Email - signupRes, _ := resolvers.Signup(ctx, model.SignUpInput{ + signupRes, _ := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, @@ -25,16 +27,16 @@ func updateUserTest(s TestSetup, t *testing.T) { adminRole := "admin" userRole := "user" newRoles := []*string{&adminRole, &userRole} - _, err := resolvers.UpdateUser(ctx, model.UpdateUserInput{ + _, err := resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{ ID: user.ID, Roles: newRoles, }) assert.NotNil(t, err, "unauthorized") - h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) - _, err = resolvers.UpdateUser(ctx, model.UpdateUserInput{ + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) + _, err = resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{ ID: user.ID, Roles: newRoles, }) diff --git a/server/__test__/urls_test.go b/server/test/urls_test.go similarity index 100% rename from server/__test__/urls_test.go rename to server/test/urls_test.go diff --git a/server/__test__/users_test.go b/server/test/users_test.go similarity index 59% rename from server/__test__/users_test.go rename to server/test/users_test.go index abf2741..8c4d62a 100644 --- a/server/__test__/users_test.go +++ b/server/test/users_test.go @@ -5,29 +5,31 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) -func usersTest(s TestSetup, t *testing.T) { +func usersTest(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should get users list with admin secret only`, func(t *testing.T) { req, ctx := createContext(s) email := "users." + s.TestInfo.Email - resolvers.Signup(ctx, model.SignUpInput{ + resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - users, err := resolvers.Users(ctx) + users, err := resolvers.UsersResolver(ctx) assert.NotNil(t, err, "unauthorized") - h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) - users, err = resolvers.Users(ctx) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) + users, err = resolvers.UsersResolver(ctx) assert.Nil(t, err) rLen := len(users) assert.GreaterOrEqual(t, rLen, 1) diff --git a/server/__test__/validator_test.go b/server/test/validator_test.go similarity index 71% rename from server/__test__/validator_test.go rename to server/test/validator_test.go index 7145d74..646be5b 100644 --- a/server/__test__/validator_test.go +++ b/server/test/validator_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -22,8 +22,7 @@ func TestIsValidEmail(t *testing.T) { func TestIsValidOrigin(t *testing.T) { // don't use portocal(http/https) for ALLOWED_ORIGINS while testing, // as we trim them off while running the main function - constants.EnvData.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"} - + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyAllowedOrigins, []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}) assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin") assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin") assert.True(t, utils.IsValidOrigin("http://app.google.com"), "it should be valid origin") @@ -33,11 +32,12 @@ func TestIsValidOrigin(t *testing.T) { assert.True(t, utils.IsValidOrigin("http://xyx.abc.in"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://xyxabc.in"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://localhost:8080"), "it should be valid origin") + envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.EnvKeyAllowedOrigins, []string{"*"}) } func TestIsValidIdentifier(t *testing.T) { assert.False(t, utils.IsValidVerificationIdentifier("test"), "it should be invalid identifier") - assert.True(t, utils.IsValidVerificationIdentifier(enum.BasicAuthSignup.String()), "it should be valid identifier") - assert.True(t, utils.IsValidVerificationIdentifier(enum.UpdateEmail.String()), "it should be valid identifier") - assert.True(t, utils.IsValidVerificationIdentifier(enum.ForgotPassword.String()), "it should be valid identifier") + assert.True(t, utils.IsValidVerificationIdentifier(constants.VerificationTypeBasicAuthSignup), "it should be valid identifier") + assert.True(t, utils.IsValidVerificationIdentifier(constants.VerificationTypeUpdateEmail), "it should be valid identifier") + assert.True(t, utils.IsValidVerificationIdentifier(constants.VerificationTypeForgotPassword), "it should be valid identifier") } diff --git a/server/__test__/verification_requests_test.go b/server/test/verification_requests_test.go similarity index 54% rename from server/__test__/verification_requests_test.go rename to server/test/verification_requests_test.go index 21f2886..2c7de87 100644 --- a/server/__test__/verification_requests_test.go +++ b/server/test/verification_requests_test.go @@ -5,30 +5,33 @@ import ( "testing" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) -func verificationRequestsTest(s TestSetup, t *testing.T) { +func verificationRequestsTest(t *testing.T, s TestSetup) { + t.Helper() + t.Run(`should get verification requests with admin secret only`, func(t *testing.T) { req, ctx := createContext(s) email := "verification_requests." + s.TestInfo.Email - resolvers.Signup(ctx, model.SignUpInput{ + resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - requests, err := resolvers.VerificationRequests(ctx) - assert.NotNil(t, err, "unauthorizer") + requests, err := resolvers.VerificationRequestsResolver(ctx) + assert.NotNil(t, err, "unauthorized") - h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) assert.Nil(t, err) - req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) - requests, err = resolvers.VerificationRequests(ctx) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), h)) + requests, err = resolvers.VerificationRequestsResolver(ctx) assert.Nil(t, err) rLen := len(requests) diff --git a/server/__test__/verify_email_test.go b/server/test/verify_email_test.go similarity index 74% rename from server/__test__/verify_email_test.go rename to server/test/verify_email_test.go index aa01619..67affae 100644 --- a/server/__test__/verify_email_test.go +++ b/server/test/verify_email_test.go @@ -3,18 +3,19 @@ package test import ( "testing" + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" "github.com/stretchr/testify/assert" ) -func verifyEmailTest(s TestSetup, t *testing.T) { +func verifyEmailTest(t *testing.T, s TestSetup) { + t.Helper() t.Run(`should verify email`, func(t *testing.T) { _, ctx := createContext(s) email := "verify_email." + s.TestInfo.Email - res, err := resolvers.Signup(ctx, model.SignUpInput{ + res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ Email: email, Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, @@ -23,11 +24,11 @@ func verifyEmailTest(s TestSetup, t *testing.T) { user := *res.User assert.Equal(t, email, user.Email) assert.Nil(t, res.AccessToken, "access token should be nil") - verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String()) + verificationRequest, err := db.Mgr.GetVerificationByEmail(email, constants.VerificationTypeBasicAuthSignup) assert.Nil(t, err) assert.Equal(t, email, verificationRequest.Email) - verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{ + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ Token: verificationRequest.Token, }) assert.Nil(t, err) diff --git a/server/utils/auth_token.go b/server/utils/auth_token.go index 6003291..984ab9f 100644 --- a/server/utils/auth_token.go +++ b/server/utils/auth_token.go @@ -11,17 +11,19 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt" "github.com/robertkrimen/otto" "golang.org/x/crypto/bcrypt" ) -func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) { - t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE)) +// CreateAuthToken util to create JWT token, based on +// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT +func CreateAuthToken(user db.User, tokenType string, roles []string) (string, int64, error) { + t := jwt.New(jwt.GetSigningMethod(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtType).(string))) expiryBound := time.Hour - if tokenType == enum.RefreshToken { + if tokenType == constants.TokenTypeRefreshToken { // expires in 1 year expiryBound = time.Hour * 8760 } @@ -33,12 +35,13 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st var userMap map[string]interface{} json.Unmarshal(userBytes, &userMap) + claimKey := envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtRoleClaim).(string) customClaims := jwt.MapClaims{ - "exp": expiresAt, - "iat": time.Now().Unix(), - "token_type": tokenType.String(), - "allowed_roles": strings.Split(user.Roles, ","), - constants.EnvData.JWT_ROLE_CLAIM: roles, + "exp": expiresAt, + "iat": time.Now().Unix(), + "token_type": tokenType, + "allowed_roles": strings.Split(user.Roles, ","), + claimKey: roles, } for k, v := range userMap { @@ -48,7 +51,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st } // check for the extra access token script - accessTokenScript := os.Getenv("CUSTOM_ACCESS_TOKEN_SCRIPT") + accessTokenScript := os.Getenv(constants.EnvKeyCustomAccessTokenScript) if accessTokenScript != "" { vm := otto.New() @@ -79,7 +82,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st t.Claims = customClaims - token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET)) + token, err := t.SignedString([]byte(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtSecret).(string))) if err != nil { return "", 0, err } @@ -87,6 +90,8 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st return token, expiresAt, nil } +// GetAuthToken helps in getting the JWT token from the +// request cookie or authorization header func GetAuthToken(gc *gin.Context) (string, error) { token, err := GetCookie(gc) if err != nil || token == "" { @@ -101,12 +106,13 @@ func GetAuthToken(gc *gin.Context) (string, error) { return token, nil } +// VerifyAuthToken helps in verifying the JWT token func VerifyAuthToken(token string) (map[string]interface{}, error) { var res map[string]interface{} claims := jwt.MapClaims{} _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { - return []byte(constants.EnvData.JWT_SECRET), nil + return []byte(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtSecret).(string)), nil }) if err != nil { return res, err @@ -126,10 +132,12 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) { return res, nil } -func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, error) { - return HashPassword(constants.EnvData.ADMIN_SECRET) +// CreateAdminAuthToken creates the admin token based on secret key +func CreateAdminAuthToken(tokenType string, c *gin.Context) (string, error) { + return EncryptPassword(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string)) } +// GetAdminAuthToken helps in getting the admin token from the request cookie func GetAdminAuthToken(gc *gin.Context) (string, error) { token, err := GetAdminCookie(gc) if err != nil || token == "" { @@ -143,7 +151,7 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) { return "", err } - err = bcrypt.CompareHashAndPassword([]byte(decodedValue), []byte(constants.EnvData.ADMIN_SECRET)) + err = bcrypt.CompareHashAndPassword([]byte(decodedValue), []byte(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string))) log.Println("error comparing hash:", err) if err != nil { return "", fmt.Errorf(`unauthorized`) diff --git a/server/utils/common.go b/server/utils/common.go index 0a58959..52348bf 100644 --- a/server/utils/common.go +++ b/server/utils/common.go @@ -1,24 +1,11 @@ package utils import ( - "io" - "os" + "github.com/authorizerdev/authorizer/server/db" + "github.com/gin-gonic/gin" ) -func WriteToFile(filename string, data string) error { - file, err := os.Create(filename) - if err != nil { - return err - } - defer file.Close() - - _, err = io.WriteString(file, data) - if err != nil { - return err - } - return file.Sync() -} - +// StringSliceContains checks if a string slice contains a particular string func StringSliceContains(s []string, e string) bool { for _, a := range s { if a == e { @@ -27,3 +14,15 @@ func StringSliceContains(s []string, e string) bool { } return false } + +// SaveSessionInDB saves sessions generated for a given user with meta information +// Not store token here as that could be security breach +func SaveSessionInDB(userId string, c *gin.Context) { + sessionData := db.Session{ + UserID: userId, + UserAgent: GetUserAgent(c.Request), + IP: GetIP(c.Request), + } + + db.Mgr.AddSession(sessionData) +} diff --git a/server/utils/cookie.go b/server/utils/cookie.go index 7319a21..c64e233 100644 --- a/server/utils/cookie.go +++ b/server/utils/cookie.go @@ -4,27 +4,33 @@ import ( "net/http" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/gin-gonic/gin" ) +// SetCookie sets the cookie in the response. It sets 2 cookies +// 1 COOKIE_NAME for the host (abc.com) +// 2 COOKIE_NAME-client for the domain (sub.abc.com). +// Note all sites don't allow 2nd type of cookie func SetCookie(gc *gin.Context, token string) { secure := true httpOnly := true - host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) - domain := GetDomainName(constants.EnvData.AUTHORIZER_URL) + host, _ := GetHostParts(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string)) + domain := GetDomainName(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string)) if domain != "localhost" { domain = "." + domain } gc.SetSameSite(http.SameSiteNoneMode) - gc.SetCookie(constants.EnvData.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly) - gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly) + gc.SetCookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string), token, 3600, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string)+"-client", token, 3600, "/", domain, secure, httpOnly) } +// GetCookie gets the cookie from the request func GetCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(constants.EnvData.COOKIE_NAME) + cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string)) if err != nil { - cookie, err = gc.Request.Cookie(constants.EnvData.COOKIE_NAME + "-client") + cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string) + "-client") if err != nil { return "", err } @@ -33,31 +39,33 @@ func GetCookie(gc *gin.Context) (string, error) { return cookie.Value, nil } +// DeleteCookie sets the cookie value as empty to make it expired func DeleteCookie(gc *gin.Context) { secure := true httpOnly := true - host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) - domain := GetDomainName(constants.EnvData.AUTHORIZER_URL) + host, _ := GetHostParts(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string)) + domain := GetDomainName(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string)) if domain != "localhost" { domain = "." + domain } gc.SetSameSite(http.SameSiteNoneMode) - gc.SetCookie(constants.EnvData.COOKIE_NAME, "", -1, "/", host, secure, httpOnly) - gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly) + gc.SetCookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string), "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyCookieName).(string)+"-client", "", -1, "/", domain, secure, httpOnly) } +// SetAdminCookie sets the admin cookie in the response func SetAdminCookie(gc *gin.Context, token string) { secure := true httpOnly := true - host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) + host, _ := GetHostParts(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string)) - gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, token, 3600, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), token, 3600, "/", host, secure, httpOnly) } func GetAdminCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(constants.EnvData.ADMIN_COOKIE_NAME) + cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string)) if err != nil { return "", err } @@ -67,7 +75,7 @@ func GetAdminCookie(gc *gin.Context) (string, error) { func DeleteAdminCookie(gc *gin.Context) { secure := true httpOnly := true - host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) + host, _ := GetHostParts(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string)) - gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, "", -1, "/", host, secure, httpOnly) + gc.SetCookie(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminCookieName).(string), "", -1, "/", host, secure, httpOnly) } diff --git a/server/utils/create_session.go b/server/utils/create_session.go deleted file mode 100644 index b0283b5..0000000 --- a/server/utils/create_session.go +++ /dev/null @@ -1,16 +0,0 @@ -package utils - -import ( - "github.com/authorizerdev/authorizer/server/db" - "github.com/gin-gonic/gin" -) - -func CreateSession(userId string, c *gin.Context) { - sessionData := db.Session{ - UserID: userId, - UserAgent: GetUserAgent(c.Request), - IP: GetIP(c.Request), - } - - db.Mgr.AddSession(sessionData) -} diff --git a/server/utils/crypto.go b/server/utils/crypto.go index de7ce39..5a872d9 100644 --- a/server/utils/crypto.go +++ b/server/utils/crypto.go @@ -5,15 +5,20 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" + "encoding/json" "io" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "golang.org/x/crypto/bcrypt" ) +// EncryptB64 encrypts data into base64 string func EncryptB64(text string) string { return base64.StdEncoding.EncodeToString([]byte(text)) } +// DecryptB64 decrypts from base64 string to readable string func DecryptB64(s string) (string, error) { data, err := base64.StdEncoding.DecodeString(s) if err != nil { @@ -22,8 +27,9 @@ func DecryptB64(s string) (string, error) { return string(data), nil } +// EncryptAES encrypts data using AES algorithm func EncryptAES(text []byte) ([]byte, error) { - key := []byte(constants.EnvData.ENCRYPTION_KEY) + key := []byte(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyEncryptionKey).(string)) c, err := aes.NewCipher(key) var res []byte if err != nil { @@ -55,8 +61,9 @@ func EncryptAES(text []byte) ([]byte, error) { return gcm.Seal(nonce, nonce, text, nil), nil } +// DecryptAES decrypts data using AES algorithm func DecryptAES(ciphertext []byte) ([]byte, error) { - key := []byte(constants.EnvData.ENCRYPTION_KEY) + key := []byte(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyEncryptionKey).(string)) c, err := aes.NewCipher(key) var res []byte if err != nil { @@ -81,3 +88,39 @@ func DecryptAES(ciphertext []byte) ([]byte, error) { return plaintext, nil } + +// EncryptEnvData is used to encrypt the env data +func EncryptEnvData(data map[string]interface{}) ([]byte, error) { + jsonBytes, err := json.Marshal(data) + if err != nil { + return []byte{}, err + } + + envStoreObj := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + + err = json.Unmarshal(jsonBytes, &envStoreObj) + if err != nil { + return []byte{}, err + } + + configData, err := json.Marshal(envStoreObj) + if err != nil { + return []byte{}, err + } + encryptedConfig, err := EncryptAES(configData) + if err != nil { + return []byte{}, err + } + + return encryptedConfig, nil +} + +// EncryptPassword is used for encrypting password +func EncryptPassword(password string) (string, error) { + pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + + return string(pw), nil +} diff --git a/server/utils/email.go b/server/utils/email.go deleted file mode 100644 index fbb8edf..0000000 --- a/server/utils/email.go +++ /dev/null @@ -1,231 +0,0 @@ -package utils - -import ( - "bytes" - "encoding/json" - "html/template" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/email" -) - -// SendVerificationMail to send verification email -func SendVerificationMail(toEmail, token string) error { - // The receiver needs to be in slice as the receive supports multiple receiver - Receiver := []string{toEmail} - - Subject := "Please verify your email" - message := ` - - - - - - - - - - - - - - -
- - - - - - - -
- - - - - - - -
-
-
- - - ` - data := make(map[string]interface{}, 3) - data["OrgLogo"] = constants.EnvData.ORGANIZATION_LOGO - data["OrgName"] = constants.EnvData.ORGANIZATION_NAME - data["AuthUrl"] = constants.EnvData.AUTHORIZER_URL + "/verify_email?token=" + token - message = AddEmailTemplate(message, data, "verify_email.tmpl") - // bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) - - return email.SendMail(Receiver, Subject, message) -} - -// SendForgotPasswordMail to send verification email -func SendForgotPasswordMail(toEmail, token, host string) error { - if constants.EnvData.RESET_PASSWORD_URL == "" { - constants.EnvData.RESET_PASSWORD_URL = constants.EnvData.AUTHORIZER_URL + "/app/reset-password" - } - - // The receiver needs to be in slice as the receive supports multiple receiver - Receiver := []string{toEmail} - - Subject := "Reset Password" - - message := ` - - - - - - - - - - - - - - -
- - - - - - - -
- - - - - - - -
-
-
- - - ` - - data := make(map[string]interface{}, 3) - data["OrgLogo"] = constants.EnvData.ORGANIZATION_LOGO - data["ToEmail"] = constants.EnvData.ORGANIZATION_NAME - data["AuthUrl"] = constants.EnvData.RESET_PASSWORD_URL + "?token=" + token - message = AddEmailTemplate(message, data, "reset_password_email.tmpl") - - return email.SendMail(Receiver, Subject, message) -} - -func AddEmailTemplate(a string, b map[string]interface{}, templateName string) string { - tmpl, err := template.New(templateName).Parse(a) - if err != nil { - output, _ := json.Marshal(b) - return string(output) - } - buf := &bytes.Buffer{} - err = tmpl.Execute(buf, b) - if err != nil { - panic(err) - } - s := buf.String() - return s -} diff --git a/server/utils/encrypt_config.go b/server/utils/encrypt_config.go index 692321f..9cbed94 100644 --- a/server/utils/encrypt_config.go +++ b/server/utils/encrypt_config.go @@ -3,7 +3,7 @@ package utils import ( "encoding/json" - "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" ) func EncryptConfig(data map[string]interface{}) ([]byte, error) { @@ -12,12 +12,14 @@ func EncryptConfig(data map[string]interface{}) ([]byte, error) { return []byte{}, err } - err = json.Unmarshal(jsonBytes, &constants.EnvData) + envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() + + err = json.Unmarshal(jsonBytes, &envData) if err != nil { return []byte{}, err } - configData, err := json.Marshal(constants.EnvData) + configData, err := json.Marshal(envData) if err != nil { return []byte{}, err } diff --git a/server/utils/get_response_user_data.go b/server/utils/get_response_user_data.go index 2c34ccd..ed3a8f2 100644 --- a/server/utils/get_response_user_data.go +++ b/server/utils/get_response_user_data.go @@ -7,6 +7,9 @@ import ( "github.com/authorizerdev/authorizer/server/graph/model" ) +// TODO move this to provider +// rename it to AsAPIUser + func GetResponseUserData(user db.User) *model.User { isEmailVerified := user.EmailVerifiedAt != nil isPhoneVerified := user.PhoneNumberVerifiedAt != nil diff --git a/server/utils/gin_context.go b/server/utils/gin_context.go index cdcf1e1..72fd480 100644 --- a/server/utils/gin_context.go +++ b/server/utils/gin_context.go @@ -7,6 +7,9 @@ import ( "github.com/gin-gonic/gin" ) +// TODO renamae GinContextKey -> GinContext + +// GinContext to get gin context from context func GinContextFromContext(ctx context.Context) (*gin.Context, error) { ginContext := ctx.Value("GinContextKey") if ginContext == nil { diff --git a/server/utils/hash_password.go b/server/utils/hash_password.go deleted file mode 100644 index 049b4d6..0000000 --- a/server/utils/hash_password.go +++ /dev/null @@ -1,12 +0,0 @@ -package utils - -import "golang.org/x/crypto/bcrypt" - -func HashPassword(password string) (string, error) { - pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return "", err - } - - return string(pw), nil -} diff --git a/server/utils/init_server.go b/server/utils/init_server.go deleted file mode 100644 index 9315314..0000000 --- a/server/utils/init_server.go +++ /dev/null @@ -1,6 +0,0 @@ -package utils - -// any jobs that we want to run at start of server can be executed here - -func InitServer() { -} diff --git a/server/utils/meta.go b/server/utils/meta.go index 9df8022..888aca3 100644 --- a/server/utils/meta.go +++ b/server/utils/meta.go @@ -2,19 +2,19 @@ package utils import ( "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/graph/model" ) -// GetMeta helps in getting the meta data about the deployment -// version, +// GetMeta helps in getting the meta data about the deployment from EnvData func GetMetaInfo() model.Meta { return model.Meta{ - Version: constants.VERSION, - IsGoogleLoginEnabled: constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "", - IsGithubLoginEnabled: constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "", - IsFacebookLoginEnabled: constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "", - IsBasicAuthenticationEnabled: !constants.DISABLE_BASIC_AUTHENTICATION, - IsEmailVerificationEnabled: !constants.DISABLE_EMAIL_VERIFICATION, - IsMagicLinkLoginEnabled: !constants.DISABLE_MAGIC_LINK_LOGIN, + Version: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyVersion).(string), + IsGoogleLoginEnabled: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGoogleClientID).(string) != "" && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGoogleClientSecret).(string) != "", + IsGithubLoginEnabled: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGithubClientID).(string) != "" && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyGithubClientSecret).(string) != "", + IsFacebookLoginEnabled: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyFacebookClientID).(string) != "" && envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyFacebookClientSecret).(string) != "", + IsBasicAuthenticationEnabled: !envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableBasicAuthentication).(bool), + IsEmailVerificationEnabled: !envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableEmailVerification).(bool), + IsMagicLinkLoginEnabled: !envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyDisableMagicLinkLogin).(bool), } } diff --git a/server/utils/request_info.go b/server/utils/request_info.go index 226078e..fd1f540 100644 --- a/server/utils/request_info.go +++ b/server/utils/request_info.go @@ -2,6 +2,7 @@ package utils import "net/http" +// GetIP helps in getting the IP address from the request func GetIP(r *http.Request) string { IPAddress := r.Header.Get("X-Real-Ip") if IPAddress == "" { @@ -14,6 +15,7 @@ func GetIP(r *http.Request) string { return IPAddress } +// GetUserAgent helps in getting the user agent from the request func GetUserAgent(r *http.Request) string { return r.UserAgent() } diff --git a/server/utils/validator.go b/server/utils/validator.go index 74b718a..e51bc16 100644 --- a/server/utils/validator.go +++ b/server/utils/validator.go @@ -6,17 +6,20 @@ import ( "strings" "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/enum" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/gin-gonic/gin" ) +// IsValidEmail validates email func IsValidEmail(email string) bool { _, err := mail.ParseAddress(email) return err == nil } +// IsValidOrigin validates origin based on ALLOWED_ORIGINS func IsValidOrigin(url string) bool { - if len(constants.EnvData.ALLOWED_ORIGINS) == 1 && constants.EnvData.ALLOWED_ORIGINS[0] == "*" { + allowedOrigins := envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAllowedOrigins).([]string) + if len(allowedOrigins) == 1 && allowedOrigins[0] == "*" { return true } @@ -24,7 +27,7 @@ func IsValidOrigin(url string) bool { hostName, port := GetHostParts(url) currentOrigin := hostName + ":" + port - for _, origin := range constants.EnvData.ALLOWED_ORIGINS { + for _, origin := range allowedOrigins { replacedString := origin // if has regex whitelisted domains if strings.Contains(origin, "*") { @@ -49,6 +52,7 @@ func IsValidOrigin(url string) bool { return hasValidURL } +// IsSuperAdmin checks if user is super admin func IsSuperAdmin(gc *gin.Context) bool { token, err := GetAdminAuthToken(gc) if err != nil { @@ -57,12 +61,13 @@ func IsSuperAdmin(gc *gin.Context) bool { return false } - return secret == constants.EnvData.ADMIN_SECRET + return secret == envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAdminSecret).(string) } return token != "" } +// IsValidRoles validates roles func IsValidRoles(userRoles []string, roles []string) bool { valid := true for _, role := range roles { @@ -75,13 +80,17 @@ func IsValidRoles(userRoles []string, roles []string) bool { return valid } +// IsValidVerificationIdentifier validates verification identifier that is used to identify +// the type of verification request func IsValidVerificationIdentifier(identifier string) bool { - if identifier != enum.BasicAuthSignup.String() && identifier != enum.ForgotPassword.String() && identifier != enum.UpdateEmail.String() { + if identifier != constants.VerificationTypeBasicAuthSignup && identifier != constants.VerificationTypeForgotPassword && identifier != constants.VerificationTypeUpdateEmail { return false } return true } +// IsStringArrayEqual validates if string array are equal. +// This does check if the order is same func IsStringArrayEqual(a, b []string) bool { if len(a) != len(b) { return false diff --git a/server/utils/verification_token.go b/server/utils/verification_token.go index 254e8ac..73a1c7b 100644 --- a/server/utils/verification_token.go +++ b/server/utils/verification_token.go @@ -4,39 +4,46 @@ import ( "time" "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" "github.com/golang-jwt/jwt" ) +// TODO see if we can move this to different service + +// UserInfo is the user info that is stored in the JWT of verification request type UserInfo struct { Email string `json:"email"` Host string `json:"host"` RedirectURL string `json:"redirect_url"` } +// CustomClaim is the custom claim that is stored in the JWT of verification request type CustomClaim struct { *jwt.StandardClaims TokenType string `json:"token_type"` UserInfo } +// CreateVerificationToken creates a verification JWT token func CreateVerificationToken(email string, tokenType string) (string, error) { - t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE)) + t := jwt.New(jwt.GetSigningMethod(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtType).(string))) t.Claims = &CustomClaim{ &jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), }, tokenType, - UserInfo{Email: email, Host: constants.EnvData.AUTHORIZER_URL, RedirectURL: constants.EnvData.APP_URL}, + UserInfo{Email: email, Host: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAuthorizerURL).(string), RedirectURL: envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyAppURL).(string)}, } - return t.SignedString([]byte(constants.EnvData.JWT_SECRET)) + return t.SignedString([]byte(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtSecret).(string))) } +// VerifyVerificationToken verifies the verification JWT token func VerifyVerificationToken(token string) (*CustomClaim, error) { claims := &CustomClaim{} _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { - return []byte(constants.EnvData.JWT_SECRET), nil + return []byte(envstore.EnvInMemoryStoreObj.GetEnvVariable(constants.EnvKeyJwtSecret).(string)), nil }) if err != nil { return claims, err