diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fac4792..74ce864 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,7 +23,8 @@ jobs: sudo mv github-assets-uploader /usr/sbin/ && \ sudo rm -f github-assets-uploader.tar.gz && \ github-assets-uploader -version && \ - make build-app + make build-app && \ + make build-dashboard - name: Print Go paths run: whereis go - name: Print Go Version @@ -37,12 +38,12 @@ jobs: make clean && \ CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \ mv build/server build/server.exe && \ - zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates + zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates dashboard/build - name: Package files for linux run: | make clean && \ CGO_ENABLED=1 make && \ - tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates + tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates dashboard/build - name: Upload assets run: | github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \ diff --git a/.gitignore b/.gitignore index 30b32c7..bab8c91 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ server/.env data app/node_modules app/build +dashboard/node_modules +dashboard/build build .env data.db diff --git a/Dockerfile b/Dockerfile index a60cab8..d0045c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,14 +14,17 @@ RUN apk add build-base &&\ FROM node:17-alpine3.12 as node-builder WORKDIR /authorizer COPY app app +COPY dashboard dashboard COPY Makefile . RUN apk add build-base &&\ - make build-app + make build-app && \ + make build-dashboard FROM alpine:latest WORKDIR /root/ -RUN mkdir app +RUN mkdir app dashboard COPY --from=node-builder /authorizer/app/build app/build +COPY --from=node-builder /authorizer/dashboard/build dashboard/build COPY --from=go-builder /authorizer/build build COPY templates templates EXPOSE 8080 diff --git a/Makefile b/Makefile index 4da765e..e45a5d5 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ cmd: cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server' build-app: cd app && npm i && npm run build +build-dashboard: + cd dashboard && npm i && npm run build clean: rm -rf build test: diff --git a/app/README.md b/app/README.md index 2a33c8e..15ac1e9 100644 --- a/app/README.md +++ b/app/README.md @@ -1,3 +1,14 @@ # Authorizer APP -App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js \ No newline at end of file +App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js + +### Getting started + +**Setting up locally** + +- `cd app` +- `npm start` + +**Creating production build** + +- `make build-app` diff --git a/dashboard/README.md b/dashboard/README.md new file mode 100644 index 0000000..863e9dc --- /dev/null +++ b/dashboard/README.md @@ -0,0 +1,12 @@ +# Authorizer dashboard + +### Getting started + +**Setting up locally** + +- `cd dashboard` +- `npm start` + +**Creating production build** + +- `make build-dashboard` diff --git a/dashboard/esbuild.config.js b/dashboard/esbuild.config.js new file mode 100644 index 0000000..40fe839 --- /dev/null +++ b/dashboard/esbuild.config.js @@ -0,0 +1,12 @@ +const __is_prod__ = process.env.NODE_ENV === 'production'; +require('esbuild').build({ + entryPoints: ['src/index.tsx'], + chunkNames: '[name]-[hash]', + bundle: true, + minify: __is_prod__, + outdir: 'build', + splitting: true, + format: 'esm', + watch: !__is_prod__, + logLevel: 'info', +}); diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json new file mode 100644 index 0000000..10b7230 --- /dev/null +++ b/dashboard/package-lock.json @@ -0,0 +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==" + } + } +} diff --git a/dashboard/package.json b/dashboard/package.json new file mode 100644 index 0000000..ba96a12 --- /dev/null +++ b/dashboard/package.json @@ -0,0 +1,30 @@ +{ + "name": "dashboard", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js", + "start": "NODE_ENV=development node ./esbuild.config.js" + }, + "keywords": [], + "author": "Lakhan Samani", + "license": "ISC", + "dependencies": { + "@chakra-ui/react": "^1.7.3", + "@emotion/react": "^11.7.1", + "@emotion/styled": "^11.6.0", + "@types/react": "^17.0.38", + "@types/react-dom": "^17.0.11", + "@types/react-router-dom": "^5.3.2", + "esbuild": "^0.14.9", + "framer-motion": "^5.5.5", + "graphql": "^16.2.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-icons": "^4.3.1", + "react-router-dom": "^6.2.1", + "typescript": "^4.5.4", + "urql": "^2.0.6" + } +} diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx new file mode 100644 index 0000000..4effe54 --- /dev/null +++ b/dashboard/src/App.tsx @@ -0,0 +1,44 @@ +import * as React from "react"; +import { ChakraProvider, extendTheme } from "@chakra-ui/react"; +import { BrowserRouter } from "react-router-dom"; +import { createClient, Provider } from "urql"; +import {AppRoutes} from './routes' +import { AuthContainer } from "./containers/AuthContainer"; + +const queryClient = createClient({ + url: "/graphql", + fetchOptions: () => { + return { + credentials: "include", + }; + }, +}); + +const theme = extendTheme({ + styles: { + global: { + "html, body, #root": { + height: "100%", + }, + }, + }, + colors: { + blue: { + 500: "rgb(59,130,246)", + }, + }, +}); + +export default function App() { + return ( + + + + + + + + + + ); +} diff --git a/dashboard/src/components/Sidebar.tsx b/dashboard/src/components/Sidebar.tsx new file mode 100644 index 0000000..d4fbb13 --- /dev/null +++ b/dashboard/src/components/Sidebar.tsx @@ -0,0 +1,54 @@ +import { Box, Image, Link, Text } from "@chakra-ui/react"; +import { NavLink, useLocation } from "react-router-dom"; +import React from "react"; +import { LOGO_URL } from "../constants"; + +const routes = [ + { + route: "/users", + name: "Users", + }, + { + route: "/settings", + name: "Settings", + }, +]; + +export const Sidebar = () => { + const { pathname } = useLocation(); + return ( + + + + + + Authorizer + + + + {routes.map(({ route, name }: any) => ( + + {name} + + ))} + + ); +}; diff --git a/dashboard/src/constants.ts b/dashboard/src/constants.ts new file mode 100644 index 0000000..2a2eacc --- /dev/null +++ b/dashboard/src/constants.ts @@ -0,0 +1 @@ +export const LOGO_URL = "https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png" \ No newline at end of file diff --git a/dashboard/src/containers/AuthContainer.tsx b/dashboard/src/containers/AuthContainer.tsx new file mode 100644 index 0000000..e8aad71 --- /dev/null +++ b/dashboard/src/containers/AuthContainer.tsx @@ -0,0 +1,37 @@ +import { Center, Spinner } from "@chakra-ui/react"; +import React from "react"; +import { Navigate, Route, Routes } from "react-router-dom"; +import { useLocation } from "react-router-dom"; +import { useQuery } from "urql"; +import { AdminSessionQuery } from "../graphql/queries"; +import { hasAdminSecret } from "../utils"; + +export const AuthContainer = ({ children }: { children: any }) => { + const { pathname } = useLocation(); + const isOnboardingComplete = hasAdminSecret(); + const [result] = useQuery({ + query: AdminSessionQuery, + pause: !isOnboardingComplete, + }); + + if (result.fetching) { + return ( +
+ +
+ ); + } + + if ( + result?.error?.message.includes("unauthorized") && + pathname !== "/login" + ) { + return ; + } + + if (!isOnboardingComplete && pathname !== "/setup") { + return ; + } + + return children; +}; diff --git a/dashboard/src/graphql/mutation/index.ts b/dashboard/src/graphql/mutation/index.ts new file mode 100644 index 0000000..01fcacc --- /dev/null +++ b/dashboard/src/graphql/mutation/index.ts @@ -0,0 +1,15 @@ +export const AdminSignup = ` + mutation adminSignup($secret: String!) { + _admin_signup (params: {admin_secret: $secret}) { + message + } + } +`; + +export const AdminLogin = ` +mutation adminLogin($secret: String!){ + _admin_login(params: { admin_secret: $secret }) { + message + } +} +` \ No newline at end of file diff --git a/dashboard/src/graphql/queries/index.ts b/dashboard/src/graphql/queries/index.ts new file mode 100644 index 0000000..837dbbc --- /dev/null +++ b/dashboard/src/graphql/queries/index.ts @@ -0,0 +1,7 @@ +export const AdminSessionQuery = ` + query { + _admin_session{ + message + } + } +`; diff --git a/dashboard/src/index.tsx b/dashboard/src/index.tsx new file mode 100644 index 0000000..b597a44 --- /dev/null +++ b/dashboard/src/index.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render(, document.getElementById('root')); diff --git a/dashboard/src/layouts/AuthLayout.tsx b/dashboard/src/layouts/AuthLayout.tsx new file mode 100644 index 0000000..10c73bd --- /dev/null +++ b/dashboard/src/layouts/AuthLayout.tsx @@ -0,0 +1,29 @@ +import { Box, Center, Flex, Image, Text } from "@chakra-ui/react"; +import React from "react"; +import { LOGO_URL } from "../constants"; + +export function AuthLayout({ children }: { children: React.ReactNode }) { + return ( + +
+ + + + Authorizer + +
+
+ {children} +
+
+ ); +} diff --git a/dashboard/src/layouts/DashboardLayout.tsx b/dashboard/src/layouts/DashboardLayout.tsx new file mode 100644 index 0000000..d3b663f --- /dev/null +++ b/dashboard/src/layouts/DashboardLayout.tsx @@ -0,0 +1,14 @@ +import { Box, Flex } from "@chakra-ui/react"; +import React from "react"; +import { Sidebar } from "../components/Sidebar"; + +export function DashboardLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + {children} + + ); +} diff --git a/dashboard/src/pages/Auth.tsx b/dashboard/src/pages/Auth.tsx new file mode 100644 index 0000000..0db905f --- /dev/null +++ b/dashboard/src/pages/Auth.tsx @@ -0,0 +1,90 @@ +import { + Button, + FormControl, + FormLabel, + Input, + useToast, + VStack, +} from "@chakra-ui/react"; +import React, { useEffect } from "react"; +import { useMutation } from "urql"; +import { AuthLayout } from "../layouts/AuthLayout"; +import { AdminLogin, AdminSignup } from "../graphql/mutation"; +import { useLocation, useNavigate } from "react-router-dom"; + +export const Auth = () => { + const [loginResult, login] = useMutation(AdminLogin); + const [signUpResult, signup] = useMutation(AdminSignup); + + const toast = useToast(); + const navigate = useNavigate() + const { pathname } = useLocation(); + const isLogin = pathname === "/login"; + + const handleAdminSecret = (e: any) => { + e.preventDefault(); + const formValues = [...e.target.elements].reduce((agg: any, elem: any) => { + if (elem.id) { + return { + ...agg, + [elem.id]: elem.value, + }; + } + + return agg; + }, {}); + + (isLogin ? login : signup)({ + secret: formValues["admin-secret"], + }).then((res) => { + if (!res.error?.name) { + navigate("/"); + } + }); + }; + + const errors = isLogin ? loginResult.error : signUpResult.error; + + useEffect(() => { + if (errors?.graphQLErrors) { + (errors?.graphQLErrors || []).map((error: any) => { + toast({ + title: error.message, + isClosable: true, + status: "error", + position:"bottom-right" + }); + }) + } + }, [errors]) + + return ( + +
+ + + + {isLogin ? "Enter" : "Setup"} Admin Secret + + + + + +
+
+ ); +}; diff --git a/dashboard/src/pages/Home.tsx b/dashboard/src/pages/Home.tsx new file mode 100644 index 0000000..2fd4335 --- /dev/null +++ b/dashboard/src/pages/Home.tsx @@ -0,0 +1,6 @@ +import { Box } from "@chakra-ui/react"; +import React from "react"; + +export function Home() { + return Welcome to Authorizer dashboard!; +} diff --git a/dashboard/src/pages/Users.tsx b/dashboard/src/pages/Users.tsx new file mode 100644 index 0000000..3f97712 --- /dev/null +++ b/dashboard/src/pages/Users.tsx @@ -0,0 +1,6 @@ +import { Box } from "@chakra-ui/react"; +import React from "react"; + +export function Users() { + return users; +} diff --git a/dashboard/src/routes/index.tsx b/dashboard/src/routes/index.tsx new file mode 100644 index 0000000..3c1e2f9 --- /dev/null +++ b/dashboard/src/routes/index.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Outlet, Route, Routes } from "react-router-dom"; +import { DashboardLayout } from "../layouts/DashboardLayout"; +import { Auth } from "../pages/Auth"; + +import { Home } from "../pages/Home"; +import { Users } from "../pages/Users"; + +export const AppRoutes = () => { + return ( + + } /> + } /> + + + + } + > + } /> + } /> + + + ); +}; diff --git a/dashboard/src/utils/index.ts b/dashboard/src/utils/index.ts new file mode 100644 index 0000000..cab1ebf --- /dev/null +++ b/dashboard/src/utils/index.ts @@ -0,0 +1,3 @@ +export const hasAdminSecret = () => { + return (window)["__authorizer__"].isOnboardingCompleted === true +} \ No newline at end of file diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json new file mode 100644 index 0000000..a752b14 --- /dev/null +++ b/dashboard/tsconfig.json @@ -0,0 +1,72 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + // "lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */, + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/scripts/build-mac.sh b/scripts/build-mac.sh index 1e3f4a2..e41d529 100644 --- a/scripts/build-mac.sh +++ b/scripts/build-mac.sh @@ -1,7 +1,7 @@ VERSION="$1" make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz -tar cvfz ${FILE_NAME} .env app/build build templates +tar cvfz ${FILE_NAME} .env app/build build templates dashboard/build AUTH="Authorization: token $GITHUB_TOKEN" RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION}) echo $RELASE_INFO diff --git a/server/__test__/admin_login_test.go b/server/__test__/admin_login_test.go new file mode 100644 index 0000000..eefd8b9 --- /dev/null +++ b/server/__test__/admin_login_test.go @@ -0,0 +1,27 @@ +package test + +import ( + "testing" + + "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 adminLoginTests(s TestSetup, t *testing.T) { + t.Run(`should complete admin login`, func(t *testing.T) { + _, ctx := createContext(s) + _, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{ + AdminSecret: "admin_test", + }) + + assert.NotNil(t, err) + + _, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{ + AdminSecret: constants.EnvData.ADMIN_SECRET, + }) + + 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..c548368 --- /dev/null +++ b/server/__test__/admin_logout_test.go @@ -0,0 +1,26 @@ +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 new file mode 100644 index 0000000..afc0179 --- /dev/null +++ b/server/__test__/admin_session_test.go @@ -0,0 +1,28 @@ +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__/admin_signup_test.go b/server/__test__/admin_signup_test.go new file mode 100644 index 0000000..441533e --- /dev/null +++ b/server/__test__/admin_signup_test.go @@ -0,0 +1,31 @@ +package test + +import ( + "log" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "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) { + 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 = "" + + _, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ + AdminSecret: uuid.New().String(), + }) + + assert.Nil(t, err) + }) +} diff --git a/server/__test__/config_test.go b/server/__test__/config_test.go new file mode 100644 index 0000000..5cf6d2c --- /dev/null +++ b/server/__test__/config_test.go @@ -0,0 +1,29 @@ +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 configTests(s TestSetup, t *testing.T) { + 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) + assert.Nil(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h)) + res, err := resolvers.ConfigResolver(ctx) + + assert.Nil(t, err) + assert.Equal(t, *res.AdminSecret, constants.EnvData.ADMIN_SECRET) + }) +} diff --git a/server/__test__/delete_user_test.go b/server/__test__/delete_user_test.go index 4f306dc..ae891d1 100644 --- a/server/__test__/delete_user_test.go +++ b/server/__test__/delete_user_test.go @@ -1,11 +1,13 @@ package test import ( + "fmt" "testing" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -24,7 +26,10 @@ func deleteUserTest(s TestSetup, t *testing.T) { }) assert.NotNil(t, err, "unauthorized") - req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) + 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.DeleteUser(ctx, model.DeleteUserInput{ Email: email, }) diff --git a/server/__test__/env_test.go b/server/__test__/env_test.go index 690ad51..328d5f6 100644 --- a/server/__test__/env_test.go +++ b/server/__test__/env_test.go @@ -8,18 +8,18 @@ import ( ) func TestEnvs(t *testing.T) { - constants.ENV_PATH = "../../.env.sample" + constants.EnvData.ENV_PATH = "../../.env.sample" - assert.Equal(t, constants.ADMIN_SECRET, "admin") - assert.Equal(t, constants.ENV, "production") - assert.False(t, constants.DISABLE_EMAIL_VERIFICATION) - assert.False(t, constants.DISABLE_MAGIC_LINK_LOGIN) - assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION) - assert.Equal(t, constants.JWT_TYPE, "HS256") - assert.Equal(t, constants.JWT_SECRET, "random_string") - assert.Equal(t, constants.JWT_ROLE_CLAIM, "role") - assert.EqualValues(t, constants.ROLES, []string{"user"}) - assert.EqualValues(t, constants.DEFAULT_ROLES, []string{"user"}) - assert.EqualValues(t, constants.PROTECTED_ROLES, []string{"admin"}) - assert.EqualValues(t, constants.ALLOWED_ORIGINS, []string{"*"}) + 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__/logout_test.go b/server/__test__/logout_test.go index 634e036..9788279 100644 --- a/server/__test__/logout_test.go +++ b/server/__test__/logout_test.go @@ -1,8 +1,10 @@ package test import ( + "fmt" "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" @@ -25,7 +27,7 @@ func logoutTests(s TestSetup, t *testing.T) { }) token := *verifyRes.AccessToken - req.Header.Add("Authorization", "Bearer "+token) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) _, err = resolvers.Logout(ctx) assert.Nil(t, err) _, err = resolvers.Profile(ctx) diff --git a/server/__test__/magic_link_login_test.go b/server/__test__/magic_link_login_test.go index c1771cf..9bc3c0d 100644 --- a/server/__test__/magic_link_login_test.go +++ b/server/__test__/magic_link_login_test.go @@ -1,8 +1,10 @@ package test import ( + "fmt" "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" @@ -26,7 +28,7 @@ func magicLinkLoginTests(s TestSetup, t *testing.T) { }) token := *verifyRes.AccessToken - req.Header.Add("Authorization", "Bearer "+token) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) _, err = resolvers.Profile(ctx) assert.Nil(t, err) diff --git a/server/__test__/meta_test.go b/server/__test__/meta_test.go index f4167c1..9989141 100644 --- a/server/__test__/meta_test.go +++ b/server/__test__/meta_test.go @@ -2,6 +2,7 @@ package test import ( "context" + "log" "testing" "github.com/authorizerdev/authorizer/server/resolvers" @@ -12,6 +13,7 @@ func metaTests(s TestSetup, t *testing.T) { t.Run(`should get meta information`, func(t *testing.T) { ctx := context.Background() meta, err := resolvers.Meta(ctx) + log.Println("=> meta:", meta) 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 index 8693f37..2c6f853 100644 --- a/server/__test__/profile_test.go +++ b/server/__test__/profile_test.go @@ -1,8 +1,10 @@ package test import ( + "fmt" "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" @@ -30,7 +32,7 @@ func profileTests(s TestSetup, t *testing.T) { }) token := *verifyRes.AccessToken - req.Header.Add("Authorization", "Bearer "+token) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) profileRes, err := resolvers.Profile(ctx) assert.Nil(t, err) diff --git a/server/__test__/resolvers_test.go b/server/__test__/resolvers_test.go index 7eb73d5..e756672 100644 --- a/server/__test__/resolvers_test.go +++ b/server/__test__/resolvers_test.go @@ -1,11 +1,13 @@ 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) { @@ -16,14 +18,36 @@ func TestResolvers(t *testing.T) { } for dbType, dbURL := range databases { - constants.DATABASE_URL = dbURL - constants.DATABASE_TYPE = dbType + 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) @@ -36,12 +60,6 @@ func TestResolvers(t *testing.T) { magicLinkLoginTests(s, t) logoutTests(s, t) metaTests(s, t) - - // admin tests - verificationRequestsTest(s, t) - usersTest(s, t) - deleteUserTest(s, t) - updateUserTest(s, t) }) } } diff --git a/server/__test__/session_test.go b/server/__test__/session_test.go index 20f02ba..ae764cc 100644 --- a/server/__test__/session_test.go +++ b/server/__test__/session_test.go @@ -1,8 +1,10 @@ package test import ( + "fmt" "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" @@ -30,7 +32,8 @@ func sessionTests(s TestSetup, t *testing.T) { }) token := *verifyRes.AccessToken - req.Header.Add("Authorization", "Bearer "+token) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) + sessionRes, err := resolvers.Session(ctx, []string{}) assert.Nil(t, err) diff --git a/server/__test__/test.go b/server/__test__/test.go index b43232f..39848f6 100644 --- a/server/__test__/test.go +++ b/server/__test__/test.go @@ -72,7 +72,8 @@ func testSetup() TestSetup { Password: "test", } - constants.ENV_PATH = "../../.env.sample" + constants.EnvData.ENV_PATH = "../../.env.sample" + env.InitEnv() session.InitSession() diff --git a/server/__test__/update_config_test.go b/server/__test__/update_config_test.go new file mode 100644 index 0000000..65eda37 --- /dev/null +++ b/server/__test__/update_config_test.go @@ -0,0 +1,44 @@ +package test + +import ( + "fmt" + "log" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "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) { + 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) + + 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)) + newURL := "https://test.com" + data = model.UpdateConfigInput{ + AppURL: &newURL, + } + _, 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) + data = model.UpdateConfigInput{ + AppURL: &originalAppURL, + } + _, err = resolvers.UpdateConfigResolver(ctx, data) + assert.Nil(t, err) + }) +} diff --git a/server/__test__/update_profile_test.go b/server/__test__/update_profile_test.go index 7d6d4d1..6a2e3d4 100644 --- a/server/__test__/update_profile_test.go +++ b/server/__test__/update_profile_test.go @@ -1,8 +1,10 @@ package test import ( + "fmt" "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" @@ -33,7 +35,7 @@ func updateProfileTests(s TestSetup, t *testing.T) { }) token := *verifyRes.AccessToken - req.Header.Add("Authorization", "Bearer "+token) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token)) _, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{ FamilyName: &fName, }) diff --git a/server/__test__/update_user_test.go b/server/__test__/update_user_test.go index 45d614a..26a9583 100644 --- a/server/__test__/update_user_test.go +++ b/server/__test__/update_user_test.go @@ -1,11 +1,13 @@ package test import ( + "fmt" "testing" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -29,7 +31,9 @@ func updateUserTest(s TestSetup, t *testing.T) { }) assert.NotNil(t, err, "unauthorized") - req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) + 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.UpdateUser(ctx, model.UpdateUserInput{ ID: user.ID, Roles: newRoles, diff --git a/server/__test__/users_test.go b/server/__test__/users_test.go index 6386a8e..abf2741 100644 --- a/server/__test__/users_test.go +++ b/server/__test__/users_test.go @@ -1,11 +1,13 @@ package test import ( + "fmt" "testing" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -22,7 +24,9 @@ func usersTest(s TestSetup, t *testing.T) { users, err := resolvers.Users(ctx) assert.NotNil(t, err, "unauthorized") - req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) + 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)) users, err = resolvers.Users(ctx) assert.Nil(t, err) rLen := len(users) diff --git a/server/__test__/validator_test.go b/server/__test__/validator_test.go index 4999716..7145d74 100644 --- a/server/__test__/validator_test.go +++ b/server/__test__/validator_test.go @@ -22,7 +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.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"} + constants.EnvData.ALLOWED_ORIGINS = []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") diff --git a/server/__test__/verification_requests_test.go b/server/__test__/verification_requests_test.go index 22fc26e..21f2886 100644 --- a/server/__test__/verification_requests_test.go +++ b/server/__test__/verification_requests_test.go @@ -1,11 +1,13 @@ package test import ( + "fmt" "testing" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/utils" "github.com/stretchr/testify/assert" ) @@ -23,7 +25,9 @@ func verificationRequestsTest(s TestSetup, t *testing.T) { requests, err := resolvers.VerificationRequests(ctx) assert.NotNil(t, err, "unauthorizer") - req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) + 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)) requests, err = resolvers.VerificationRequests(ctx) assert.Nil(t, err) diff --git a/server/constants/constants.go b/server/constants/constants.go index cc50b0e..a2bc497 100644 --- a/server/constants/constants.go +++ b/server/constants/constants.go @@ -1,51 +1,62 @@ package constants -// this constants are configured via env -var ( - ADMIN_SECRET = "" - ENV = "" - ENV_PATH = "" - VERSION = "" - DATABASE_TYPE = "" - DATABASE_URL = "" - DATABASE_NAME = "" - SMTP_HOST = "" - SMTP_PORT = "" - SMTP_USERNAME = "" - SMTP_PASSWORD = "" - SENDER_EMAIL = "" - JWT_TYPE = "" - JWT_SECRET = "" - ALLOWED_ORIGINS = []string{} - AUTHORIZER_URL = "" - APP_URL = "" - PORT = "" - REDIS_URL = "" - IS_PROD = false - COOKIE_NAME = "" - RESET_PASSWORD_URL = "" - DISABLE_EMAIL_VERIFICATION = false - DISABLE_BASIC_AUTHENTICATION = false - DISABLE_MAGIC_LINK_LOGIN = false - DISABLE_LOGIN_PAGE = false +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 = "role" + ROLES []string + PROTECTED_ROLES []string + DEFAULT_ROLES []string + JWT_ROLE_CLAIM string // OAuth login - GOOGLE_CLIENT_ID = "" - GOOGLE_CLIENT_SECRET = "" - GITHUB_CLIENT_ID = "" - GITHUB_CLIENT_SECRET = "" - FACEBOOK_CLIENT_ID = "" - FACEBOOK_CLIENT_SECRET = "" - TWITTER_CLIENT_ID = "" - TWITTER_CLIENT_SECRET = "" + 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 = "Authorizer" - ORGANIZATION_LOGO = "https://authorizer.dev/images/logo.png" -) + 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/db/arangodb.go b/server/db/arangodb.go index 1d624ed..f6cdb06 100644 --- a/server/db/arangodb.go +++ b/server/db/arangodb.go @@ -17,7 +17,7 @@ import ( func initArangodb() (arangoDriver.Database, error) { ctx := context.Background() conn, err := http.NewConnection(http.ConnectionConfig{ - Endpoints: []string{constants.DATABASE_URL}, + Endpoints: []string{constants.EnvData.DATABASE_URL}, }) if err != nil { return nil, err @@ -32,16 +32,16 @@ func initArangodb() (arangoDriver.Database, error) { var arangodb driver.Database - arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.DATABASE_NAME) + arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.EnvData.DATABASE_NAME) if arangodb_exists { - log.Println(constants.DATABASE_NAME + " db exists already") - arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME) + log.Println(constants.EnvData.DATABASE_NAME + " db exists already") + arangodb, err = arangoClient.Database(nil, constants.EnvData.DATABASE_NAME) if err != nil { return nil, err } } else { - arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil) + arangodb, err = arangoClient.CreateDatabase(nil, constants.EnvData.DATABASE_NAME, nil) if err != nil { return nil, err } @@ -100,5 +100,15 @@ func initArangodb() (arangoDriver.Database, error) { Sparse: true, }) + configCollectionExists, err := arangodb.CollectionExists(ctx, Collections.Config) + if configCollectionExists { + log.Println(Collections.Config + " collection exists already") + } else { + _, err = arangodb.CreateCollection(ctx, Collections.Config, nil) + if err != nil { + log.Println("error creating collection("+Collections.Config+"):", err) + } + } + return arangodb, err } diff --git a/server/db/config.go b/server/db/config.go new file mode 100644 index 0000000..d4f724a --- /dev/null +++ b/server/db/config.go @@ -0,0 +1,161 @@ +package db + +import ( + "fmt" + "log" + "time" + + arangoDriver "github.com/arangodb/go-driver" + "github.com/google/uuid" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type Config struct { + Key string `json:"_key,omitempty" bson:"_key"` // for arangodb + ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"` + Config []byte `gorm:"type:text" json:"config" bson:"config"` + Hash string `gorm:"type:hash" json:"hash" bson:"hash"` + UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"` + CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"` +} + +// AddConfig function to add config +func (mgr *manager) AddConfig(config Config) (Config, error) { + if config.ID == "" { + config.ID = uuid.New().String() + } + + if IsORMSupported { + // copy id as value for fields required for mongodb & arangodb + config.Key = config.ID + result := mgr.sqlDB.Create(&config) + + if result.Error != nil { + log.Println("error adding config:", result.Error) + return config, result.Error + } + } + + if IsArangoDB { + config.CreatedAt = time.Now().Unix() + config.UpdatedAt = time.Now().Unix() + configCollection, _ := mgr.arangodb.Collection(nil, Collections.Config) + meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(nil), config) + if err != nil { + log.Println("error adding config:", err) + return config, err + } + config.Key = meta.Key + config.ID = meta.ID.String() + } + + if IsMongoDB { + config.CreatedAt = time.Now().Unix() + config.UpdatedAt = time.Now().Unix() + config.Key = config.ID + configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection()) + _, err := configCollection.InsertOne(nil, config) + if err != nil { + log.Println("error adding config:", err) + return config, err + } + } + + return config, nil +} + +// UpdateConfig function to update config +func (mgr *manager) UpdateConfig(config Config) (Config, error) { + config.UpdatedAt = time.Now().Unix() + + if IsORMSupported { + result := mgr.sqlDB.Save(&config) + + if result.Error != nil { + log.Println("error updating config:", result.Error) + return config, result.Error + } + } + + if IsArangoDB { + collection, _ := mgr.arangodb.Collection(nil, Collections.Config) + meta, err := collection.UpdateDocument(nil, config.Key, config) + if err != nil { + log.Println("error updating config:", err) + return config, err + } + + config.Key = meta.Key + config.ID = meta.ID.String() + } + + if IsMongoDB { + configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection()) + _, err := configCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": config.ID}}, bson.M{"$set": config}, options.MergeUpdateOptions()) + if err != nil { + log.Println("error updating config:", err) + return config, err + } + } + + return config, nil +} + +// GetConfig function to get config +func (mgr *manager) GetConfig() (Config, error) { + var config Config + + if IsORMSupported { + result := mgr.sqlDB.First(&config) + + if result.Error != nil { + return config, result.Error + } + } + + if IsArangoDB { + query := fmt.Sprintf("FOR d in %s RETURN d", Collections.Config) + + cursor, err := mgr.arangodb.Query(nil, query, nil) + if err != nil { + return config, err + } + defer cursor.Close() + + for { + if !cursor.HasMore() { + if config.Key == "" { + return config, fmt.Errorf("config not found") + } + break + } + _, err := cursor.ReadDocument(nil, &config) + if err != nil { + return config, err + } + } + } + + if IsMongoDB { + configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection()) + cursor, err := configCollection.Find(nil, bson.M{}, options.Find()) + if err != nil { + return config, err + } + defer cursor.Close(nil) + + for cursor.Next(nil) { + err := cursor.Decode(&config) + if err != nil { + return config, err + } + } + + if config.ID == "" { + return config, fmt.Errorf("config not found") + } + } + + return config, nil +} diff --git a/server/db/db.go b/server/db/db.go index f8e7256..8727da5 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -29,6 +29,9 @@ type Manager interface { GetVerificationByEmail(email string, identifier string) (VerificationRequest, error) AddSession(session Session) error DeleteUserSession(userId string) error + AddConfig(config Config) (Config, error) + UpdateConfig(config Config) (Config, error) + GetConfig() (Config, error) } type manager struct { @@ -42,6 +45,7 @@ type CollectionList struct { User string VerificationRequest string Session string + Config string } var ( @@ -54,6 +58,7 @@ var ( User: Prefix + "users", VerificationRequest: Prefix + "verification_requests", Session: Prefix + "sessions", + Config: Prefix + "config", } ) @@ -61,9 +66,9 @@ func InitDB() { var sqlDB *gorm.DB var err error - IsORMSupported = constants.DATABASE_TYPE != enum.Arangodb.String() && constants.DATABASE_TYPE != enum.Mongodb.String() - IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String() - IsMongoDB = constants.DATABASE_TYPE == enum.Mongodb.String() + 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() // sql db orm config ormConfig := &gorm.Config{ @@ -72,20 +77,20 @@ func InitDB() { }, } - log.Println("db type:", constants.DATABASE_TYPE) + log.Println("db type:", constants.EnvData.DATABASE_TYPE) - switch constants.DATABASE_TYPE { + switch constants.EnvData.DATABASE_TYPE { case enum.Postgres.String(): - sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig) + sqlDB, err = gorm.Open(postgres.Open(constants.EnvData.DATABASE_URL), ormConfig) break case enum.Sqlite.String(): - sqlDB, err = gorm.Open(sqlite.Open(constants.DATABASE_URL), ormConfig) + sqlDB, err = gorm.Open(sqlite.Open(constants.EnvData.DATABASE_URL), ormConfig) break case enum.Mysql.String(): - sqlDB, err = gorm.Open(mysql.Open(constants.DATABASE_URL), ormConfig) + sqlDB, err = gorm.Open(mysql.Open(constants.EnvData.DATABASE_URL), ormConfig) break case enum.SQLServer.String(): - sqlDB, err = gorm.Open(sqlserver.Open(constants.DATABASE_URL), ormConfig) + sqlDB, err = gorm.Open(sqlserver.Open(constants.EnvData.DATABASE_URL), ormConfig) break case enum.Arangodb.String(): arangodb, err := initArangodb() @@ -118,7 +123,7 @@ func InitDB() { if err != nil { log.Fatal("Failed to init sqlDB:", err) } else { - sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}) + sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}, &Config{}) } Mgr = &manager{ sqlDB: sqlDB, diff --git a/server/db/mongodb.go b/server/db/mongodb.go index c69f5bc..21d9320 100644 --- a/server/db/mongodb.go +++ b/server/db/mongodb.go @@ -12,7 +12,7 @@ import ( ) func initMongodb() (*mongo.Database, error) { - mongodbOptions := options.Client().ApplyURI(constants.DATABASE_URL) + mongodbOptions := options.Client().ApplyURI(constants.EnvData.DATABASE_URL) maxWait := time.Duration(5 * time.Second) mongodbOptions.ConnectTimeout = &maxWait mongoClient, err := mongo.NewClient(mongodbOptions) @@ -30,7 +30,7 @@ func initMongodb() (*mongo.Database, error) { return nil, err } - mongodb := mongoClient.Database(constants.DATABASE_NAME, options.Database()) + mongodb := mongoClient.Database(constants.EnvData.DATABASE_NAME, options.Database()) mongodb.CreateCollection(ctx, Collections.User, options.CreateCollection()) userCollection := mongodb.Collection(Collections.User, options.Collection()) @@ -73,5 +73,7 @@ func initMongodb() (*mongo.Database, error) { }, }, options.CreateIndexes()) + mongodb.CreateCollection(ctx, Collections.Config, options.CreateCollection()) + return mongodb, nil } diff --git a/server/db/user.go b/server/db/user.go index 60254fa..f020dcf 100644 --- a/server/db/user.go +++ b/server/db/user.go @@ -43,7 +43,7 @@ func (mgr *manager) AddUser(user User) (User, error) { } if user.Roles == "" { - user.Roles = constants.DEFAULT_ROLES[0] + user.Roles = constants.EnvData.DEFAULT_ROLES[0] } if IsORMSupported { diff --git a/server/email/email.go b/server/email/email.go index e598fcd..843bf0f 100644 --- a/server/email/email.go +++ b/server/email/email.go @@ -11,13 +11,13 @@ import ( func SendMail(to []string, Subject, bodyMessage string) error { m := gomail.NewMessage() - m.SetHeader("From", constants.SENDER_EMAIL) + m.SetHeader("From", constants.EnvData.SENDER_EMAIL) m.SetHeader("To", to...) m.SetHeader("Subject", Subject) m.SetBody("text/html", bodyMessage) - port, _ := strconv.Atoi(constants.SMTP_PORT) - d := gomail.NewDialer(constants.SMTP_HOST, port, constants.SMTP_USERNAME, constants.SMTP_PASSWORD) - if constants.ENV == "development" { + 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" { d.TLSConfig = &tls.Config{InsecureSkipVerify: true} } if err := d.DialAndSend(m); err != nil { diff --git a/server/env/env.go b/server/env/env.go index fb5e4f0..66a632d 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -7,6 +7,7 @@ import ( "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/utils" + "github.com/google/uuid" "github.com/joho/godotenv" ) @@ -20,167 +21,173 @@ var ( // InitEnv -> to initialize env and through error if required env are not present func InitEnv() { - if constants.ENV_PATH == "" { - constants.ENV_PATH = `.env` + if constants.EnvData.ENV_PATH == "" { + constants.EnvData.ENV_PATH = `.env` } if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" { - constants.ENV_PATH = *ARG_ENV_FILE + constants.EnvData.ENV_PATH = *ARG_ENV_FILE } - err := godotenv.Load(constants.ENV_PATH) + err := godotenv.Load(constants.EnvData.ENV_PATH) if err != nil { - log.Printf("error loading %s file", constants.ENV_PATH) + log.Printf("error loading %s file", constants.EnvData.ENV_PATH) } - if constants.ADMIN_SECRET == "" { - constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET") - if constants.ADMIN_SECRET == "" { - panic("root admin secret is required") - } + if constants.EnvData.ADMIN_SECRET == "" { + constants.EnvData.ADMIN_SECRET = os.Getenv("ADMIN_SECRET") } - if constants.ENV == "" { - constants.ENV = os.Getenv("ENV") - if constants.ENV == "" { - constants.ENV = "production" - } - - if constants.ENV == "production" { - constants.IS_PROD = true - os.Setenv("GIN_MODE", "release") - } else { - constants.IS_PROD = false - } - } - - if constants.DATABASE_TYPE == "" { - constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE") - log.Println(constants.DATABASE_TYPE) + if constants.EnvData.DATABASE_TYPE == "" { + constants.EnvData.DATABASE_TYPE = os.Getenv("DATABASE_TYPE") + log.Println(constants.EnvData.DATABASE_TYPE) if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" { - constants.DATABASE_TYPE = *ARG_DB_TYPE + constants.EnvData.DATABASE_TYPE = *ARG_DB_TYPE } - if constants.DATABASE_TYPE == "" { + if constants.EnvData.DATABASE_TYPE == "" { panic("DATABASE_TYPE is required") } } - if constants.DATABASE_URL == "" { - constants.DATABASE_URL = os.Getenv("DATABASE_URL") + if constants.EnvData.DATABASE_URL == "" { + constants.EnvData.DATABASE_URL = os.Getenv("DATABASE_URL") if ARG_DB_URL != nil && *ARG_DB_URL != "" { - constants.DATABASE_URL = *ARG_DB_URL + constants.EnvData.DATABASE_URL = *ARG_DB_URL } - if constants.DATABASE_URL == "" { + if constants.EnvData.DATABASE_URL == "" { panic("DATABASE_URL is required") } } - if constants.DATABASE_NAME == "" { - constants.DATABASE_NAME = os.Getenv("DATABASE_NAME") - if constants.DATABASE_NAME == "" { - constants.DATABASE_NAME = "authorizer" + if constants.EnvData.DATABASE_NAME == "" { + constants.EnvData.DATABASE_NAME = os.Getenv("DATABASE_NAME") + if constants.EnvData.DATABASE_NAME == "" { + constants.EnvData.DATABASE_NAME = "authorizer" } } - if constants.SMTP_HOST == "" { - constants.SMTP_HOST = os.Getenv("SMTP_HOST") - } + if constants.EnvData.ENV == "" { + constants.EnvData.ENV = os.Getenv("ENV") + if constants.EnvData.ENV == "" { + constants.EnvData.ENV = "production" + } - if constants.SMTP_PORT == "" { - constants.SMTP_PORT = os.Getenv("SMTP_PORT") - } - - if constants.SMTP_USERNAME == "" { - constants.SMTP_USERNAME = os.Getenv("SMTP_USERNAME") - } - - if constants.SMTP_PASSWORD == "" { - constants.SMTP_PASSWORD = os.Getenv("SMTP_PASSWORD") - } - - if constants.SENDER_EMAIL == "" { - constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL") - } - - if constants.JWT_SECRET == "" { - constants.JWT_SECRET = os.Getenv("JWT_SECRET") - } - - if constants.JWT_TYPE == "" { - constants.JWT_TYPE = os.Getenv("JWT_TYPE") - } - - if constants.JWT_ROLE_CLAIM == "" { - constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM") - - if constants.JWT_ROLE_CLAIM == "" { - constants.JWT_ROLE_CLAIM = "role" + if constants.EnvData.ENV == "production" { + constants.EnvData.IS_PROD = true + os.Setenv("GIN_MODE", "release") + } else { + constants.EnvData.IS_PROD = false } } - if constants.AUTHORIZER_URL == "" { - constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/") + 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 constants.EnvData.JWT_TYPE == "" { + constants.EnvData.JWT_TYPE = os.Getenv("JWT_TYPE") + if constants.EnvData.JWT_TYPE == "" { + constants.EnvData.JWT_TYPE = "HS256" + } + } + + if constants.EnvData.JWT_ROLE_CLAIM == "" { + constants.EnvData.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM") + + if constants.EnvData.JWT_ROLE_CLAIM == "" { + constants.EnvData.JWT_ROLE_CLAIM = "role" + } + } + + if constants.EnvData.AUTHORIZER_URL == "" { + constants.EnvData.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/") if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" { - constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL + constants.EnvData.AUTHORIZER_URL = *ARG_AUTHORIZER_URL } } - if constants.PORT == "" { - constants.PORT = os.Getenv("PORT") - if constants.PORT == "" { - constants.PORT = "8080" + if constants.EnvData.PORT == "" { + constants.EnvData.PORT = os.Getenv("PORT") + if constants.EnvData.PORT == "" { + constants.EnvData.PORT = "8080" } } - if constants.REDIS_URL == "" { - constants.REDIS_URL = os.Getenv("REDIS_URL") + if constants.EnvData.REDIS_URL == "" { + constants.EnvData.REDIS_URL = os.Getenv("REDIS_URL") } - if constants.COOKIE_NAME == "" { - constants.COOKIE_NAME = os.Getenv("COOKIE_NAME") + if constants.EnvData.COOKIE_NAME == "" { + constants.EnvData.COOKIE_NAME = os.Getenv("COOKIE_NAME") + if constants.EnvData.COOKIE_NAME == "" { + constants.EnvData.COOKIE_NAME = "authorizer" + } } - if constants.GOOGLE_CLIENT_ID == "" { - constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID") + if constants.EnvData.GOOGLE_CLIENT_ID == "" { + constants.EnvData.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID") } - if constants.GOOGLE_CLIENT_SECRET == "" { - constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET") + if constants.EnvData.GOOGLE_CLIENT_SECRET == "" { + constants.EnvData.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET") } - if constants.GITHUB_CLIENT_ID == "" { - constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID") + if constants.EnvData.GITHUB_CLIENT_ID == "" { + constants.EnvData.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID") } - if constants.GITHUB_CLIENT_SECRET == "" { - constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET") + if constants.EnvData.GITHUB_CLIENT_SECRET == "" { + constants.EnvData.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET") } - if constants.FACEBOOK_CLIENT_ID == "" { - constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID") + if constants.EnvData.FACEBOOK_CLIENT_ID == "" { + constants.EnvData.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID") } - if constants.FACEBOOK_CLIENT_SECRET == "" { - constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET") + if constants.EnvData.FACEBOOK_CLIENT_SECRET == "" { + constants.EnvData.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET") } - if constants.RESET_PASSWORD_URL == "" { - constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") + if constants.EnvData.RESET_PASSWORD_URL == "" { + constants.EnvData.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") } - constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true" - constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true" - constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true" - constants.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true" + 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.SMTP_HOST == "" || constants.SMTP_USERNAME == "" || constants.SMTP_PASSWORD == "" || constants.SENDER_EMAIL == "" { - constants.DISABLE_EMAIL_VERIFICATION = true - constants.DISABLE_MAGIC_LINK_LOGIN = 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 } allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",") @@ -209,18 +216,10 @@ func InitEnv() { allowedOrigins = []string{"*"} } - constants.ALLOWED_ORIGINS = allowedOrigins + constants.EnvData.ALLOWED_ORIGINS = allowedOrigins - if constants.JWT_TYPE == "" { - constants.JWT_TYPE = "HS256" - } - - if constants.COOKIE_NAME == "" { - constants.COOKIE_NAME = "authorizer" - } - - if constants.DISABLE_EMAIL_VERIFICATION { - constants.DISABLE_MAGIC_LINK_LOGIN = true + if constants.EnvData.DISABLE_EMAIL_VERIFICATION { + constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true } rolesEnv := strings.TrimSpace(os.Getenv("ROLES")) @@ -260,19 +259,19 @@ func InitEnv() { } } - if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRoleSplit) > 0 { + if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 { panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) } - constants.ROLES = roles - constants.DEFAULT_ROLES = defaultRoles - constants.PROTECTED_ROLES = protectedRoles + constants.EnvData.ROLES = roles + constants.EnvData.DEFAULT_ROLES = defaultRoles + constants.EnvData.PROTECTED_ROLES = protectedRoles if os.Getenv("ORGANIZATION_NAME") != "" { - constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME") + constants.EnvData.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME") } if os.Getenv("ORGANIZATION_LOGO") != "" { - constants.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO") + constants.EnvData.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO") } } diff --git a/server/env/persist_env.go b/server/env/persist_env.go new file mode 100644 index 0000000..23c3560 --- /dev/null +++ b/server/env/persist_env.go @@ -0,0 +1,138 @@ +package env + +import ( + "encoding/json" + "log" + "os" + "reflect" + "strings" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/utils" + "github.com/google/uuid" +) + +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 + encodedHash := utils.EncryptB64(hash) + + configData, err := json.Marshal(constants.EnvData) + if err != nil { + return err + } + encryptedConfig, err := utils.EncryptAES(configData) + if err != nil { + return err + } + + config = db.Config{ + Hash: encodedHash, + Config: encryptedConfig, + } + + db.Mgr.AddConfig(config) + } else { + // decrypt the config data from db + // decryption can be done using the hash stored in db + encryptionKey := config.Hash + decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey) + if err != nil { + return err + } + constants.EnvData.ENCRYPTION_KEY = decryptedEncryptionKey + decryptedConfigs, err := utils.DecryptAES(config.Config) + if err != nil { + return err + } + + // temp json to validate with env + var jsonData map[string]interface{} + + err = json.Unmarshal(decryptedConfigs, &jsonData) + if err != nil { + return err + } + + // if env is changed via env file or OS env + // give that higher preference and update db, but we don't recommend it + + hasChanged := false + + for key, value := range jsonData { + fieldType := reflect.TypeOf(value).String() + + // check only for derivative keys + // No need to check for ENCRYPTION_KEY which special key we use for encrypting config data + // as we have removed it from json + envValue := strings.TrimSpace(os.Getenv(key)) + + // env is not empty + if envValue != "" { + // check the type + // currently we have 3 types of env vars: string, bool, []string{} + if fieldType == "string" { + if value != envValue { + jsonData[key] = envValue + hasChanged = true + } + } + + if fieldType == "bool" { + newValue := envValue == "true" + if value != newValue { + jsonData[key] = newValue + hasChanged = true + } + } + + if fieldType == "[]interface {}" { + stringArr := []string{} + envStringArr := strings.Split(envValue, ",") + for _, v := range value.([]interface{}) { + stringArr = append(stringArr, v.(string)) + } + if !utils.IsStringArrayEqual(stringArr, envStringArr) { + jsonData[key] = envStringArr + } + } + } + } + + // handle derivative cases like disabling email verification & magic login + // in case SMTP is off but env is set to true + if jsonData["SMTP_HOST"] == "" || jsonData["SENDER_EMAIL"] == "" || jsonData["SENDER_PASSWORD"] == "" { + if !jsonData["DISABLE_EMAIL_VERIFICATION"].(bool) { + jsonData["DISABLE_EMAIL_VERIFICATION"] = true + hasChanged = true + } + + if !jsonData["DISABLE_MAGIC_LINK_LOGIN"].(bool) { + jsonData["DISABLE_MAGIC_LINK_LOGIN"] = true + hasChanged = true + } + } + + if hasChanged { + encryptedConfig, err := utils.EncryptConfig(jsonData) + if err != nil { + return err + } + + config.Config = encryptedConfig + _, err = db.Mgr.UpdateConfig(config) + if err != nil { + log.Println("error updating config:", err) + return err + } + } + + } + + return nil +} diff --git a/server/go.mod b/server/go.mod index 6168c55..efd8cb6 100644 --- a/server/go.mod +++ b/server/go.mod @@ -20,7 +20,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f - github.com/stretchr/testify v1.7.0 github.com/ugorji/go v1.2.6 // indirect github.com/vektah/gqlparser/v2 v2.2.0 go.mongodb.org/mongo-driver v1.8.1 @@ -30,7 +29,7 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/mail.v2 v2.3.1 // indirect + gopkg.in/mail.v2 v2.3.1 gopkg.in/yaml.v2 v2.4.0 // indirect gorm.io/driver/mysql v1.2.1 gorm.io/driver/postgres v1.2.3 diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 300307a..3a336d9 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -50,6 +50,42 @@ type ComplexityRoot struct { User func(childComplexity int) int } + Config struct { + AdminSecret func(childComplexity int) int + AllowedOrigins func(childComplexity int) int + AppURL func(childComplexity int) int + AuthorizerURL func(childComplexity int) int + CookieName func(childComplexity int) int + DatabaseName func(childComplexity int) int + DatabaseType func(childComplexity int) int + DatabaseURL func(childComplexity int) int + DefaultRoles func(childComplexity int) int + DisableBasicAuthentication func(childComplexity int) int + DisableEmailVerification func(childComplexity int) int + DisableLoginPage func(childComplexity int) int + DisableMagicLinkLogin func(childComplexity int) int + FacebookClientID func(childComplexity int) int + FacebookClientSecret func(childComplexity int) int + GithubClientID func(childComplexity int) int + GithubClientSecret func(childComplexity int) int + GoogleClientID func(childComplexity int) int + GoogleClientSecret func(childComplexity int) int + JwtRoleClaim func(childComplexity int) int + JwtSecret func(childComplexity int) int + JwtType func(childComplexity int) int + OrganizationLogo func(childComplexity int) int + OrganizationName func(childComplexity int) int + ProtectedRoles func(childComplexity int) int + RedisURL func(childComplexity int) int + ResetPasswordURL func(childComplexity int) int + Roles func(childComplexity int) int + SMTPHost func(childComplexity int) int + SMTPPassword func(childComplexity int) int + SMTPPort func(childComplexity int) int + SMTPUsername func(childComplexity int) int + SenderEmail func(childComplexity int) int + } + Error struct { Message func(childComplexity int) int Reason func(childComplexity int) int @@ -66,6 +102,9 @@ type ComplexityRoot struct { } Mutation struct { + AdminLogin func(childComplexity int, params model.AdminLoginInput) int + AdminLogout func(childComplexity int) int + AdminSignup func(childComplexity int, params model.AdminSignupInput) int DeleteUser func(childComplexity int, params model.DeleteUserInput) int ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int Login func(childComplexity int, params model.LoginInput) int @@ -74,12 +113,15 @@ type ComplexityRoot struct { ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int ResetPassword func(childComplexity int, params model.ResetPasswordInput) int Signup func(childComplexity int, params model.SignUpInput) int + UpdateConfig func(childComplexity int, params model.UpdateConfigInput) int UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int UpdateUser func(childComplexity int, params model.UpdateUserInput) int VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int } Query struct { + AdminSession func(childComplexity int) int + Config func(childComplexity int) int Meta func(childComplexity int) int Profile func(childComplexity int) int Session func(childComplexity int, roles []string) int @@ -134,6 +176,10 @@ type MutationResolver interface { ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) + AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) + AdminLogin(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) + AdminLogout(ctx context.Context) (*model.Response, error) + UpdateConfig(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) } type QueryResolver interface { Meta(ctx context.Context) (*model.Meta, error) @@ -141,6 +187,8 @@ type QueryResolver interface { Profile(ctx context.Context) (*model.User, error) Users(ctx context.Context) ([]*model.User, error) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) + AdminSession(ctx context.Context) (*model.Response, error) + Config(ctx context.Context) (*model.Config, error) } type executableSchema struct { @@ -186,6 +234,237 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthResponse.User(childComplexity), true + case "Config.ADMIN_SECRET": + if e.complexity.Config.AdminSecret == nil { + break + } + + return e.complexity.Config.AdminSecret(childComplexity), true + + case "Config.ALLOWED_ORIGINS": + if e.complexity.Config.AllowedOrigins == nil { + break + } + + return e.complexity.Config.AllowedOrigins(childComplexity), true + + case "Config.APP_URL": + if e.complexity.Config.AppURL == nil { + break + } + + return e.complexity.Config.AppURL(childComplexity), true + + case "Config.AUTHORIZER_URL": + if e.complexity.Config.AuthorizerURL == nil { + break + } + + return e.complexity.Config.AuthorizerURL(childComplexity), true + + case "Config.COOKIE_NAME": + if e.complexity.Config.CookieName == nil { + break + } + + return e.complexity.Config.CookieName(childComplexity), true + + case "Config.DATABASE_NAME": + if e.complexity.Config.DatabaseName == nil { + break + } + + return e.complexity.Config.DatabaseName(childComplexity), true + + case "Config.DATABASE_TYPE": + if e.complexity.Config.DatabaseType == nil { + break + } + + return e.complexity.Config.DatabaseType(childComplexity), true + + case "Config.DATABASE_URL": + if e.complexity.Config.DatabaseURL == nil { + break + } + + return e.complexity.Config.DatabaseURL(childComplexity), true + + case "Config.DEFAULT_ROLES": + if e.complexity.Config.DefaultRoles == nil { + break + } + + return e.complexity.Config.DefaultRoles(childComplexity), true + + case "Config.DISABLE_BASIC_AUTHENTICATION": + if e.complexity.Config.DisableBasicAuthentication == nil { + break + } + + return e.complexity.Config.DisableBasicAuthentication(childComplexity), true + + case "Config.DISABLE_EMAIL_VERIFICATION": + if e.complexity.Config.DisableEmailVerification == nil { + break + } + + return e.complexity.Config.DisableEmailVerification(childComplexity), true + + case "Config.DISABLE_LOGIN_PAGE": + if e.complexity.Config.DisableLoginPage == nil { + break + } + + return e.complexity.Config.DisableLoginPage(childComplexity), true + + case "Config.DISABLE_MAGIC_LINK_LOGIN": + if e.complexity.Config.DisableMagicLinkLogin == nil { + break + } + + return e.complexity.Config.DisableMagicLinkLogin(childComplexity), true + + case "Config.FACEBOOK_CLIENT_ID": + if e.complexity.Config.FacebookClientID == nil { + break + } + + return e.complexity.Config.FacebookClientID(childComplexity), true + + case "Config.FACEBOOK_CLIENT_SECRET": + if e.complexity.Config.FacebookClientSecret == nil { + break + } + + return e.complexity.Config.FacebookClientSecret(childComplexity), true + + case "Config.GITHUB_CLIENT_ID": + if e.complexity.Config.GithubClientID == nil { + break + } + + return e.complexity.Config.GithubClientID(childComplexity), true + + case "Config.GITHUB_CLIENT_SECRET": + if e.complexity.Config.GithubClientSecret == nil { + break + } + + return e.complexity.Config.GithubClientSecret(childComplexity), true + + case "Config.GOOGLE_CLIENT_ID": + if e.complexity.Config.GoogleClientID == nil { + break + } + + return e.complexity.Config.GoogleClientID(childComplexity), true + + case "Config.GOOGLE_CLIENT_SECRET": + if e.complexity.Config.GoogleClientSecret == nil { + break + } + + return e.complexity.Config.GoogleClientSecret(childComplexity), true + + case "Config.JWT_ROLE_CLAIM": + if e.complexity.Config.JwtRoleClaim == nil { + break + } + + return e.complexity.Config.JwtRoleClaim(childComplexity), true + + case "Config.JWT_SECRET": + if e.complexity.Config.JwtSecret == nil { + break + } + + return e.complexity.Config.JwtSecret(childComplexity), true + + case "Config.JWT_TYPE": + if e.complexity.Config.JwtType == nil { + break + } + + return e.complexity.Config.JwtType(childComplexity), true + + case "Config.ORGANIZATION_LOGO": + if e.complexity.Config.OrganizationLogo == nil { + break + } + + return e.complexity.Config.OrganizationLogo(childComplexity), true + + case "Config.ORGANIZATION_NAME": + if e.complexity.Config.OrganizationName == nil { + break + } + + return e.complexity.Config.OrganizationName(childComplexity), true + + case "Config.PROTECTED_ROLES": + if e.complexity.Config.ProtectedRoles == nil { + break + } + + return e.complexity.Config.ProtectedRoles(childComplexity), true + + case "Config.REDIS_URL": + if e.complexity.Config.RedisURL == nil { + break + } + + return e.complexity.Config.RedisURL(childComplexity), true + + case "Config.RESET_PASSWORD_URL": + if e.complexity.Config.ResetPasswordURL == nil { + break + } + + return e.complexity.Config.ResetPasswordURL(childComplexity), true + + case "Config.ROLES": + if e.complexity.Config.Roles == nil { + break + } + + return e.complexity.Config.Roles(childComplexity), true + + case "Config.SMTP_HOST": + if e.complexity.Config.SMTPHost == nil { + break + } + + return e.complexity.Config.SMTPHost(childComplexity), true + + case "Config.SMTP_PASSWORD": + if e.complexity.Config.SMTPPassword == nil { + break + } + + return e.complexity.Config.SMTPPassword(childComplexity), true + + case "Config.SMTP_PORT": + if e.complexity.Config.SMTPPort == nil { + break + } + + return e.complexity.Config.SMTPPort(childComplexity), true + + case "Config.SMTP_USERNAME": + if e.complexity.Config.SMTPUsername == nil { + break + } + + return e.complexity.Config.SMTPUsername(childComplexity), true + + case "Config.SENDER_EMAIL": + if e.complexity.Config.SenderEmail == nil { + break + } + + return e.complexity.Config.SenderEmail(childComplexity), true + case "Error.message": if e.complexity.Error.Message == nil { break @@ -249,6 +528,37 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Meta.Version(childComplexity), true + case "Mutation._admin_login": + if e.complexity.Mutation.AdminLogin == nil { + break + } + + args, err := ec.field_Mutation__admin_login_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AdminLogin(childComplexity, args["params"].(model.AdminLoginInput)), true + + case "Mutation._admin_logout": + if e.complexity.Mutation.AdminLogout == nil { + break + } + + return e.complexity.Mutation.AdminLogout(childComplexity), true + + case "Mutation._admin_signup": + if e.complexity.Mutation.AdminSignup == nil { + break + } + + args, err := ec.field_Mutation__admin_signup_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AdminSignup(childComplexity, args["params"].(model.AdminSignupInput)), true + case "Mutation._delete_user": if e.complexity.Mutation.DeleteUser == nil { break @@ -340,6 +650,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.Signup(childComplexity, args["params"].(model.SignUpInput)), true + case "Mutation._update_config": + if e.complexity.Mutation.UpdateConfig == nil { + break + } + + args, err := ec.field_Mutation__update_config_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdateConfig(childComplexity, args["params"].(model.UpdateConfigInput)), true + case "Mutation.update_profile": if e.complexity.Mutation.UpdateProfile == nil { break @@ -376,6 +698,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.VerifyEmail(childComplexity, args["params"].(model.VerifyEmailInput)), true + case "Query._admin_session": + if e.complexity.Query.AdminSession == nil { + break + } + + return e.complexity.Query.AdminSession(childComplexity), true + + case "Query._config": + if e.complexity.Query.Config == nil { + break + } + + return e.complexity.Query.Config(childComplexity), true + case "Query.meta": if e.complexity.Query.Meta == nil { break @@ -719,6 +1055,85 @@ type Response { message: String! } +type Config { + ADMIN_SECRET: String + DATABASE_TYPE: String + DATABASE_URL: String + DATABASE_NAME: String + SMTP_HOST: String + SMTP_PORT: String + SMTP_USERNAME: String + SMTP_PASSWORD: String + SENDER_EMAIL: String + JWT_TYPE: String + JWT_SECRET: String + ALLOWED_ORIGINS: [String!] + AUTHORIZER_URL: String + APP_URL: String + REDIS_URL: String + COOKIE_NAME: String + RESET_PASSWORD_URL: String + DISABLE_EMAIL_VERIFICATION: Boolean + DISABLE_BASIC_AUTHENTICATION: Boolean + DISABLE_MAGIC_LINK_LOGIN: Boolean + DISABLE_LOGIN_PAGE: Boolean + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String +} + +input UpdateConfigInput { + ADMIN_SECRET: String + DATABASE_TYPE: String + DATABASE_URL: String + DATABASE_NAME: String + SMTP_HOST: String + SMTP_PORT: String + SENDER_EMAIL: String + SENDER_PASSWORD: String + JWT_TYPE: String + JWT_SECRET: String + ALLOWED_ORIGINS: [String!] + AUTHORIZER_URL: String + APP_URL: String + REDIS_URL: String + COOKIE_NAME: String + RESET_PASSWORD_URL: String + DISABLE_EMAIL_VERIFICATION: Boolean + DISABLE_BASIC_AUTHENTICATION: Boolean + DISABLE_MAGIC_LINK_LOGIN: Boolean + DISABLE_LOGIN_PAGE: Boolean + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String +} + +input AdminLoginInput { + admin_secret: String! +} + +input AdminSignupInput { + admin_secret: String! +} + input SignUpInput { email: String! given_name: String @@ -810,15 +1225,21 @@ type Mutation { # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! + _admin_signup(params: AdminSignupInput!): Response! + _admin_login(params: AdminLoginInput!): Response! + _admin_logout: Response! + _update_config(params: UpdateConfigInput!): Response! } type Query { meta: Meta! - session(roles: [String!]): AuthResponse + session(roles: [String!]): AuthResponse! profile: User! # admin only apis _users: [User!]! _verification_requests: [VerificationRequest!]! + _admin_session: Response! + _config: Config! } `, BuiltIn: false}, } @@ -828,6 +1249,36 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_Mutation__admin_login_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.AdminLoginInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNAdminLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAdminLoginInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Mutation__admin_signup_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.AdminSignupInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNAdminSignupInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAdminSignupInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation__delete_user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -843,6 +1294,21 @@ func (ec *executionContext) field_Mutation__delete_user_args(ctx context.Context return args, nil } +func (ec *executionContext) field_Mutation__update_config_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.UpdateConfigInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNUpdateConfigInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateConfigInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation__update_user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1177,6 +1643,1062 @@ func (ec *executionContext) _AuthResponse_user(ctx context.Context, field graphq return ec.marshalOUser2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } +func (ec *executionContext) _Config_ADMIN_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.AdminSecret, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DATABASE_TYPE(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DatabaseType, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DATABASE_URL(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DatabaseURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DATABASE_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DatabaseName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_SMTP_HOST(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SMTPHost, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_SMTP_PORT(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SMTPPort, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_SMTP_USERNAME(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SMTPUsername, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_SMTP_PASSWORD(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SMTPPassword, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_SENDER_EMAIL(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SenderEmail, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_JWT_TYPE(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.JwtType, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_JWT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.JwtSecret, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_ALLOWED_ORIGINS(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.AllowedOrigins, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalOString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_AUTHORIZER_URL(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.AuthorizerURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_APP_URL(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.AppURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_REDIS_URL(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.RedisURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_COOKIE_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CookieName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_RESET_PASSWORD_URL(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ResetPasswordURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DISABLE_EMAIL_VERIFICATION(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisableEmailVerification, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DISABLE_BASIC_AUTHENTICATION(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisableBasicAuthentication, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DISABLE_MAGIC_LINK_LOGIN(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisableMagicLinkLogin, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DISABLE_LOGIN_PAGE(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisableLoginPage, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_ROLES(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Roles, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalOString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_PROTECTED_ROLES(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ProtectedRoles, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalOString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_DEFAULT_ROLES(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DefaultRoles, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalOString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_JWT_ROLE_CLAIM(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.JwtRoleClaim, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_GOOGLE_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.GoogleClientID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_GOOGLE_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.GoogleClientSecret, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_GITHUB_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.GithubClientID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_GITHUB_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.GithubClientSecret, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_FACEBOOK_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.FacebookClientID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_FACEBOOK_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.FacebookClientSecret, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.OrganizationName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _Config_ORGANIZATION_LOGO(ctx context.Context, field graphql.CollectedField, obj *model.Config) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Config", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.OrganizationLogo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + func (ec *executionContext) _Error_message(ctx context.Context, field graphql.CollectedField, obj *model.Error) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -1947,6 +3469,167 @@ func (ec *executionContext) _Mutation__update_user(ctx context.Context, field gr return ec.marshalNUser2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation__admin_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation__admin_signup_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AdminSignup(rctx, args["params"].(model.AdminSignupInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation__admin_login(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation__admin_login_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AdminLogin(rctx, args["params"].(model.AdminLoginInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation__admin_logout(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AdminLogout(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation__update_config(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation__update_config_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateConfig(rctx, args["params"].(model.UpdateConfigInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_meta(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2014,11 +3697,14 @@ func (ec *executionContext) _Query_session(ctx context.Context, field graphql.Co return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } res := resTmp.(*model.AuthResponse) fc.Result = res - return ec.marshalOAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) + return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) } func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -2126,6 +3812,76 @@ func (ec *executionContext) _Query__verification_requests(ctx context.Context, f return ec.marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Query__admin_session(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().AdminSession(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query__config(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Config(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Config) + fc.Result = res + return ec.marshalNConfig2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐConfig(ctx, field.Selections, res) +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4140,6 +5896,52 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputAdminLoginInput(ctx context.Context, obj interface{}) (model.AdminLoginInput, error) { + var it model.AdminLoginInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "admin_secret": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("admin_secret")) + it.AdminSecret, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputAdminSignupInput(ctx context.Context, obj interface{}) (model.AdminSignupInput, error) { + var it model.AdminSignupInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "admin_secret": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("admin_secret")) + it.AdminSecret, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDeleteUserInput(ctx context.Context, obj interface{}) (model.DeleteUserInput, error) { var it model.DeleteUserInput asMap := map[string]interface{}{} @@ -4437,6 +6239,277 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i return it, nil } +func (ec *executionContext) unmarshalInputUpdateConfigInput(ctx context.Context, obj interface{}) (model.UpdateConfigInput, error) { + var it model.UpdateConfigInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "ADMIN_SECRET": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ADMIN_SECRET")) + it.AdminSecret, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "DATABASE_TYPE": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DATABASE_TYPE")) + it.DatabaseType, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "DATABASE_URL": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DATABASE_URL")) + it.DatabaseURL, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "DATABASE_NAME": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DATABASE_NAME")) + it.DatabaseName, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "SMTP_HOST": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("SMTP_HOST")) + it.SMTPHost, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "SMTP_PORT": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("SMTP_PORT")) + it.SMTPPort, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "SENDER_EMAIL": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("SENDER_EMAIL")) + it.SenderEmail, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "SENDER_PASSWORD": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("SENDER_PASSWORD")) + it.SenderPassword, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "JWT_TYPE": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("JWT_TYPE")) + it.JwtType, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "JWT_SECRET": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("JWT_SECRET")) + it.JwtSecret, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "ALLOWED_ORIGINS": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ALLOWED_ORIGINS")) + it.AllowedOrigins, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + case "AUTHORIZER_URL": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("AUTHORIZER_URL")) + it.AuthorizerURL, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "APP_URL": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("APP_URL")) + it.AppURL, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "REDIS_URL": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("REDIS_URL")) + it.RedisURL, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "COOKIE_NAME": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("COOKIE_NAME")) + it.CookieName, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "RESET_PASSWORD_URL": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("RESET_PASSWORD_URL")) + it.ResetPasswordURL, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "DISABLE_EMAIL_VERIFICATION": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_EMAIL_VERIFICATION")) + it.DisableEmailVerification, err = ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + case "DISABLE_BASIC_AUTHENTICATION": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_BASIC_AUTHENTICATION")) + it.DisableBasicAuthentication, err = ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + case "DISABLE_MAGIC_LINK_LOGIN": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_MAGIC_LINK_LOGIN")) + it.DisableMagicLinkLogin, err = ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + case "DISABLE_LOGIN_PAGE": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_LOGIN_PAGE")) + it.DisableLoginPage, err = ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + case "ROLES": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ROLES")) + it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + case "PROTECTED_ROLES": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("PROTECTED_ROLES")) + it.ProtectedRoles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + case "DEFAULT_ROLES": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DEFAULT_ROLES")) + it.DefaultRoles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + case "JWT_ROLE_CLAIM": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("JWT_ROLE_CLAIM")) + it.JwtRoleClaim, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "GOOGLE_CLIENT_ID": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("GOOGLE_CLIENT_ID")) + it.GoogleClientID, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "GOOGLE_CLIENT_SECRET": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("GOOGLE_CLIENT_SECRET")) + it.GoogleClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "GITHUB_CLIENT_ID": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("GITHUB_CLIENT_ID")) + it.GithubClientID, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "GITHUB_CLIENT_SECRET": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("GITHUB_CLIENT_SECRET")) + it.GithubClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "FACEBOOK_CLIENT_ID": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("FACEBOOK_CLIENT_ID")) + it.FacebookClientID, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "FACEBOOK_CLIENT_SECRET": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("FACEBOOK_CLIENT_SECRET")) + it.FacebookClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "ORGANIZATION_NAME": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ORGANIZATION_NAME")) + it.OrganizationName, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "ORGANIZATION_LOGO": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ORGANIZATION_LOGO")) + it.OrganizationLogo, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputUpdateProfileInput(ctx context.Context, obj interface{}) (model.UpdateProfileInput, error) { var it model.UpdateProfileInput asMap := map[string]interface{}{} @@ -4715,6 +6788,94 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection return out } +var configImplementors = []string{"Config"} + +func (ec *executionContext) _Config(ctx context.Context, sel ast.SelectionSet, obj *model.Config) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, configImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Config") + case "ADMIN_SECRET": + out.Values[i] = ec._Config_ADMIN_SECRET(ctx, field, obj) + case "DATABASE_TYPE": + out.Values[i] = ec._Config_DATABASE_TYPE(ctx, field, obj) + case "DATABASE_URL": + out.Values[i] = ec._Config_DATABASE_URL(ctx, field, obj) + case "DATABASE_NAME": + out.Values[i] = ec._Config_DATABASE_NAME(ctx, field, obj) + case "SMTP_HOST": + out.Values[i] = ec._Config_SMTP_HOST(ctx, field, obj) + case "SMTP_PORT": + out.Values[i] = ec._Config_SMTP_PORT(ctx, field, obj) + case "SMTP_USERNAME": + out.Values[i] = ec._Config_SMTP_USERNAME(ctx, field, obj) + case "SMTP_PASSWORD": + out.Values[i] = ec._Config_SMTP_PASSWORD(ctx, field, obj) + case "SENDER_EMAIL": + out.Values[i] = ec._Config_SENDER_EMAIL(ctx, field, obj) + case "JWT_TYPE": + out.Values[i] = ec._Config_JWT_TYPE(ctx, field, obj) + case "JWT_SECRET": + out.Values[i] = ec._Config_JWT_SECRET(ctx, field, obj) + case "ALLOWED_ORIGINS": + out.Values[i] = ec._Config_ALLOWED_ORIGINS(ctx, field, obj) + case "AUTHORIZER_URL": + out.Values[i] = ec._Config_AUTHORIZER_URL(ctx, field, obj) + case "APP_URL": + out.Values[i] = ec._Config_APP_URL(ctx, field, obj) + case "REDIS_URL": + out.Values[i] = ec._Config_REDIS_URL(ctx, field, obj) + case "COOKIE_NAME": + out.Values[i] = ec._Config_COOKIE_NAME(ctx, field, obj) + case "RESET_PASSWORD_URL": + out.Values[i] = ec._Config_RESET_PASSWORD_URL(ctx, field, obj) + case "DISABLE_EMAIL_VERIFICATION": + out.Values[i] = ec._Config_DISABLE_EMAIL_VERIFICATION(ctx, field, obj) + case "DISABLE_BASIC_AUTHENTICATION": + out.Values[i] = ec._Config_DISABLE_BASIC_AUTHENTICATION(ctx, field, obj) + case "DISABLE_MAGIC_LINK_LOGIN": + out.Values[i] = ec._Config_DISABLE_MAGIC_LINK_LOGIN(ctx, field, obj) + case "DISABLE_LOGIN_PAGE": + out.Values[i] = ec._Config_DISABLE_LOGIN_PAGE(ctx, field, obj) + case "ROLES": + out.Values[i] = ec._Config_ROLES(ctx, field, obj) + case "PROTECTED_ROLES": + out.Values[i] = ec._Config_PROTECTED_ROLES(ctx, field, obj) + case "DEFAULT_ROLES": + out.Values[i] = ec._Config_DEFAULT_ROLES(ctx, field, obj) + case "JWT_ROLE_CLAIM": + out.Values[i] = ec._Config_JWT_ROLE_CLAIM(ctx, field, obj) + case "GOOGLE_CLIENT_ID": + out.Values[i] = ec._Config_GOOGLE_CLIENT_ID(ctx, field, obj) + case "GOOGLE_CLIENT_SECRET": + out.Values[i] = ec._Config_GOOGLE_CLIENT_SECRET(ctx, field, obj) + case "GITHUB_CLIENT_ID": + out.Values[i] = ec._Config_GITHUB_CLIENT_ID(ctx, field, obj) + case "GITHUB_CLIENT_SECRET": + out.Values[i] = ec._Config_GITHUB_CLIENT_SECRET(ctx, field, obj) + case "FACEBOOK_CLIENT_ID": + out.Values[i] = ec._Config_FACEBOOK_CLIENT_ID(ctx, field, obj) + case "FACEBOOK_CLIENT_SECRET": + out.Values[i] = ec._Config_FACEBOOK_CLIENT_SECRET(ctx, field, obj) + case "ORGANIZATION_NAME": + out.Values[i] = ec._Config_ORGANIZATION_NAME(ctx, field, obj) + case "ORGANIZATION_LOGO": + out.Values[i] = ec._Config_ORGANIZATION_LOGO(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var errorImplementors = []string{"Error"} func (ec *executionContext) _Error(ctx context.Context, sel ast.SelectionSet, obj *model.Error) graphql.Marshaler { @@ -4874,6 +7035,26 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "_admin_signup": + out.Values[i] = ec._Mutation__admin_signup(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "_admin_login": + out.Values[i] = ec._Mutation__admin_login(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "_admin_logout": + out.Values[i] = ec._Mutation__admin_logout(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "_update_config": + out.Values[i] = ec._Mutation__update_config(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -4923,6 +7104,9 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_session(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } return res }) case "profile": @@ -4967,6 +7151,34 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "_admin_session": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query__admin_session(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "_config": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query__config(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -5369,6 +7581,16 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o // region ***************************** type.gotpl ***************************** +func (ec *executionContext) unmarshalNAdminLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAdminLoginInput(ctx context.Context, v interface{}) (model.AdminLoginInput, error) { + res, err := ec.unmarshalInputAdminLoginInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNAdminSignupInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAdminSignupInput(ctx context.Context, v interface{}) (model.AdminSignupInput, error) { + res, err := ec.unmarshalInputAdminSignupInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalNAuthResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx context.Context, sel ast.SelectionSet, v model.AuthResponse) graphql.Marshaler { return ec._AuthResponse(ctx, sel, &v) } @@ -5398,6 +7620,20 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) marshalNConfig2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐConfig(ctx context.Context, sel ast.SelectionSet, v model.Config) graphql.Marshaler { + return ec._Config(ctx, sel, &v) +} + +func (ec *executionContext) marshalNConfig2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐConfig(ctx context.Context, sel ast.SelectionSet, v *model.Config) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._Config(ctx, sel, v) +} + func (ec *executionContext) unmarshalNDeleteUserInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐDeleteUserInput(ctx context.Context, v interface{}) (model.DeleteUserInput, error) { res, err := ec.unmarshalInputDeleteUserInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -5527,6 +7763,11 @@ func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel return ret } +func (ec *executionContext) unmarshalNUpdateConfigInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateConfigInput(ctx context.Context, v interface{}) (model.UpdateConfigInput, error) { + res, err := ec.unmarshalInputUpdateConfigInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNUpdateProfileInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateProfileInput(ctx context.Context, v interface{}) (model.UpdateProfileInput, error) { res, err := ec.unmarshalInputUpdateProfileInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -5911,13 +8152,6 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a return res } -func (ec *executionContext) marshalOAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx context.Context, sel ast.SelectionSet, v *model.AuthResponse) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._AuthResponse(ctx, sel, v) -} - func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) { res, err := graphql.UnmarshalBoolean(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index ffd76ea..acde6b3 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -2,6 +2,14 @@ package model +type AdminLoginInput struct { + AdminSecret string `json:"admin_secret"` +} + +type AdminSignupInput struct { + AdminSecret string `json:"admin_secret"` +} + type AuthResponse struct { Message string `json:"message"` AccessToken *string `json:"access_token"` @@ -9,6 +17,42 @@ type AuthResponse struct { User *User `json:"user"` } +type Config struct { + AdminSecret *string `json:"ADMIN_SECRET"` + DatabaseType *string `json:"DATABASE_TYPE"` + DatabaseURL *string `json:"DATABASE_URL"` + DatabaseName *string `json:"DATABASE_NAME"` + SMTPHost *string `json:"SMTP_HOST"` + SMTPPort *string `json:"SMTP_PORT"` + SMTPUsername *string `json:"SMTP_USERNAME"` + SMTPPassword *string `json:"SMTP_PASSWORD"` + SenderEmail *string `json:"SENDER_EMAIL"` + JwtType *string `json:"JWT_TYPE"` + JwtSecret *string `json:"JWT_SECRET"` + AllowedOrigins []string `json:"ALLOWED_ORIGINS"` + AuthorizerURL *string `json:"AUTHORIZER_URL"` + AppURL *string `json:"APP_URL"` + RedisURL *string `json:"REDIS_URL"` + CookieName *string `json:"COOKIE_NAME"` + ResetPasswordURL *string `json:"RESET_PASSWORD_URL"` + DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"` + DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"` + DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"` + DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"` + Roles []string `json:"ROLES"` + ProtectedRoles []string `json:"PROTECTED_ROLES"` + DefaultRoles []string `json:"DEFAULT_ROLES"` + JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"` + GoogleClientID *string `json:"GOOGLE_CLIENT_ID"` + GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"` + GithubClientID *string `json:"GITHUB_CLIENT_ID"` + GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"` + FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"` + FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"` + OrganizationName *string `json:"ORGANIZATION_NAME"` + OrganizationLogo *string `json:"ORGANIZATION_LOGO"` +} + type DeleteUserInput struct { Email string `json:"email"` } @@ -73,6 +117,41 @@ type SignUpInput struct { Roles []string `json:"roles"` } +type UpdateConfigInput struct { + AdminSecret *string `json:"ADMIN_SECRET"` + DatabaseType *string `json:"DATABASE_TYPE"` + DatabaseURL *string `json:"DATABASE_URL"` + DatabaseName *string `json:"DATABASE_NAME"` + SMTPHost *string `json:"SMTP_HOST"` + SMTPPort *string `json:"SMTP_PORT"` + SenderEmail *string `json:"SENDER_EMAIL"` + SenderPassword *string `json:"SENDER_PASSWORD"` + JwtType *string `json:"JWT_TYPE"` + JwtSecret *string `json:"JWT_SECRET"` + AllowedOrigins []string `json:"ALLOWED_ORIGINS"` + AuthorizerURL *string `json:"AUTHORIZER_URL"` + AppURL *string `json:"APP_URL"` + RedisURL *string `json:"REDIS_URL"` + CookieName *string `json:"COOKIE_NAME"` + ResetPasswordURL *string `json:"RESET_PASSWORD_URL"` + DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"` + DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"` + DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"` + DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"` + Roles []string `json:"ROLES"` + ProtectedRoles []string `json:"PROTECTED_ROLES"` + DefaultRoles []string `json:"DEFAULT_ROLES"` + JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"` + GoogleClientID *string `json:"GOOGLE_CLIENT_ID"` + GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"` + GithubClientID *string `json:"GITHUB_CLIENT_ID"` + GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"` + FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"` + FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"` + OrganizationName *string `json:"ORGANIZATION_NAME"` + OrganizationLogo *string `json:"ORGANIZATION_LOGO"` +} + type UpdateProfileInput struct { OldPassword *string `json:"old_password"` NewPassword *string `json:"new_password"` diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 4353073..741e9dd 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -62,6 +62,85 @@ type Response { message: String! } +type Config { + ADMIN_SECRET: String + DATABASE_TYPE: String + DATABASE_URL: String + DATABASE_NAME: String + SMTP_HOST: String + SMTP_PORT: String + SMTP_USERNAME: String + SMTP_PASSWORD: String + SENDER_EMAIL: String + JWT_TYPE: String + JWT_SECRET: String + ALLOWED_ORIGINS: [String!] + AUTHORIZER_URL: String + APP_URL: String + REDIS_URL: String + COOKIE_NAME: String + RESET_PASSWORD_URL: String + DISABLE_EMAIL_VERIFICATION: Boolean + DISABLE_BASIC_AUTHENTICATION: Boolean + DISABLE_MAGIC_LINK_LOGIN: Boolean + DISABLE_LOGIN_PAGE: Boolean + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String +} + +input UpdateConfigInput { + ADMIN_SECRET: String + DATABASE_TYPE: String + DATABASE_URL: String + DATABASE_NAME: String + SMTP_HOST: String + SMTP_PORT: String + SENDER_EMAIL: String + SENDER_PASSWORD: String + JWT_TYPE: String + JWT_SECRET: String + ALLOWED_ORIGINS: [String!] + AUTHORIZER_URL: String + APP_URL: String + REDIS_URL: String + COOKIE_NAME: String + RESET_PASSWORD_URL: String + DISABLE_EMAIL_VERIFICATION: Boolean + DISABLE_BASIC_AUTHENTICATION: Boolean + DISABLE_MAGIC_LINK_LOGIN: Boolean + DISABLE_LOGIN_PAGE: Boolean + ROLES: [String!] + PROTECTED_ROLES: [String!] + DEFAULT_ROLES: [String!] + JWT_ROLE_CLAIM: String + GOOGLE_CLIENT_ID: String + GOOGLE_CLIENT_SECRET: String + GITHUB_CLIENT_ID: String + GITHUB_CLIENT_SECRET: String + FACEBOOK_CLIENT_ID: String + FACEBOOK_CLIENT_SECRET: String + ORGANIZATION_NAME: String + ORGANIZATION_LOGO: String +} + +input AdminLoginInput { + admin_secret: String! +} + +input AdminSignupInput { + admin_secret: String! +} + input SignUpInput { email: String! given_name: String @@ -153,13 +232,19 @@ type Mutation { # admin only apis _delete_user(params: DeleteUserInput!): Response! _update_user(params: UpdateUserInput!): User! + _admin_signup(params: AdminSignupInput!): Response! + _admin_login(params: AdminLoginInput!): Response! + _admin_logout: Response! + _update_config(params: UpdateConfigInput!): Response! } type Query { meta: Meta! - session(roles: [String!]): AuthResponse + session(roles: [String!]): AuthResponse! profile: User! # admin only apis _users: [User!]! _verification_requests: [VerificationRequest!]! + _admin_session: Response! + _config: Config! } diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 9510800..52aa7c4 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -55,6 +55,22 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUs return resolvers.UpdateUser(ctx, params) } +func (r *mutationResolver) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) { + return resolvers.AdminSignupResolver(ctx, params) +} + +func (r *mutationResolver) AdminLogin(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) { + return resolvers.AdminLoginResolver(ctx, params) +} + +func (r *mutationResolver) AdminLogout(ctx context.Context) (*model.Response, error) { + return resolvers.AdminLogout(ctx) +} + +func (r *mutationResolver) UpdateConfig(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) { + return resolvers.UpdateConfigResolver(ctx, params) +} + func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) { return resolvers.Meta(ctx) } @@ -75,13 +91,19 @@ func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.Veri return resolvers.VerificationRequests(ctx) } +func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) { + return resolvers.AdminSession(ctx) +} + +func (r *queryResolver) Config(ctx context.Context) (*model.Config, error) { + return resolvers.ConfigResolver(ctx) +} + // Mutation returns generated.MutationResolver implementation. func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -type ( - mutationResolver struct{ *Resolver } - queryResolver struct{ *Resolver } -) +type mutationResolver struct{ *Resolver } +type queryResolver struct{ *Resolver } diff --git a/server/handlers/app.go b/server/handlers/app.go index 4fb7070..e2569e4 100644 --- a/server/handlers/app.go +++ b/server/handlers/app.go @@ -1,7 +1,6 @@ package handlers import ( - "encoding/base64" "encoding/json" "log" "net/http" @@ -30,17 +29,17 @@ func AppHandler() gin.HandlerFunc { // return // } - stateObj.AuthorizerURL = constants.AUTHORIZER_URL - stateObj.RedirectURL = constants.AUTHORIZER_URL + "/app" + stateObj.AuthorizerURL = constants.EnvData.AUTHORIZER_URL + stateObj.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/app" } else { - decodedState, err := base64.StdEncoding.DecodeString(state) + decodedState, err := utils.DecryptB64(state) if err != nil { c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"}) return } - err = json.Unmarshal(decodedState, &stateObj) + err = json.Unmarshal([]byte(decodedState), &stateObj) if err != nil { c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"}) return @@ -60,7 +59,7 @@ func AppHandler() gin.HandlerFunc { } // validate host and domain of authorizer url - if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.AUTHORIZER_URL { + if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.EnvData.AUTHORIZER_URL { c.JSON(400, gin.H{"error": "invalid host url"}) return } @@ -77,8 +76,8 @@ func AppHandler() gin.HandlerFunc { "data": map[string]string{ "authorizerURL": stateObj.AuthorizerURL, "redirectURL": stateObj.RedirectURL, - "organizationName": constants.ORGANIZATION_NAME, - "organizationLogo": constants.ORGANIZATION_LOGO, + "organizationName": constants.EnvData.ORGANIZATION_NAME, + "organizationLogo": constants.EnvData.ORGANIZATION_LOGO, }, }) } diff --git a/server/handlers/dashboard.go b/server/handlers/dashboard.go new file mode 100644 index 0000000..f16e442 --- /dev/null +++ b/server/handlers/dashboard.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "net/http" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/gin-gonic/gin" +) + +func DashboardHandler() gin.HandlerFunc { + return func(c *gin.Context) { + isOnboardingCompleted := false + + if constants.EnvData.ADMIN_SECRET != "" { + isOnboardingCompleted = true + } + + c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{ + "data": map[string]interface{}{ + "isOnboardingCompleted": isOnboardingCompleted, + }, + }) + } +} diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index 4fd0364..a6c922a 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -195,7 +195,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { // make sure inputRoles don't include protected roles hasProtectedRole := false for _, ir := range inputRoles { - if utils.StringSliceContains(constants.PROTECTED_ROLES, ir) { + if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ir) { hasProtectedRole = true } } @@ -238,7 +238,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { // check if it contains protected unassigned role hasProtectedRole := false for _, ur := range unasignedRoles { - if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) { + if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) { hasProtectedRole = true } } diff --git a/server/handlers/oauth_login.go b/server/handlers/oauth_login.go index 1f3cd4e..0363f70 100644 --- a/server/handlers/oauth_login.go +++ b/server/handlers/oauth_login.go @@ -34,14 +34,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.ROLES, constants.PROTECTED_ROLES...)...), rolesSplit) { + if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), rolesSplit) { c.JSON(400, gin.H{ "error": "invalid role", }) return } } else { - roles = strings.Join(constants.DEFAULT_ROLES, ",") + roles = strings.Join(constants.EnvData.DEFAULT_ROLES, ",") } uuid := uuid.New() @@ -57,7 +57,7 @@ func OAuthLoginHandler() gin.HandlerFunc { } session.SetSocailLoginState(oauthStateString, enum.Google.String()) // during the init of OAuthProvider authorizer url might be empty - oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google" + oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google" url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) case enum.Github.String(): @@ -66,7 +66,7 @@ func OAuthLoginHandler() gin.HandlerFunc { break } session.SetSocailLoginState(oauthStateString, enum.Github.String()) - oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github" + oauth.OAuthProviders.GithubConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github" url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) case enum.Facebook.String(): @@ -75,7 +75,7 @@ func OAuthLoginHandler() gin.HandlerFunc { break } session.SetSocailLoginState(oauthStateString, enum.Facebook.String()) - oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook" + oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook" url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url) default: diff --git a/server/main.go b/server/main.go index c709788..7e7c88e 100644 --- a/server/main.go +++ b/server/main.go @@ -22,20 +22,22 @@ func main() { env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path") flag.Parse() - constants.VERSION = VERSION + constants.EnvData.VERSION = VERSION env.InitEnv() db.InitDB() + env.PersistEnv() + session.InitSession() oauth.InitOAuth() utils.InitServer() router := router.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.DISABLE_LOGIN_PAGE { - router.LoadHTMLGlob("templates/*") + if !constants.EnvData.DISABLE_LOGIN_PAGE { app := router.Group("/app") { app.Static("/build", "app/build") @@ -44,5 +46,11 @@ func main() { } } - router.Run(":" + constants.PORT) + app := router.Group("/dashboard") + { + app.Static("/build", "dashboard/build") + app.GET("/", handlers.DashboardHandler()) + } + + router.Run(":" + constants.EnvData.PORT) } diff --git a/server/middlewares/context.go b/server/middlewares/context.go index 32e824b..fd205ac 100644 --- a/server/middlewares/context.go +++ b/server/middlewares/context.go @@ -11,10 +11,10 @@ import ( func GinContextToContextMiddleware() gin.HandlerFunc { return func(c *gin.Context) { - if constants.AUTHORIZER_URL == "" { + if constants.EnvData.AUTHORIZER_URL == "" { url := location.Get(c) - constants.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host - log.Println("authorizer url:", constants.AUTHORIZER_URL) + constants.EnvData.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host + log.Println("authorizer url:", constants.EnvData.AUTHORIZER_URL) } 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 e0bb4a9..f093e8a 100644 --- a/server/middlewares/cors.go +++ b/server/middlewares/cors.go @@ -1,7 +1,6 @@ package middlewares import ( - "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/utils" "github.com/gin-gonic/gin" ) @@ -9,7 +8,6 @@ import ( func CORSMiddleware() gin.HandlerFunc { return func(c *gin.Context) { origin := c.Request.Header.Get("Origin") - constants.APP_URL = origin if utils.IsValidOrigin(origin) { c.Writer.Header().Set("Access-Control-Allow-Origin", origin) diff --git a/server/oauth/oauth.go b/server/oauth/oauth.go index 64b6927..5c6d943 100644 --- a/server/oauth/oauth.go +++ b/server/oauth/oauth.go @@ -28,33 +28,33 @@ var ( func InitOAuth() { ctx := context.Background() - if constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "" { + if constants.EnvData.GOOGLE_CLIENT_ID != "" && constants.EnvData.GOOGLE_CLIENT_SECRET != "" { 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.GOOGLE_CLIENT_ID, - ClientSecret: constants.GOOGLE_CLIENT_SECRET, - RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google", + ClientID: constants.EnvData.GOOGLE_CLIENT_ID, + ClientSecret: constants.EnvData.GOOGLE_CLIENT_SECRET, + RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google", Endpoint: OIDCProviders.GoogleOIDC.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } } - if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" { + if constants.EnvData.GITHUB_CLIENT_ID != "" && constants.EnvData.GITHUB_CLIENT_SECRET != "" { OAuthProviders.GithubConfig = &oauth2.Config{ - ClientID: constants.GITHUB_CLIENT_ID, - ClientSecret: constants.GITHUB_CLIENT_SECRET, - RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github", + ClientID: constants.EnvData.GITHUB_CLIENT_ID, + ClientSecret: constants.EnvData.GITHUB_CLIENT_SECRET, + RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github", Endpoint: githubOAuth2.Endpoint, } } - if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" { + if constants.EnvData.FACEBOOK_CLIENT_ID != "" && constants.EnvData.FACEBOOK_CLIENT_SECRET != "" { OAuthProviders.FacebookConfig = &oauth2.Config{ - ClientID: constants.FACEBOOK_CLIENT_ID, - ClientSecret: constants.FACEBOOK_CLIENT_SECRET, - RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook", + ClientID: constants.EnvData.FACEBOOK_CLIENT_ID, + ClientSecret: constants.EnvData.FACEBOOK_CLIENT_SECRET, + RedirectURL: constants.EnvData.AUTHORIZER_URL + "/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 new file mode 100644 index 0000000..e829454 --- /dev/null +++ b/server/resolvers/admin_login.go @@ -0,0 +1,34 @@ +package resolvers + +import ( + "context" + "fmt" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/utils" +) + +func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + var res *model.Response + + if err != nil { + return res, err + } + + if params.AdminSecret != constants.EnvData.ADMIN_SECRET { + return res, fmt.Errorf(`invalid admin secret`) + } + + hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + if err != nil { + return res, err + } + utils.SetAdminCookie(gc, hashedKey) + + res = &model.Response{ + Message: "admin logged in successfully", + } + return res, nil +} diff --git a/server/resolvers/admin_logout.go b/server/resolvers/admin_logout.go new file mode 100644 index 0000000..d6bb2f8 --- /dev/null +++ b/server/resolvers/admin_logout.go @@ -0,0 +1,29 @@ +package resolvers + +import ( + "context" + "fmt" + + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/utils" +) + +func AdminLogout(ctx context.Context) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + var res *model.Response + + if err != nil { + return res, err + } + + if !utils.IsSuperAdmin(gc) { + return res, fmt.Errorf("unauthorized") + } + + utils.DeleteAdminCookie(gc) + + res = &model.Response{ + Message: "admin logged out successfully", + } + return res, nil +} diff --git a/server/resolvers/admin_session.go b/server/resolvers/admin_session.go new file mode 100644 index 0000000..50a5ab6 --- /dev/null +++ b/server/resolvers/admin_session.go @@ -0,0 +1,34 @@ +package resolvers + +import ( + "context" + "fmt" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/utils" +) + +func AdminSession(ctx context.Context) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + var res *model.Response + + if err != nil { + return res, err + } + + if !utils.IsSuperAdmin(gc) { + return res, fmt.Errorf("unauthorized") + } + + hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + if err != nil { + return res, err + } + utils.SetAdminCookie(gc, hashedKey) + + res = &model.Response{ + Message: "admin logged in successfully", + } + return res, nil +} diff --git a/server/resolvers/admin_signup.go b/server/resolvers/admin_signup.go new file mode 100644 index 0000000..2a943de --- /dev/null +++ b/server/resolvers/admin_signup.go @@ -0,0 +1,77 @@ +package resolvers + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/utils" +) + +func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + var res *model.Response + + if err != nil { + return res, err + } + + if strings.TrimSpace(params.AdminSecret) == "" { + err = fmt.Errorf("please select secure admin secret") + return res, err + } + + if len(params.AdminSecret) < 6 { + err = fmt.Errorf("admin secret must be at least 6 characters") + return res, err + } + + if constants.EnvData.ADMIN_SECRET != "" { + err = fmt.Errorf("admin sign up already completed") + return res, err + } + + constants.EnvData.ADMIN_SECRET = params.AdminSecret + + // consvert EnvData to JSON + var jsonData map[string]interface{} + + jsonBytes, err := json.Marshal(constants.EnvData) + if err != nil { + return res, err + } + + if err := json.Unmarshal(jsonBytes, &jsonData); err != nil { + return res, err + } + + config, err := db.Mgr.GetConfig() + if err != nil { + return res, err + } + + configData, err := utils.EncryptConfig(jsonData) + if err != nil { + return res, err + } + + config.Config = configData + if _, err := db.Mgr.UpdateConfig(config); err != nil { + return res, err + } + + hashedKey, err := utils.HashPassword(params.AdminSecret) + if err != nil { + return res, err + } + utils.SetAdminCookie(gc, hashedKey) + + res = &model.Response{ + Message: "admin signed up successfully", + } + return res, nil +} diff --git a/server/resolvers/config.go b/server/resolvers/config.go new file mode 100644 index 0000000..10bdd64 --- /dev/null +++ b/server/resolvers/config.go @@ -0,0 +1,60 @@ +package resolvers + +import ( + "context" + "fmt" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/utils" +) + +func ConfigResolver(ctx context.Context) (*model.Config, error) { + gc, err := utils.GinContextFromContext(ctx) + var res *model.Config + + if err != nil { + return res, err + } + + if !utils.IsSuperAdmin(gc) { + return res, fmt.Errorf("unauthorized") + } + + 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, + } + return res, nil +} diff --git a/server/resolvers/forgot_password.go b/server/resolvers/forgot_password.go index fc5ba16..8c5c583 100644 --- a/server/resolvers/forgot_password.go +++ b/server/resolvers/forgot_password.go @@ -20,7 +20,7 @@ func ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*mod if err != nil { return res, err } - if constants.DISABLE_BASIC_AUTHENTICATION { + if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } host := gc.Request.Host diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 00b444e..482cf4b 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -22,7 +22,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e return res, err } - if constants.DISABLE_BASIC_AUTHENTICATION { + if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } @@ -46,7 +46,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.DEFAULT_ROLES + roles := constants.EnvData.DEFAULT_ROLES currentRoles := strings.Split(user.Roles, ",") if len(params.Roles) > 0 { if !utils.IsValidRoles(currentRoles, params.Roles) { diff --git a/server/resolvers/magic_link_login.go b/server/resolvers/magic_link_login.go index 2bb3d9d..8ba726a 100644 --- a/server/resolvers/magic_link_login.go +++ b/server/resolvers/magic_link_login.go @@ -17,7 +17,7 @@ import ( func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) { var res *model.Response - if constants.DISABLE_MAGIC_LINK_LOGIN { + if constants.EnvData.DISABLE_MAGIC_LINK_LOGIN { return res, fmt.Errorf(`magic link login is disabled for this instance`) } @@ -41,13 +41,13 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod // define roles for new user if len(params.Roles) > 0 { // check if roles exists - if !utils.IsValidRoles(constants.ROLES, params.Roles) { + if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) { return res, fmt.Errorf(`invalid roles`) } else { inputRoles = params.Roles } } else { - inputRoles = constants.DEFAULT_ROLES + inputRoles = constants.EnvData.DEFAULT_ROLES } user.Roles = strings.Join(inputRoles, ",") @@ -72,7 +72,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.PROTECTED_ROLES, ur) { + if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) { hasProtectedRole = true } } @@ -98,7 +98,7 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod } } - if !constants.DISABLE_EMAIL_VERIFICATION { + if !constants.EnvData.DISABLE_EMAIL_VERIFICATION { // insert verification request verificationType := enum.MagicLinkLogin.String() token, err := utils.CreateVerificationToken(params.Email, verificationType) diff --git a/server/resolvers/reset_password.go b/server/resolvers/reset_password.go index 3ad2a03..a83cf52 100644 --- a/server/resolvers/reset_password.go +++ b/server/resolvers/reset_password.go @@ -15,7 +15,7 @@ import ( func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { var res *model.Response - if constants.DISABLE_BASIC_AUTHENTICATION { + if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } diff --git a/server/resolvers/session.go b/server/resolvers/session.go index cecc0fe..a587deb 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -45,7 +45,7 @@ func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { expiresTimeObj := time.Unix(expiresAt, 0) currentTimeObj := time.Now() - claimRoleInterface := claim[constants.JWT_ROLE_CLAIM].([]interface{}) + claimRoleInterface := claim[constants.EnvData.JWT_ROLE_CLAIM].([]interface{}) claimRoles := make([]string, len(claimRoleInterface)) for i, v := range claimRoleInterface { claimRoles[i] = v.(string) diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index aa9a241..ba19b45 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -22,7 +22,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, return res, err } - if constants.DISABLE_BASIC_AUTHENTICATION { + if constants.EnvData.DISABLE_BASIC_AUTHENTICATION { return res, fmt.Errorf(`basic authentication is disabled for this instance`) } if params.ConfirmPassword != params.Password { @@ -52,13 +52,13 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, if len(params.Roles) > 0 { // check if roles exists - if !utils.IsValidRoles(constants.ROLES, params.Roles) { + if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) { return res, fmt.Errorf(`invalid roles`) } else { inputRoles = params.Roles } } else { - inputRoles = constants.DEFAULT_ROLES + inputRoles = constants.EnvData.DEFAULT_ROLES } user := db.User{ @@ -103,7 +103,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, } user.SignupMethods = enum.BasicAuth.String() - if constants.DISABLE_EMAIL_VERIFICATION { + if constants.EnvData.DISABLE_EMAIL_VERIFICATION { now := time.Now().Unix() user.EmailVerifiedAt = &now } @@ -115,7 +115,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, roles := strings.Split(user.Roles, ",") userToReturn := utils.GetResponseUserData(user) - if !constants.DISABLE_EMAIL_VERIFICATION { + if !constants.EnvData.DISABLE_EMAIL_VERIFICATION { // insert verification request verificationType := enum.BasicAuthSignup.String() token, err := utils.CreateVerificationToken(params.Email, verificationType) diff --git a/server/resolvers/update_config.go b/server/resolvers/update_config.go new file mode 100644 index 0000000..b1a407c --- /dev/null +++ b/server/resolvers/update_config.go @@ -0,0 +1,105 @@ +package resolvers + +import ( + "context" + "encoding/json" + "fmt" + "log" + "reflect" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/utils" +) + +func UpdateConfigResolver(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + var res *model.Response + + if err != nil { + return res, err + } + + if !utils.IsSuperAdmin(gc) { + return res, fmt.Errorf("unauthorized") + } + + var data map[string]interface{} + byteData, err := json.Marshal(params) + if err != nil { + return res, fmt.Errorf("error marshalling params: %t", err) + } + + err = json.Unmarshal(byteData, &data) + if err != nil { + return res, fmt.Errorf("error un-marshalling params: %t", err) + } + + updatedData := make(map[string]interface{}) + for key, value := range data { + if value != nil { + fieldType := reflect.TypeOf(value).String() + + if fieldType == "string" || fieldType == "bool" { + updatedData[key] = value + } + + if fieldType == "[]interface {}" { + stringArr := []string{} + for _, v := range value.([]interface{}) { + stringArr = append(stringArr, v.(string)) + } + updatedData[key] = stringArr + } + } + } + + // 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["DISABLE_MAGIC_LINK_LOGIN"].(bool) { + updatedData["DISABLE_MAGIC_LINK_LOGIN"] = true + } + } + + config, err := db.Mgr.GetConfig() + if err != nil { + return res, err + } + + encryptedConfig, err := utils.EncryptConfig(updatedData) + if err != nil { + return res, err + } + + // in case of db change re-initialize db + if params.DatabaseType != nil || params.DatabaseURL != nil || params.DatabaseName != nil { + db.InitDB() + } + + // in case of admin secret change update the cookie with new hash + if params.AdminSecret != nil { + hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET) + if err != nil { + return res, err + } + utils.SetAdminCookie(gc, hashedKey) + } + + config.Config = encryptedConfig + _, err = db.Mgr.UpdateConfig(config) + if err != nil { + log.Println("error updating config:", err) + return res, err + } + + res = &model.Response{ + Message: "configurations updated successfully", + } + return res, nil +} diff --git a/server/resolvers/update_user.go b/server/resolvers/update_user.go index 93222de..c67021b 100644 --- a/server/resolvers/update_user.go +++ b/server/resolvers/update_user.go @@ -112,7 +112,7 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, inputRoles = append(inputRoles, *item) } - if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), inputRoles) { + if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), inputRoles) { return res, fmt.Errorf("invalid list of roles") } diff --git a/server/session/session.go b/server/session/session.go index 08138b0..39727b3 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -95,9 +95,9 @@ func RemoveSocialLoginState(key string) { } func InitSession() { - if constants.REDIS_URL != "" { + if constants.EnvData.REDIS_URL != "" { log.Println("using redis store to save sessions") - opt, err := redis.ParseURL(constants.REDIS_URL) + opt, err := redis.ParseURL(constants.EnvData.REDIS_URL) if err != nil { log.Fatalln("Error parsing redis url:", err) } diff --git a/server/utils/auth_token.go b/server/utils/auth_token.go index 23995de..6003291 100644 --- a/server/utils/auth_token.go +++ b/server/utils/auth_token.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "log" + "net/url" "os" "strings" "time" @@ -14,10 +15,11 @@ import ( "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.JWT_TYPE)) + t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE)) expiryBound := time.Hour if tokenType == enum.RefreshToken { // expires in 1 year @@ -32,11 +34,11 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st json.Unmarshal(userBytes, &userMap) customClaims := jwt.MapClaims{ - "exp": expiresAt, - "iat": time.Now().Unix(), - "token_type": tokenType.String(), - "allowed_roles": strings.Split(user.Roles, ","), - constants.JWT_ROLE_CLAIM: roles, + "exp": expiresAt, + "iat": time.Now().Unix(), + "token_type": tokenType.String(), + "allowed_roles": strings.Split(user.Roles, ","), + constants.EnvData.JWT_ROLE_CLAIM: roles, } for k, v := range userMap { @@ -77,7 +79,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st t.Claims = customClaims - token, err := t.SignedString([]byte(constants.JWT_SECRET)) + token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET)) if err != nil { return "", 0, err } @@ -89,7 +91,6 @@ func GetAuthToken(gc *gin.Context) (string, error) { token, err := GetCookie(gc) if err != nil || token == "" { // try to check in auth header for cookie - log.Println("cookie not found checking headers") auth := gc.Request.Header.Get("Authorization") if auth == "" { return "", fmt.Errorf(`unauthorized`) @@ -105,7 +106,7 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) { claims := jwt.MapClaims{} _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { - return []byte(constants.JWT_SECRET), nil + return []byte(constants.EnvData.JWT_SECRET), nil }) if err != nil { return res, err @@ -124,3 +125,29 @@ 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) +} + +func GetAdminAuthToken(gc *gin.Context) (string, error) { + token, err := GetAdminCookie(gc) + if err != nil || token == "" { + return "", fmt.Errorf("unauthorized") + } + + // cookie escapes special characters like $ + // hence we need to unescape before comparing + decodedValue, err := url.QueryUnescape(token) + if err != nil { + return "", err + } + + err = bcrypt.CompareHashAndPassword([]byte(decodedValue), []byte(constants.EnvData.ADMIN_SECRET)) + log.Println("error comparing hash:", err) + if err != nil { + return "", fmt.Errorf(`unauthorized`) + } + + return token, nil +} diff --git a/server/utils/cookie.go b/server/utils/cookie.go index 38cf327..7319a21 100644 --- a/server/utils/cookie.go +++ b/server/utils/cookie.go @@ -10,21 +10,21 @@ import ( func SetCookie(gc *gin.Context, token string) { secure := true httpOnly := true - host, _ := GetHostParts(constants.AUTHORIZER_URL) - domain := GetDomainName(constants.AUTHORIZER_URL) + host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) + domain := GetDomainName(constants.EnvData.AUTHORIZER_URL) if domain != "localhost" { domain = "." + domain } gc.SetSameSite(http.SameSiteNoneMode) - gc.SetCookie(constants.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly) - gc.SetCookie(constants.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly) + gc.SetCookie(constants.EnvData.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly) + gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly) } func GetCookie(gc *gin.Context) (string, error) { - cookie, err := gc.Request.Cookie(constants.COOKIE_NAME) + cookie, err := gc.Request.Cookie(constants.EnvData.COOKIE_NAME) if err != nil { - cookie, err = gc.Request.Cookie(constants.COOKIE_NAME + "-client") + cookie, err = gc.Request.Cookie(constants.EnvData.COOKIE_NAME + "-client") if err != nil { return "", err } @@ -37,13 +37,37 @@ func DeleteCookie(gc *gin.Context) { secure := true httpOnly := true - host, _ := GetHostParts(constants.AUTHORIZER_URL) - domain := GetDomainName(constants.AUTHORIZER_URL) + host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) + domain := GetDomainName(constants.EnvData.AUTHORIZER_URL) if domain != "localhost" { domain = "." + domain } gc.SetSameSite(http.SameSiteNoneMode) - gc.SetCookie(constants.COOKIE_NAME, "", -1, "/", host, secure, httpOnly) - gc.SetCookie(constants.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly) + gc.SetCookie(constants.EnvData.COOKIE_NAME, "", -1, "/", host, secure, httpOnly) + gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly) +} + +func SetAdminCookie(gc *gin.Context, token string) { + secure := true + httpOnly := true + host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) + + gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, token, 3600, "/", host, secure, httpOnly) +} + +func GetAdminCookie(gc *gin.Context) (string, error) { + cookie, err := gc.Request.Cookie(constants.EnvData.ADMIN_COOKIE_NAME) + if err != nil { + return "", err + } + return cookie.Value, nil +} + +func DeleteAdminCookie(gc *gin.Context) { + secure := true + httpOnly := true + host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL) + + gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, "", -1, "/", host, secure, httpOnly) } diff --git a/server/utils/crypto.go b/server/utils/crypto.go new file mode 100644 index 0000000..de7ce39 --- /dev/null +++ b/server/utils/crypto.go @@ -0,0 +1,83 @@ +package utils + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "io" + + "github.com/authorizerdev/authorizer/server/constants" +) + +func EncryptB64(text string) string { + return base64.StdEncoding.EncodeToString([]byte(text)) +} + +func DecryptB64(s string) (string, error) { + data, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return "", err + } + return string(data), nil +} + +func EncryptAES(text []byte) ([]byte, error) { + key := []byte(constants.EnvData.ENCRYPTION_KEY) + c, err := aes.NewCipher(key) + var res []byte + if err != nil { + return res, err + } + + // gcm or Galois/Counter Mode, is a mode of operation + // for symmetric key cryptographic block ciphers + // - https://en.wikipedia.org/wiki/Galois/Counter_Mode + gcm, err := cipher.NewGCM(c) + if err != nil { + return res, err + } + + // creates a new byte array the size of the nonce + // which must be passed to Seal + nonce := make([]byte, gcm.NonceSize()) + // populates our nonce with a cryptographically secure + // random sequence + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return res, err + } + + // here we encrypt our text using the Seal function + // Seal encrypts and authenticates plaintext, authenticates the + // additional data and appends the result to dst, returning the updated + // slice. The nonce must be NonceSize() bytes long and unique for all + // time, for a given key. + return gcm.Seal(nonce, nonce, text, nil), nil +} + +func DecryptAES(ciphertext []byte) ([]byte, error) { + key := []byte(constants.EnvData.ENCRYPTION_KEY) + c, err := aes.NewCipher(key) + var res []byte + if err != nil { + return res, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return res, err + } + + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return res, err + } + + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return res, err + } + + return plaintext, nil +} diff --git a/server/utils/email.go b/server/utils/email.go index 1a4028a..fbb8edf 100644 --- a/server/utils/email.go +++ b/server/utils/email.go @@ -101,9 +101,9 @@ func SendVerificationMail(toEmail, token string) error { ` data := make(map[string]interface{}, 3) - data["OrgLogo"] = constants.ORGANIZATION_LOGO - data["OrgName"] = constants.ORGANIZATION_NAME - data["AuthUrl"] = constants.AUTHORIZER_URL + "/verify_email?token=" + token + 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) @@ -112,8 +112,8 @@ func SendVerificationMail(toEmail, token string) error { // SendForgotPasswordMail to send verification email func SendForgotPasswordMail(toEmail, token, host string) error { - if constants.RESET_PASSWORD_URL == "" { - constants.RESET_PASSWORD_URL = constants.AUTHORIZER_URL + "/app/reset-password" + 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 @@ -207,9 +207,9 @@ func SendForgotPasswordMail(toEmail, token, host string) error { ` data := make(map[string]interface{}, 3) - data["OrgLogo"] = constants.ORGANIZATION_LOGO - data["ToEmail"] = constants.ORGANIZATION_NAME - data["AuthUrl"] = constants.RESET_PASSWORD_URL + "?token=" + token + 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) diff --git a/server/utils/encrypt_config.go b/server/utils/encrypt_config.go new file mode 100644 index 0000000..692321f --- /dev/null +++ b/server/utils/encrypt_config.go @@ -0,0 +1,30 @@ +package utils + +import ( + "encoding/json" + + "github.com/authorizerdev/authorizer/server/constants" +) + +func EncryptConfig(data map[string]interface{}) ([]byte, error) { + jsonBytes, err := json.Marshal(data) + if err != nil { + return []byte{}, err + } + + err = json.Unmarshal(jsonBytes, &constants.EnvData) + if err != nil { + return []byte{}, err + } + + configData, err := json.Marshal(constants.EnvData) + if err != nil { + return []byte{}, err + } + encryptedConfig, err := EncryptAES(configData) + if err != nil { + return []byte{}, err + } + + return encryptedConfig, nil +} diff --git a/server/utils/validator.go b/server/utils/validator.go index 4c0c6ed..74b718a 100644 --- a/server/utils/validator.go +++ b/server/utils/validator.go @@ -16,7 +16,7 @@ func IsValidEmail(email string) bool { } func IsValidOrigin(url string) bool { - if len(constants.ALLOWED_ORIGINS) == 1 && constants.ALLOWED_ORIGINS[0] == "*" { + if len(constants.EnvData.ALLOWED_ORIGINS) == 1 && constants.EnvData.ALLOWED_ORIGINS[0] == "*" { return true } @@ -24,7 +24,7 @@ func IsValidOrigin(url string) bool { hostName, port := GetHostParts(url) currentOrigin := hostName + ":" + port - for _, origin := range constants.ALLOWED_ORIGINS { + for _, origin := range constants.EnvData.ALLOWED_ORIGINS { replacedString := origin // if has regex whitelisted domains if strings.Contains(origin, "*") { @@ -50,12 +50,17 @@ func IsValidOrigin(url string) bool { } func IsSuperAdmin(gc *gin.Context) bool { - secret := gc.Request.Header.Get("x-authorizer-admin-secret") - if secret == "" { - return false + token, err := GetAdminAuthToken(gc) + if err != nil { + secret := gc.Request.Header.Get("x-authorizer-admin-secret") + if secret == "" { + return false + } + + return secret == constants.EnvData.ADMIN_SECRET } - return secret == constants.ADMIN_SECRET + return token != "" } func IsValidRoles(userRoles []string, roles []string) bool { diff --git a/server/utils/verification_token.go b/server/utils/verification_token.go index 7dbef67..254e8ac 100644 --- a/server/utils/verification_token.go +++ b/server/utils/verification_token.go @@ -20,23 +20,23 @@ type CustomClaim struct { } func CreateVerificationToken(email string, tokenType string) (string, error) { - t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE)) + t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE)) t.Claims = &CustomClaim{ &jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), }, tokenType, - UserInfo{Email: email, Host: constants.AUTHORIZER_URL, RedirectURL: constants.APP_URL}, + UserInfo{Email: email, Host: constants.EnvData.AUTHORIZER_URL, RedirectURL: constants.EnvData.APP_URL}, } - return t.SignedString([]byte(constants.JWT_SECRET)) + return t.SignedString([]byte(constants.EnvData.JWT_SECRET)) } func VerifyVerificationToken(token string) (*CustomClaim, error) { claims := &CustomClaim{} _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { - return []byte(constants.JWT_SECRET), nil + return []byte(constants.EnvData.JWT_SECRET), nil }) if err != nil { return claims, err diff --git a/templates/dashboard.tmpl b/templates/dashboard.tmpl new file mode 100644 index 0000000..afc046c --- /dev/null +++ b/templates/dashboard.tmpl @@ -0,0 +1,16 @@ + + + + + + + Document + + + +
+ + +