Compare commits
160 Commits
chore/upda
...
1.1.41
Author | SHA1 | Date | |
---|---|---|---|
![]() |
df406ba053 | ||
![]() |
4a7877a21b | ||
![]() |
79089cc009 | ||
![]() |
149d0cac7a | ||
![]() |
8863140e75 | ||
![]() |
b8ffadd36c | ||
![]() |
7dd20128af | ||
![]() |
19f5ff61c0 | ||
![]() |
146707d062 | ||
![]() |
0810c4a201 | ||
![]() |
3603af9f84 | ||
![]() |
1ac8ba4ce0 | ||
![]() |
cdcdc444b2 | ||
![]() |
330f35f2fc | ||
![]() |
70242debe1 | ||
![]() |
4018da6697 | ||
![]() |
a73c6ee49e | ||
![]() |
c23fb1bb32 | ||
![]() |
270853a6a3 | ||
![]() |
2d0346ff23 | ||
![]() |
4b26e1ce85 | ||
![]() |
8212e81023 | ||
![]() |
642581eefd | ||
![]() |
b7357dde21 | ||
![]() |
a1df2ce31f | ||
![]() |
748761926d | ||
![]() |
d632195ba5 | ||
![]() |
25970f80e1 | ||
![]() |
a52b7c86e7 | ||
![]() |
504d0f34d7 | ||
![]() |
44879f1a8f | ||
![]() |
b39f0b87fd | ||
![]() |
b2423140e2 | ||
![]() |
4f810d2f8b | ||
![]() |
313b510ba1 | ||
![]() |
105d9be685 | ||
![]() |
bc68b61879 | ||
![]() |
2847300bf6 | ||
![]() |
d438480f37 | ||
![]() |
da29f9d055 | ||
![]() |
f29256a8f5 | ||
![]() |
1eb8965f98 | ||
![]() |
1c4e29fa7c | ||
![]() |
7a28795fa0 | ||
![]() |
f5db00beb0 | ||
![]() |
d515a1f41d | ||
![]() |
c948c98e94 | ||
![]() |
e985e096bc | ||
![]() |
70bab70ead | ||
![]() |
6ddaf88e3f | ||
![]() |
f8bcd0fe51 | ||
![]() |
16c4b8ab76 | ||
![]() |
0788c5ff5e | ||
![]() |
2d5d38de02 | ||
![]() |
0dd06d9afd | ||
![]() |
d2f472a9cf | ||
![]() |
4678193300 | ||
![]() |
67be8ae285 | ||
![]() |
f9d2130c65 | ||
![]() |
bb2a42a1db | ||
![]() |
f857c993c8 | ||
![]() |
824f286b9b | ||
![]() |
ecefe12355 | ||
![]() |
5c8f9406f6 | ||
![]() |
75a547cfe2 | ||
![]() |
579899c397 | ||
![]() |
9320f1cb07 | ||
![]() |
c09558043e | ||
![]() |
49556b1709 | ||
![]() |
4775641431 | ||
![]() |
ae84213e34 | ||
![]() |
87bf1c3045 | ||
![]() |
e525877467 | ||
![]() |
b467e7002d | ||
![]() |
78bdd10a15 | ||
![]() |
512fd4f1f7 | ||
![]() |
67da4a49e4 | ||
![]() |
48deae1d11 | ||
![]() |
1f2ded4219 | ||
![]() |
cb5af1e679 | ||
![]() |
27160ecbd5 | ||
![]() |
c6c3af1114 | ||
![]() |
e54b7f18f0 | ||
![]() |
a18046748b | ||
![]() |
1bff6720fc | ||
![]() |
024ffd85f3 | ||
![]() |
e171820614 | ||
![]() |
19f9caf478 | ||
![]() |
4afd544c41 | ||
![]() |
307c6f7d15 | ||
![]() |
bbc6394cf3 | ||
![]() |
63c8e2e55f | ||
![]() |
b224892a39 | ||
![]() |
13edf1965c | ||
![]() |
1f220a5205 | ||
![]() |
32fb954a1c | ||
![]() |
65eadb66fa | ||
![]() |
9ce53eb8e8 | ||
![]() |
3b196f074b | ||
![]() |
f2fe584793 | ||
![]() |
287b952dad | ||
![]() |
e690066652 | ||
![]() |
0f67d74657 | ||
![]() |
274909b7c9 | ||
![]() |
549385e5df | ||
![]() |
6e09307c22 | ||
![]() |
7fc69dcc55 | ||
![]() |
8449821d1b | ||
![]() |
476bdf00fc | ||
![]() |
e41f123866 | ||
![]() |
094782aeca | ||
![]() |
9630cbbc3e | ||
![]() |
1ac060136a | ||
![]() |
c6019e650b | ||
![]() |
b2e0a3371f | ||
![]() |
a68876a6f4 | ||
![]() |
2c867b0314 | ||
![]() |
74b858ac24 | ||
![]() |
fedc3173fe | ||
![]() |
de4381261e | ||
![]() |
a916b8c32c | ||
![]() |
89f08b6d31 | ||
![]() |
cc23784df8 | ||
![]() |
7ff3b3018a | ||
![]() |
2b52932e98 | ||
![]() |
c716638725 | ||
![]() |
252cd1fa2d | ||
![]() |
7c2693b086 | ||
![]() |
eaa10ec5bc | ||
![]() |
253128ca0c | ||
![]() |
cddfe1e088 | ||
![]() |
8e655bcb5b | ||
![]() |
9a411e673c | ||
![]() |
346c8e5a47 | ||
![]() |
3cd99fe5f6 | ||
![]() |
2bd92d6028 | ||
![]() |
844e867d96 | ||
![]() |
ff805e3ef2 | ||
![]() |
0115128ee7 | ||
![]() |
820d294130 | ||
![]() |
e37472d498 | ||
![]() |
589af96888 | ||
![]() |
dccc70e5c0 | ||
![]() |
d8eceadd7f | ||
![]() |
e6c4fdff26 | ||
![]() |
896d8e046d | ||
![]() |
a6d5d4af24 | ||
![]() |
cc4eaa2847 | ||
![]() |
dba8944565 | ||
![]() |
e760a5598e | ||
![]() |
f62a22619b | ||
![]() |
c32a7fa1e4 | ||
![]() |
399b97079d | ||
![]() |
fe687cb0ca | ||
![]() |
9cb011e921 | ||
![]() |
4e1bba2ba8 | ||
![]() |
f1509f90f0 | ||
![]() |
bd4d48c7c5 | ||
![]() |
0e3242372b | ||
![]() |
89cea39c41 |
@@ -6,4 +6,5 @@ SMTP_HOST=smtp.mailtrap.io
|
||||
SMTP_PORT=2525
|
||||
SMTP_USERNAME=test
|
||||
SMTP_PASSWORD=test
|
||||
SENDER_EMAIL="info@authorizer.dev"
|
||||
SENDER_EMAIL="info@authorizer.dev"
|
||||
AWS_REGION=ap-south-1
|
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: authorizerdev
|
55
.github/workflows/release.yaml
vendored
55
.github/workflows/release.yaml
vendored
@@ -2,36 +2,44 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
logLevel:
|
||||
description: 'Log level'
|
||||
description: 'Log level'
|
||||
required: true
|
||||
default: 'warning'
|
||||
default: 'warning'
|
||||
type: choice
|
||||
options:
|
||||
- info
|
||||
- warning
|
||||
- debug
|
||||
- info
|
||||
- warning
|
||||
- debug
|
||||
tags:
|
||||
description: 'Tags'
|
||||
required: false
|
||||
required: false
|
||||
type: boolean
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
releases:
|
||||
name: Release Authorizer Binary
|
||||
name: Release Authorizer
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- # Add support for more platforms with QEMU (optional)
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.17.3'
|
||||
go-version: '^1.19.1'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install build-essential wget zip gcc-mingw-w64 && \
|
||||
sudo apt-get install build-essential wget zip libc6-dev-arm64-cross && \
|
||||
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH && \
|
||||
wget --no-check-certificate --progress=dot:mega https://github.com/wangyoucao577/assets-uploader/releases/download/v0.3.0/github-assets-uploader-v0.3.0-linux-amd64.tar.gz -O github-assets-uploader.tar.gz && \
|
||||
tar -zxf github-assets-uploader.tar.gz && \
|
||||
@@ -44,25 +52,26 @@ jobs:
|
||||
run: whereis go
|
||||
- name: Print Go Version
|
||||
run: go version
|
||||
- name: Install gox
|
||||
run: go install github.com/mitchellh/gox@latest
|
||||
- name: Set VERSION env
|
||||
run: echo VERSION=$(basename ${GITHUB_REF}) >> ${GITHUB_ENV}
|
||||
- name: Copy .env file
|
||||
run: mv .env.sample .env
|
||||
- name: Package files for windows
|
||||
- name: Build package
|
||||
run: |
|
||||
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 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 dashboard/build
|
||||
make build && \
|
||||
mkdir -p authorizer-${VERSION}-darwin-amd64/build authorizer-${VERSION}-darwin-amd64/app authorizer-${VERSION}-darwin-amd64/dashboard && cp build/darwin/amd64/server authorizer-${VERSION}-darwin-amd64/build/ && cp .env authorizer-${VERSION}-darwin-amd64/.env && cp -rf app/build authorizer-${VERSION}-darwin-amd64/app/build && cp -rf templates authorizer-${VERSION}-darwin-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-darwin-amd64/dashboard/build && tar cvfz authorizer-${VERSION}-darwin-amd64.tar.gz authorizer-${VERSION}-darwin-amd64 && \
|
||||
mkdir -p authorizer-${VERSION}-linux-amd64/build authorizer-${VERSION}-linux-amd64/app authorizer-${VERSION}-linux-amd64/dashboard && cp build/linux/amd64/server authorizer-${VERSION}-linux-amd64/build/ && cp .env authorizer-${VERSION}-linux-amd64/.env && cp -rf app/build authorizer-${VERSION}-linux-amd64/app/build && cp -rf templates authorizer-${VERSION}-linux-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-linux-amd64/dashboard/build && tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz authorizer-${VERSION}-linux-amd64 && \
|
||||
mkdir -p authorizer-${VERSION}-linux-arm64/build authorizer-${VERSION}-linux-arm64/app authorizer-${VERSION}-linux-arm64/dashboard && cp build/linux/arm64/server authorizer-${VERSION}-linux-arm64/build/ && cp .env authorizer-${VERSION}-linux-arm64/.env && cp -rf app/build authorizer-${VERSION}-linux-arm64/app/build && cp -rf templates authorizer-${VERSION}-linux-arm64/ && cp -rf dashboard/build authorizer-${VERSION}-linux-arm64/dashboard/build && tar cvfz authorizer-${VERSION}-linux-arm64.tar.gz authorizer-${VERSION}-linux-arm64 && \
|
||||
mkdir -p authorizer-${VERSION}-windows-amd64/build authorizer-${VERSION}-windows-amd64/app authorizer-${VERSION}-windows-amd64/dashboard && cp build/windows/amd64/server.exe authorizer-${VERSION}-windows-amd64/build/ && cp .env authorizer-${VERSION}-windows-amd64/.env && cp -rf app/build authorizer-${VERSION}-windows-amd64/app/build && cp -rf templates authorizer-${VERSION}-windows-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-windows-amd64/dashboard/build && zip -vr authorizer-${VERSION}-windows-amd64.zip authorizer-${VERSION}-windows-amd64
|
||||
- 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} && \
|
||||
github-assets-uploader -f authorizer-${VERSION}-darwin-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
|
||||
github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
|
||||
github-assets-uploader -f authorizer-${VERSION}-linux-arm64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
|
||||
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -74,6 +83,11 @@ jobs:
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: lakhansamani/authorizer
|
||||
tags: |
|
||||
type=schedule
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=ref,event=pr
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
@@ -82,5 +96,6 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,4 +15,6 @@ test.db
|
||||
.vscode/
|
||||
.yalc
|
||||
yalc.lock
|
||||
certs/
|
||||
certs/
|
||||
*-shm
|
||||
*-wal
|
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.19.1-alpine as go-builder
|
||||
FROM golang:1.19.5-alpine as go-builder
|
||||
WORKDIR /authorizer
|
||||
COPY server server
|
||||
COPY Makefile .
|
||||
|
30
Makefile
30
Makefile
@@ -3,6 +3,12 @@ VERSION := $(or $(VERSION),$(DEFAULT_VERSION))
|
||||
|
||||
cmd:
|
||||
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
|
||||
build:
|
||||
cd server && gox \
|
||||
-osarch="linux/amd64 linux/arm64 darwin/amd64 windows/amd64" \
|
||||
-ldflags "-w -X main.VERSION=$(VERSION)" \
|
||||
-output="../build/{{.OS}}/{{.Arch}}/server" \
|
||||
./...
|
||||
build-app:
|
||||
cd app && npm i && npm run build
|
||||
build-dashboard:
|
||||
@@ -10,7 +16,7 @@ build-dashboard:
|
||||
clean:
|
||||
rm -rf build
|
||||
test:
|
||||
rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./test
|
||||
rm -rf server/test/test.db server/test/test.db-shm server/test/test.db-wal && rm -rf test.db test.db-shm test.db-wal && cd server && go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./test
|
||||
test-mongodb:
|
||||
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
|
||||
cd server && go clean --testcache && TEST_DBS="mongodb" go test -p 1 -v ./test
|
||||
@@ -20,17 +26,31 @@ test-scylladb:
|
||||
cd server && go clean --testcache && TEST_DBS="scylladb" go test -p 1 -v ./test
|
||||
docker rm -vf authorizer_scylla_db
|
||||
test-arangodb:
|
||||
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
|
||||
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.10.3
|
||||
cd server && go clean --testcache && TEST_DBS="arangodb" go test -p 1 -v ./test
|
||||
docker rm -vf authorizer_arangodb
|
||||
test-dynamodb:
|
||||
docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest
|
||||
cd server && go clean --testcache && TEST_DBS="dynamodb" go test -p 1 -v ./test
|
||||
docker rm -vf dynamodb-local-test
|
||||
test-couchbase:
|
||||
docker run -d --name couchbase-local-test -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase:latest
|
||||
sh scripts/couchbase-test.sh
|
||||
cd server && go clean --testcache && TEST_DBS="couchbase" go test -p 1 -v ./test
|
||||
docker rm -vf couchbase-local-test
|
||||
test-all-db:
|
||||
rm -rf server/test/test.db && rm -rf test.db
|
||||
rm -rf server/test/test.db server/test/test.db-shm server/test/test.db-wal && rm -rf test.db test.db-shm test.db-wal
|
||||
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
|
||||
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
|
||||
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
|
||||
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test
|
||||
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.10.3
|
||||
docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest
|
||||
docker run -d --name couchbase-local-test -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase:latest
|
||||
sh scripts/couchbase-test.sh
|
||||
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb,dynamodb" go test -p 1 -v ./test
|
||||
docker rm -vf authorizer_scylla_db
|
||||
docker rm -vf authorizer_mongodb_db
|
||||
docker rm -vf authorizer_arangodb
|
||||
docker rm -vf dynamodb-local-test
|
||||
# docker rm -vf couchbase-local-test
|
||||
generate:
|
||||
cd server && go run github.com/99designs/gqlgen generate && go mod tidy
|
||||
|
6
app/.prettierrc.json
Normal file
6
app/.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"useTabs": true
|
||||
}
|
125
app/package-lock.json
generated
125
app/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "^1.1.2",
|
||||
"@authorizerdev/authorizer-react": "^1.1.8",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
@@ -22,13 +22,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@types/styled-components": "^5.1.11"
|
||||
"@types/styled-components": "^5.1.11",
|
||||
"prettier": "2.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@authorizerdev/authorizer-js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
||||
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.4.tgz",
|
||||
"integrity": "sha512-oLp3XMYU9xHlg5urpAOaw6NrKQ07SaI8oAi1BAMHZWivv5qi2V+6roey0y9MdvCUmsMULBBcZg87kuoMTXRJOw==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "^3.1.5"
|
||||
},
|
||||
@@ -37,14 +38,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@authorizerdev/authorizer-react": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
|
||||
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.8.tgz",
|
||||
"integrity": "sha512-5v9Zc7ZxiEQEHT1Fwj1jvEemdckcrn7mZPkdmJ/z0YOWNbF20ZpM4WsfrtJB+d293m7Rb1QKwzdFPkYINMh5tw==",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-js": "^1.1.0",
|
||||
"final-form": "^4.20.2",
|
||||
"react-final-form": "^6.5.3",
|
||||
"styled-components": "^5.3.0"
|
||||
"@authorizerdev/authorizer-js": "^1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -468,18 +466,6 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/final-form": {
|
||||
"version": "4.20.4",
|
||||
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
|
||||
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/final-form"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@@ -617,6 +603,21 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
@@ -657,33 +658,6 @@
|
||||
"react": "17.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-final-form": {
|
||||
"version": "6.5.7",
|
||||
"resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.7.tgz",
|
||||
"integrity": "sha512-o7tvJXB+McGiXOILqIC8lnOcX4aLhIBiF/Xi9Qet35b7XOS8R7KL8HLRKTfnZWQJm6MCE15v1U0SFive0NcxyA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.15.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/final-form"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"final-form": "4.20.4",
|
||||
"react": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-final-form/node_modules/@babel/runtime": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
|
||||
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
@@ -860,22 +834,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
||||
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.4.tgz",
|
||||
"integrity": "sha512-oLp3XMYU9xHlg5urpAOaw6NrKQ07SaI8oAi1BAMHZWivv5qi2V+6roey0y9MdvCUmsMULBBcZg87kuoMTXRJOw==",
|
||||
"requires": {
|
||||
"cross-fetch": "^3.1.5"
|
||||
}
|
||||
},
|
||||
"@authorizerdev/authorizer-react": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
|
||||
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.8.tgz",
|
||||
"integrity": "sha512-5v9Zc7ZxiEQEHT1Fwj1jvEemdckcrn7mZPkdmJ/z0YOWNbF20ZpM4WsfrtJB+d293m7Rb1QKwzdFPkYINMh5tw==",
|
||||
"requires": {
|
||||
"@authorizerdev/authorizer-js": "^1.1.0",
|
||||
"final-form": "^4.20.2",
|
||||
"react-final-form": "^6.5.3",
|
||||
"styled-components": "^5.3.0"
|
||||
"@authorizerdev/authorizer-js": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
@@ -1215,14 +1186,6 @@
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"final-form": {
|
||||
"version": "4.20.4",
|
||||
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
|
||||
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.0"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@@ -1329,6 +1292,12 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
@@ -1365,24 +1334,6 @@
|
||||
"scheduler": "^0.20.2"
|
||||
}
|
||||
},
|
||||
"react-final-form": {
|
||||
"version": "6.5.7",
|
||||
"resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.7.tgz",
|
||||
"integrity": "sha512-o7tvJXB+McGiXOILqIC8lnOcX4aLhIBiF/Xi9Qet35b7XOS8R7KL8HLRKTfnZWQJm6MCE15v1U0SFive0NcxyA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.15.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
|
||||
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
|
@@ -5,13 +5,14 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||
"start": "NODE_ENV=development node ./esbuild.config.js"
|
||||
"start": "NODE_ENV=development node ./esbuild.config.js",
|
||||
"format": "prettier --write 'src/**/*.(ts|tsx|js|jsx)'"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Lakhan Samani",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "^1.1.2",
|
||||
"@authorizerdev/authorizer-react": "^1.1.8",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
@@ -19,11 +20,12 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"typescript": "^4.3.5",
|
||||
"styled-components": "^5.3.0"
|
||||
"styled-components": "^5.3.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@types/styled-components": "^5.1.11"
|
||||
"@types/styled-components": "^5.1.11",
|
||||
"prettier": "2.7.1"
|
||||
}
|
||||
}
|
||||
|
@@ -5,9 +5,9 @@ import Root from './Root';
|
||||
import { createRandomString } from './utils/common';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__authorizer__: any;
|
||||
}
|
||||
interface Window {
|
||||
__authorizer__: any;
|
||||
}
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
|
@@ -32,12 +32,14 @@ export default function Root({
|
||||
const { token, loading, config } = useAuthorizer();
|
||||
|
||||
const searchParams = new URLSearchParams(
|
||||
hasWindow() ? window.location.search : ``
|
||||
hasWindow() ? window.location.search : ``,
|
||||
);
|
||||
const state = searchParams.get('state') || createRandomString();
|
||||
const scope = searchParams.get('scope')
|
||||
? searchParams.get('scope')?.toString().split(' ')
|
||||
: ['openid', 'profile', 'email'];
|
||||
const code = searchParams.get('code') || '';
|
||||
const nonce = searchParams.get('nonce') || '';
|
||||
|
||||
const urlProps: Record<string, any> = {
|
||||
state,
|
||||
@@ -58,9 +60,19 @@ export default function Root({
|
||||
if (token) {
|
||||
let redirectURL = config.redirectURL || '/app';
|
||||
let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
|
||||
|
||||
if (code !== '') {
|
||||
params += `&code=${code}`;
|
||||
}
|
||||
|
||||
if (nonce !== '') {
|
||||
params += `&nonce=${nonce}`;
|
||||
}
|
||||
|
||||
if (token.refresh_token) {
|
||||
params += `&refresh_token=${token.refresh_token}`;
|
||||
}
|
||||
|
||||
const url = new URL(redirectURL);
|
||||
if (redirectURL.includes('?')) {
|
||||
redirectURL = `${redirectURL}&${params}`;
|
||||
@@ -74,7 +86,7 @@ export default function Root({
|
||||
}
|
||||
}
|
||||
return () => {};
|
||||
}, [token]);
|
||||
}, [token, config]);
|
||||
|
||||
if (loading) {
|
||||
return <h1>Loading...</h1>;
|
||||
@@ -100,7 +112,7 @@ export default function Root({
|
||||
<Route path="/app" exact>
|
||||
<Login urlProps={urlProps} />
|
||||
</Route>
|
||||
<Route path="/app/signup" exact>
|
||||
<Route path="/app/signup">
|
||||
<SignUp urlProps={urlProps} />
|
||||
</Route>
|
||||
<Route path="/app/reset-password">
|
||||
|
@@ -60,7 +60,12 @@ export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
|
||||
{view === VIEW_TYPES.FORGOT_PASSWORD && (
|
||||
<Fragment>
|
||||
<h1 style={{ textAlign: 'center' }}>Forgot Password</h1>
|
||||
<AuthorizerForgotPassword urlProps={urlProps} />
|
||||
<AuthorizerForgotPassword
|
||||
urlProps={{
|
||||
...urlProps,
|
||||
redirect_uri: `${window.location.origin}/app/reset-password`,
|
||||
}}
|
||||
/>
|
||||
<Footer>
|
||||
<Link
|
||||
to="#"
|
||||
|
@@ -1,28 +1,28 @@
|
||||
// colors: https://tailwindcss.com/docs/customizing-colors
|
||||
|
||||
export const theme = {
|
||||
colors: {
|
||||
primary: '#3B82F6',
|
||||
primaryDisabled: '#60A5FA',
|
||||
gray: '#D1D5DB',
|
||||
danger: '#DC2626',
|
||||
success: '#10B981',
|
||||
textColor: '#374151',
|
||||
},
|
||||
fonts: {
|
||||
// typography
|
||||
fontStack: '-apple-system, system-ui, sans-serif',
|
||||
colors: {
|
||||
primary: '#3B82F6',
|
||||
primaryDisabled: '#60A5FA',
|
||||
gray: '#D1D5DB',
|
||||
danger: '#DC2626',
|
||||
success: '#10B981',
|
||||
textColor: '#374151',
|
||||
},
|
||||
fonts: {
|
||||
// typography
|
||||
fontStack: '-apple-system, system-ui, sans-serif',
|
||||
|
||||
// font sizes
|
||||
largeText: '18px',
|
||||
mediumText: '14px',
|
||||
smallText: '12px',
|
||||
tinyText: '10px',
|
||||
},
|
||||
// font sizes
|
||||
largeText: '18px',
|
||||
mediumText: '14px',
|
||||
smallText: '12px',
|
||||
tinyText: '10px',
|
||||
},
|
||||
|
||||
radius: {
|
||||
card: '5px',
|
||||
button: '5px',
|
||||
input: '5px',
|
||||
},
|
||||
radius: {
|
||||
card: '5px',
|
||||
button: '5px',
|
||||
input: '5px',
|
||||
},
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ export const createRandomString = () => {
|
||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
|
||||
let random = '';
|
||||
const randomValues = Array.from(
|
||||
getCrypto().getRandomValues(new Uint8Array(43))
|
||||
getCrypto().getRandomValues(new Uint8Array(43)),
|
||||
);
|
||||
randomValues.forEach((v) => (random += charset[v % charset.length]));
|
||||
return random;
|
||||
|
588
app/yarn.lock
Normal file
588
app/yarn.lock
Normal file
@@ -0,0 +1,588 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@authorizerdev/authorizer-js@^1.1.4":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.4.tgz"
|
||||
integrity sha512-oLp3XMYU9xHlg5urpAOaw6NrKQ07SaI8oAi1BAMHZWivv5qi2V+6roey0y9MdvCUmsMULBBcZg87kuoMTXRJOw==
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
|
||||
"@authorizerdev/authorizer-react@^1.1.8":
|
||||
version "1.1.8"
|
||||
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.8.tgz"
|
||||
integrity sha512-5v9Zc7ZxiEQEHT1Fwj1jvEemdckcrn7mZPkdmJ/z0YOWNbF20ZpM4WsfrtJB+d293m7Rb1QKwzdFPkYINMh5tw==
|
||||
dependencies:
|
||||
"@authorizerdev/authorizer-js" "^1.1.4"
|
||||
|
||||
"@babel/code-frame@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz"
|
||||
integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.16.7"
|
||||
|
||||
"@babel/generator@^7.16.8":
|
||||
version "7.16.8"
|
||||
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz"
|
||||
integrity sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.8"
|
||||
jsesc "^2.5.1"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/helper-annotate-as-pure@^7.16.0":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz"
|
||||
integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/helper-environment-visitor@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz"
|
||||
integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/helper-function-name@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz"
|
||||
integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==
|
||||
dependencies:
|
||||
"@babel/helper-get-function-arity" "^7.16.7"
|
||||
"@babel/template" "^7.16.7"
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/helper-get-function-arity@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz"
|
||||
integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/helper-hoist-variables@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz"
|
||||
integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz"
|
||||
integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/helper-split-export-declaration@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz"
|
||||
integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==
|
||||
dependencies:
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/helper-validator-identifier@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz"
|
||||
integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
|
||||
|
||||
"@babel/highlight@^7.16.7":
|
||||
version "7.16.10"
|
||||
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz"
|
||||
integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.16.7"
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.16.10", "@babel/parser@^7.16.7":
|
||||
version "7.16.12"
|
||||
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz"
|
||||
integrity sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1":
|
||||
version "7.14.8"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz"
|
||||
integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz"
|
||||
integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.16.7"
|
||||
"@babel/parser" "^7.16.7"
|
||||
"@babel/types" "^7.16.7"
|
||||
|
||||
"@babel/traverse@^7.4.5":
|
||||
version "7.16.10"
|
||||
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz"
|
||||
integrity sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.16.7"
|
||||
"@babel/generator" "^7.16.8"
|
||||
"@babel/helper-environment-visitor" "^7.16.7"
|
||||
"@babel/helper-function-name" "^7.16.7"
|
||||
"@babel/helper-hoist-variables" "^7.16.7"
|
||||
"@babel/helper-split-export-declaration" "^7.16.7"
|
||||
"@babel/parser" "^7.16.10"
|
||||
"@babel/types" "^7.16.8"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/types@^7.16.7", "@babel/types@^7.16.8":
|
||||
version "7.16.8"
|
||||
resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz"
|
||||
integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.16.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@emotion/is-prop-valid@^0.8.8":
|
||||
version "0.8.8"
|
||||
resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz"
|
||||
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||
dependencies:
|
||||
"@emotion/memoize" "0.7.4"
|
||||
|
||||
"@emotion/memoize@0.7.4":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz"
|
||||
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||
|
||||
"@emotion/stylis@^0.8.4":
|
||||
version "0.8.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz"
|
||||
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
|
||||
|
||||
"@emotion/unitless@^0.7.4":
|
||||
version "0.7.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz"
|
||||
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
|
||||
|
||||
"@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/hoist-non-react-statics@*":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@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-dom@^17.0.9":
|
||||
version "17.0.9"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz"
|
||||
integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-router-dom@^5.1.8":
|
||||
version "5.1.8"
|
||||
resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz"
|
||||
integrity sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router@*":
|
||||
version "5.1.16"
|
||||
resolved "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz"
|
||||
integrity sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^17.0.15":
|
||||
version "17.0.15"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.15.tgz"
|
||||
integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@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/styled-components@^5.1.11":
|
||||
version "5.1.25"
|
||||
resolved "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz"
|
||||
integrity sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "*"
|
||||
"@types/react" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
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==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
"babel-plugin-styled-components@>= 1.12.0":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz"
|
||||
integrity sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw==
|
||||
dependencies:
|
||||
"@babel/helper-annotate-as-pure" "^7.16.0"
|
||||
"@babel/helper-module-imports" "^7.16.0"
|
||||
babel-plugin-syntax-jsx "^6.18.0"
|
||||
lodash "^4.17.11"
|
||||
|
||||
babel-plugin-syntax-jsx@^6.18.0:
|
||||
version "6.18.0"
|
||||
resolved "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz"
|
||||
integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
|
||||
|
||||
camelize@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz"
|
||||
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
|
||||
|
||||
chalk@^2.0.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
cross-fetch@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz"
|
||||
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
||||
dependencies:
|
||||
node-fetch "2.6.7"
|
||||
|
||||
css-color-keywords@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz"
|
||||
integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=
|
||||
|
||||
css-to-react-native@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz"
|
||||
integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
|
||||
dependencies:
|
||||
camelize "^1.0.0"
|
||||
css-color-keywords "^1.0.0"
|
||||
postcss-value-parser "^4.0.2"
|
||||
|
||||
csstype@^3.0.2:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz"
|
||||
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
|
||||
|
||||
debug@^4.1.0:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
esbuild@^0.12.17:
|
||||
version "0.12.17"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.12.17.tgz"
|
||||
integrity sha512-GshKJyVYUnlSXIZj/NheC2O0Kblh42CS7P1wJyTbbIHevTG4jYMS9NNw8EOd8dDWD0dzydYHS01MpZoUcQXB4g==
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
globals@^11.1.0:
|
||||
version "11.12.0"
|
||||
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
|
||||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
history@^4.9.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz"
|
||||
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
loose-envify "^1.2.0"
|
||||
resolve-pathname "^3.0.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
value-equal "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
|
||||
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==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
jsesc@^2.5.1:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
|
||||
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
|
||||
|
||||
lodash@^4.17.11:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
mini-create-react-context@^0.4.0:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz"
|
||||
integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.1"
|
||||
tiny-warning "^1.0.3"
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-fetch@2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
postcss-value-parser@^4.0.2:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
prettier@2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz"
|
||||
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
|
||||
|
||||
prop-types@^15.0.0, prop-types@^15.6.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
react-dom@^17.0.2, "react-dom@>= 16.8.0":
|
||||
version "17.0.2"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
|
||||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-is@^16.6.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.2, "react-is@>= 16.8.0":
|
||||
version "17.0.2"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-router-dom@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz"
|
||||
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
history "^4.9.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.2"
|
||||
react-router "5.2.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz"
|
||||
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
history "^4.9.0"
|
||||
hoist-non-react-statics "^3.1.0"
|
||||
loose-envify "^1.3.1"
|
||||
mini-create-react-context "^0.4.0"
|
||||
path-to-regexp "^1.7.0"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.6.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
"react@^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", react@^17.0.2, "react@>= 16.8.0", react@>=15, react@>=16, react@17.0.2:
|
||||
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==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
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-pathname@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz"
|
||||
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
|
||||
|
||||
scheduler@^0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz"
|
||||
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
shallowequal@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz"
|
||||
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
|
||||
|
||||
source-map@^0.5.0:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
|
||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||
|
||||
styled-components@^5.3.0, "styled-components@>= 2":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz"
|
||||
integrity sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.0.0"
|
||||
"@babel/traverse" "^7.4.5"
|
||||
"@emotion/is-prop-valid" "^0.8.8"
|
||||
"@emotion/stylis" "^0.8.4"
|
||||
"@emotion/unitless" "^0.7.4"
|
||||
babel-plugin-styled-components ">= 1.12.0"
|
||||
css-to-react-native "^3.0.0"
|
||||
hoist-non-react-statics "^3.0.0"
|
||||
shallowequal "^1.1.0"
|
||||
supports-color "^5.5.0"
|
||||
|
||||
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
tiny-invariant@^1.0.2:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz"
|
||||
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
|
||||
|
||||
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
|
||||
to-fast-properties@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz"
|
||||
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
typescript@^4.3.5:
|
||||
version "4.3.5"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz"
|
||||
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
|
||||
|
||||
value-equal@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz"
|
||||
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
|
||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
|
||||
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
||||
dependencies:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
6
dashboard/.prettierrc.json
Normal file
6
dashboard/.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"useTabs": true
|
||||
}
|
148
dashboard/package-lock.json
generated
148
dashboard/package-lock.json
generated
@@ -33,26 +33,27 @@
|
||||
"urql": "^2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-email-editor": "^1.1.7"
|
||||
"@types/react-email-editor": "^1.1.7",
|
||||
"prettier": "2.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@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==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.16.0"
|
||||
"@babel/highlight": "^7.18.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@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==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
||||
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.16.0"
|
||||
"@babel/types": "^7.18.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -66,20 +67,28 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz",
|
||||
"integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@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==",
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
|
||||
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.15.7",
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
@@ -113,11 +122,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
|
||||
"integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
|
||||
"version": "7.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz",
|
||||
"integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.15.7",
|
||||
"@babel/helper-string-parser": "^7.18.10",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1315,7 +1325,7 @@
|
||||
"node_modules/chalk/node_modules/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=",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
@@ -1336,7 +1346,7 @@
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.14",
|
||||
@@ -1804,7 +1814,7 @@
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -1990,6 +2000,21 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -2425,19 +2450,19 @@
|
||||
},
|
||||
"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==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.16.0"
|
||||
"@babel/highlight": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@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==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
||||
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.16.0"
|
||||
"@babel/types": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
@@ -2445,17 +2470,22 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz",
|
||||
"integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ=="
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz",
|
||||
"integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw=="
|
||||
},
|
||||
"@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=="
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
|
||||
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.15.7",
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
@@ -2477,11 +2507,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
|
||||
"integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
|
||||
"version": "7.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz",
|
||||
"integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.15.7",
|
||||
"@babel/helper-string-parser": "^7.18.10",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -2605,8 +2636,7 @@
|
||||
"@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==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg=="
|
||||
},
|
||||
"@chakra-ui/descendant": {
|
||||
"version": "2.1.1",
|
||||
@@ -3210,8 +3240,7 @@
|
||||
"@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==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg=="
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.0",
|
||||
@@ -3397,7 +3426,7 @@
|
||||
"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="
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3417,7 +3446,7 @@
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
||||
},
|
||||
"compute-scroll-into-view": {
|
||||
"version": "1.0.14",
|
||||
@@ -3483,8 +3512,7 @@
|
||||
"draftjs-utils": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
|
||||
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg=="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
@@ -3732,7 +3760,7 @@
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
|
||||
},
|
||||
"hey-listen": {
|
||||
"version": "1.0.8",
|
||||
@@ -3758,8 +3786,7 @@
|
||||
"html-to-draftjs": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
|
||||
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ=="
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.3.0",
|
||||
@@ -3887,6 +3914,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -3949,8 +3982,7 @@
|
||||
"react-email-editor": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-email-editor/-/react-email-editor-1.6.1.tgz",
|
||||
"integrity": "sha512-pEWpRmTY0ok03cwTGqEOoEldnzThhuRGTrcMnv8W3/jc5MTfcr9USU/IQ9HrVvFStLKoxYBIQnSKY+iCYWOtSQ==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-pEWpRmTY0ok03cwTGqEOoEldnzThhuRGTrcMnv8W3/jc5MTfcr9USU/IQ9HrVvFStLKoxYBIQnSKY+iCYWOtSQ=="
|
||||
},
|
||||
"react-fast-compare": {
|
||||
"version": "3.2.0",
|
||||
@@ -3973,8 +4005,7 @@
|
||||
"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==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ=="
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
@@ -4165,8 +4196,7 @@
|
||||
"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==",
|
||||
"requires": {}
|
||||
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
|
||||
},
|
||||
"use-sidecar": {
|
||||
"version": "1.0.5",
|
||||
|
@@ -5,7 +5,8 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||
"start": "NODE_ENV=development node ./esbuild.config.js"
|
||||
"start": "NODE_ENV=development node ./esbuild.config.js",
|
||||
"format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Lakhan Samani",
|
||||
@@ -35,6 +36,7 @@
|
||||
"urql": "^2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-email-editor": "^1.1.7"
|
||||
"@types/react-email-editor": "^1.1.7",
|
||||
"prettier": "2.7.1"
|
||||
}
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ const DeleteEmailTemplateModal = ({
|
||||
title: capitalizeFirstLetter(res.error.message),
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -53,7 +53,7 @@ const DeleteEmailTemplateModal = ({
|
||||
title: capitalizeFirstLetter(res.data?._delete_email_template.message),
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
|
@@ -51,7 +51,7 @@ const DeleteUserModal = ({
|
||||
title: capitalizeFirstLetter(res.error.message),
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -60,7 +60,7 @@ const DeleteUserModal = ({
|
||||
title: capitalizeFirstLetter(res.data?._delete_user.message),
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
|
@@ -44,7 +44,7 @@ const DeleteWebhookModal = ({
|
||||
title: capitalizeFirstLetter(res.error.message),
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -53,7 +53,7 @@ const DeleteWebhookModal = ({
|
||||
title: capitalizeFirstLetter(res.data?._delete_webhook.message),
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
@@ -20,13 +20,14 @@ import { useClient } from 'urql';
|
||||
import { FaSave } from 'react-icons/fa';
|
||||
import InputField from './InputField';
|
||||
import {
|
||||
ArrayInputType,
|
||||
DateInputType,
|
||||
MultiSelectInputType,
|
||||
SelectInputType,
|
||||
TextInputType,
|
||||
} from '../constants';
|
||||
import { getObjectDiff } from '../utils';
|
||||
import { UpdateUser } from '../graphql/mutation';
|
||||
import { GetAvailableRolesQuery } from '../graphql/queries';
|
||||
|
||||
const GenderTypes = {
|
||||
Undisclosed: null,
|
||||
@@ -57,8 +58,9 @@ const EditUserModal = ({
|
||||
}) => {
|
||||
const client = useClient();
|
||||
const toast = useToast();
|
||||
const [availableRoles, setAvailableRoles] = useState<string[]>([]);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [userData, setUserData] = React.useState<userDataTypes>({
|
||||
const [userData, setUserData] = useState<userDataTypes>({
|
||||
id: '',
|
||||
email: '',
|
||||
given_name: '',
|
||||
@@ -73,7 +75,17 @@ const EditUserModal = ({
|
||||
});
|
||||
React.useEffect(() => {
|
||||
setUserData(user);
|
||||
fetchAvailableRoles();
|
||||
}, []);
|
||||
const fetchAvailableRoles = async () => {
|
||||
const res = await client.query(GetAvailableRolesQuery).toPromise();
|
||||
if (res.data?._env?.ROLES && res.data?._env?.PROTECTED_ROLES) {
|
||||
setAvailableRoles([
|
||||
...res.data._env.ROLES,
|
||||
...res.data._env.PROTECTED_ROLES,
|
||||
]);
|
||||
}
|
||||
};
|
||||
const saveHandler = async () => {
|
||||
const diff = getObjectDiff(user, userData);
|
||||
const updatedUserData = diff.reduce(
|
||||
@@ -82,7 +94,7 @@ const EditUserModal = ({
|
||||
// @ts-ignore
|
||||
[property]: userData[property],
|
||||
}),
|
||||
{}
|
||||
{},
|
||||
);
|
||||
const res = await client
|
||||
.mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } })
|
||||
@@ -92,14 +104,14 @@ const EditUserModal = ({
|
||||
title: 'User data update failed',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
} else if (res.data?._update_user?.id) {
|
||||
toast({
|
||||
title: 'User data update successful',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
@@ -221,7 +233,8 @@ const EditUserModal = ({
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={ArrayInputType.USER_ROLES}
|
||||
availableRoles={availableRoles}
|
||||
inputType={MultiSelectInputType.USER_ROLES}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
|
@@ -1,65 +1,65 @@
|
||||
import React from "react";
|
||||
import { Flex, Stack, Text, useMediaQuery } from "@chakra-ui/react";
|
||||
import InputField from "../../components/InputField";
|
||||
import { TextInputType, TextAreaInputType } from "../../constants";
|
||||
import React from 'react';
|
||||
import { Flex, Stack, Text, useMediaQuery } from '@chakra-ui/react';
|
||||
import InputField from '../../components/InputField';
|
||||
import { TextInputType, TextAreaInputType } from '../../constants';
|
||||
|
||||
const AccessToken = ({ variables, setVariables }: any) => {
|
||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
||||
return (
|
||||
<div>
|
||||
{" "}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Access Token
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "50%"}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Access Token Expiry Time:</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.ACCESS_TOKEN_EXPIRY_TIME}
|
||||
placeholder="0h15m0s"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "60%"}
|
||||
justifyContent="start"
|
||||
direction="column"
|
||||
>
|
||||
<Text fontSize="sm">Custom Scripts:</Text>
|
||||
<Text fontSize="xs" color="blackAlpha.500">
|
||||
(Used to add custom fields in ID token)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
|
||||
placeholder="Add script here"
|
||||
minH="25vh"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Access Token
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '50%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Access Token Expiry Time:</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.ACCESS_TOKEN_EXPIRY_TIME}
|
||||
placeholder="0h15m0s"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '60%'}
|
||||
justifyContent="start"
|
||||
direction="column"
|
||||
>
|
||||
<Text fontSize="sm">Custom Scripts:</Text>
|
||||
<Text fontSize="xs" color="blackAlpha.500">
|
||||
(Used to add custom fields in ID token)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
|
||||
placeholder="Add script here"
|
||||
minH="25vh"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessToken;
|
||||
export default AccessToken;
|
||||
|
@@ -1,35 +1,35 @@
|
||||
import React from "react";
|
||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
||||
import InputField from "../../components/InputField";
|
||||
import { ArrayInputType} from "../../constants";
|
||||
import React from 'react';
|
||||
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
|
||||
import InputField from '../../components/InputField';
|
||||
import { ArrayInputType } from '../../constants';
|
||||
|
||||
const DomainWhiteListing = ({ variables, setVariables }: any) => {
|
||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
||||
return (
|
||||
<div>
|
||||
{" "}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Domain White Listing
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Allowed Origins:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={ArrayInputType.ALLOWED_ORIGINS}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Domain White Listing
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Allowed Origins:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={ArrayInputType.ALLOWED_ORIGINS}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DomainWhiteListing;
|
||||
export default DomainWhiteListing;
|
||||
|
@@ -1,114 +1,134 @@
|
||||
import React from "react";
|
||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
||||
import InputField from "../../components/InputField";
|
||||
import { TextInputType, HiddenInputType} from "../../constants";
|
||||
import React from 'react';
|
||||
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
|
||||
import InputField from '../../components/InputField';
|
||||
import { TextInputType, HiddenInputType } from '../../constants';
|
||||
const EmailConfigurations = ({
|
||||
variables,
|
||||
setVariables,
|
||||
fieldVisibility,
|
||||
setFieldVisibility,
|
||||
variables,
|
||||
setVariables,
|
||||
fieldVisibility,
|
||||
setFieldVisibility,
|
||||
}: any) => {
|
||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
||||
return (
|
||||
<div>
|
||||
{" "}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Email Configurations
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">SMTP Host:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SMTP_HOST}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">SMTP Port:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SMTP_PORT}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">SMTP Username:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SMTP_USERNAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">SMTP Password:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.SMTP_PASSWORD}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">From Email:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SENDER_EMAIL}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Email Configurations
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">SMTP Host:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SMTP_HOST}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">SMTP Port:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SMTP_PORT}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">SMTP Local Name:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SMTP_LOCAL_NAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">SMTP Username:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SMTP_USERNAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">SMTP Password:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.SMTP_PASSWORD}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">From Email:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.SENDER_EMAIL}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailConfigurations;
|
||||
export default EmailConfigurations;
|
||||
|
@@ -126,6 +126,40 @@ const Features = ({ variables, setVariables }: any) => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Stack>
|
||||
<Divider paddingY={5} />
|
||||
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
|
||||
Cookie Security Features
|
||||
</Text>
|
||||
<Stack spacing={6}>
|
||||
<Flex>
|
||||
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||
<Text fontSize="sm">Use Secure App Cookie:</Text>
|
||||
<Text fontSize="x-small">
|
||||
Note: If you set this to insecure, it will set{' '}
|
||||
<code>sameSite</code> property of cookie to <code>lax</code> mode
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex justifyContent="start">
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={SwitchInputType.APP_COOKIE_SECURE}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||
<Text fontSize="sm">Use Secure Admin Cookie:</Text>
|
||||
</Flex>
|
||||
<Flex justifyContent="start">
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={SwitchInputType.ADMIN_COOKIE_SECURE}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -37,13 +37,13 @@ const JSTConfigurations = ({
|
||||
JSON.stringify({
|
||||
type: variables.JWT_TYPE,
|
||||
key: variables.JWT_PUBLIC_KEY || variables.JWT_SECRET,
|
||||
})
|
||||
}),
|
||||
);
|
||||
toast({
|
||||
title: `JWT config copied successfully`,
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
} catch (err) {
|
||||
console.error({
|
||||
@@ -54,7 +54,7 @@ const JSTConfigurations = ({
|
||||
title: `Failed to copy JWT config`,
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@@ -16,6 +16,7 @@ import {
|
||||
FaLinkedin,
|
||||
FaApple,
|
||||
FaTwitter,
|
||||
FaMicrosoft,
|
||||
} from 'react-icons/fa';
|
||||
import { TextInputType, HiddenInputType } from '../../constants';
|
||||
|
||||
@@ -303,6 +304,57 @@ const OAuthConfig = ({
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '55px' : '35px'}
|
||||
h="35px"
|
||||
marginRight="1.5%"
|
||||
border="1px solid #3b5998"
|
||||
borderRadius="5px"
|
||||
>
|
||||
<FaMicrosoft />
|
||||
</Center>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '35%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
marginRight="1.5%"
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={envVariables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID}
|
||||
placeholder="Microsoft Active Directory TenantID"
|
||||
/>
|
||||
</Center>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '35%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
marginRight="1.5%"
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={envVariables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.MICROSOFT_CLIENT_ID}
|
||||
placeholder="Microsoft Client ID"
|
||||
/>
|
||||
</Center>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={envVariables}
|
||||
setVariables={setVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.MICROSOFT_CLIENT_SECRET}
|
||||
placeholder="Microsoft Client Secret"
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Box>
|
||||
</div>
|
||||
|
@@ -1,60 +1,60 @@
|
||||
import React from "react";
|
||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
||||
import InputField from "../InputField";
|
||||
import { TextInputType } from "../../constants";
|
||||
import React from 'react';
|
||||
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
|
||||
import InputField from '../InputField';
|
||||
import { TextInputType } from '../../constants';
|
||||
|
||||
const OrganizationInfo = ({ variables, setVariables }: any) => {
|
||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
||||
return (
|
||||
<div>
|
||||
{" "}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Organization Information
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Organization Name:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.ORGANIZATION_NAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Organization Logo:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.ORGANIZATION_LOGO}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
Organization Information
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Organization Name:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.ORGANIZATION_NAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Organization Logo:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={TextInputType.ORGANIZATION_LOGO}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationInfo;
|
||||
export default OrganizationInfo;
|
||||
|
@@ -1,138 +1,138 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import {
|
||||
Flex,
|
||||
Stack,
|
||||
Center,
|
||||
Text,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
useMediaQuery,
|
||||
} from "@chakra-ui/react";
|
||||
import { FaRegEyeSlash, FaRegEye } from "react-icons/fa";
|
||||
import InputField from "../InputField";
|
||||
import { HiddenInputType } from "../../constants";
|
||||
Flex,
|
||||
Stack,
|
||||
Center,
|
||||
Text,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
useMediaQuery,
|
||||
} from '@chakra-ui/react';
|
||||
import { FaRegEyeSlash, FaRegEye } from 'react-icons/fa';
|
||||
import InputField from '../InputField';
|
||||
import { HiddenInputType } from '../../constants';
|
||||
const SecurityAdminSecret = ({
|
||||
variables,
|
||||
setVariables,
|
||||
fieldVisibility,
|
||||
setFieldVisibility,
|
||||
validateAdminSecretHandler,
|
||||
adminSecret,
|
||||
variables,
|
||||
setVariables,
|
||||
fieldVisibility,
|
||||
setFieldVisibility,
|
||||
validateAdminSecretHandler,
|
||||
adminSecret,
|
||||
}: any) => {
|
||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
||||
return (
|
||||
<div>
|
||||
{" "}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||
Security (Admin Secret)
|
||||
</Text>
|
||||
<Stack
|
||||
spacing={6}
|
||||
padding="0 5%"
|
||||
marginTop="3%"
|
||||
border="1px solid #ff7875"
|
||||
borderRadius="5px"
|
||||
>
|
||||
<Flex
|
||||
marginTop={isNotSmallerScreen ? "3%" : "5%"}
|
||||
direction={isNotSmallerScreen ? "row" : "column"}
|
||||
>
|
||||
<Flex
|
||||
mt={3}
|
||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Old Admin Secret:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputGroup size="sm">
|
||||
<Input
|
||||
borderRadius={5}
|
||||
size="sm"
|
||||
placeholder="Enter Old Admin Secret"
|
||||
value={adminSecret.value as string}
|
||||
onChange={(event: any) => validateAdminSecretHandler(event)}
|
||||
type={
|
||||
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
|
||||
? "password"
|
||||
: "text"
|
||||
}
|
||||
/>
|
||||
<InputRightElement
|
||||
right="5px"
|
||||
children={
|
||||
<Flex>
|
||||
{fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET] ? (
|
||||
<Center
|
||||
w="25px"
|
||||
margin="0 1.5%"
|
||||
cursor="pointer"
|
||||
onClick={() =>
|
||||
setFieldVisibility({
|
||||
...fieldVisibility,
|
||||
[HiddenInputType.OLD_ADMIN_SECRET]: false,
|
||||
})
|
||||
}
|
||||
>
|
||||
<FaRegEyeSlash color="#bfbfbf" />
|
||||
</Center>
|
||||
) : (
|
||||
<Center
|
||||
w="25px"
|
||||
margin="0 1.5%"
|
||||
cursor="pointer"
|
||||
onClick={() =>
|
||||
setFieldVisibility({
|
||||
...fieldVisibility,
|
||||
[HiddenInputType.OLD_ADMIN_SECRET]: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<FaRegEye color="#bfbfbf" />
|
||||
</Center>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex
|
||||
paddingBottom="3%"
|
||||
direction={isNotSmallerScreen ? "row" : "column"}
|
||||
>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "50%"}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">New Admin Secret:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "3"}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
mb={3}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={HiddenInputType.ADMIN_SECRET}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
isDisabled={adminSecret.disableInputField}
|
||||
placeholder="Enter New Admin Secret"
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||
Security (Admin Secret)
|
||||
</Text>
|
||||
<Stack
|
||||
spacing={6}
|
||||
padding="0 5%"
|
||||
marginTop="3%"
|
||||
border="1px solid #ff7875"
|
||||
borderRadius="5px"
|
||||
>
|
||||
<Flex
|
||||
marginTop={isNotSmallerScreen ? '3%' : '5%'}
|
||||
direction={isNotSmallerScreen ? 'row' : 'column'}
|
||||
>
|
||||
<Flex
|
||||
mt={3}
|
||||
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">Old Admin Secret:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputGroup size="sm">
|
||||
<Input
|
||||
borderRadius={5}
|
||||
size="sm"
|
||||
placeholder="Enter Old Admin Secret"
|
||||
value={adminSecret.value as string}
|
||||
onChange={(event: any) => validateAdminSecretHandler(event)}
|
||||
type={
|
||||
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
|
||||
? 'password'
|
||||
: 'text'
|
||||
}
|
||||
/>
|
||||
<InputRightElement
|
||||
right="5px"
|
||||
children={
|
||||
<Flex>
|
||||
{fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET] ? (
|
||||
<Center
|
||||
w="25px"
|
||||
margin="0 1.5%"
|
||||
cursor="pointer"
|
||||
onClick={() =>
|
||||
setFieldVisibility({
|
||||
...fieldVisibility,
|
||||
[HiddenInputType.OLD_ADMIN_SECRET]: false,
|
||||
})
|
||||
}
|
||||
>
|
||||
<FaRegEyeSlash color="#bfbfbf" />
|
||||
</Center>
|
||||
) : (
|
||||
<Center
|
||||
w="25px"
|
||||
margin="0 1.5%"
|
||||
cursor="pointer"
|
||||
onClick={() =>
|
||||
setFieldVisibility({
|
||||
...fieldVisibility,
|
||||
[HiddenInputType.OLD_ADMIN_SECRET]: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<FaRegEye color="#bfbfbf" />
|
||||
</Center>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex
|
||||
paddingBottom="3%"
|
||||
direction={isNotSmallerScreen ? 'row' : 'column'}
|
||||
>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? '30%' : '50%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text fontSize="sm">New Admin Secret:</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '3'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
mb={3}
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={HiddenInputType.ADMIN_SECRET}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
isDisabled={adminSecret.disableInputField}
|
||||
placeholder="Enter New Admin Secret"
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecurityAdminSecret;
|
||||
export default SecurityAdminSecret;
|
||||
|
@@ -73,7 +73,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
|
||||
title: 'Error occurred generating jwt keys',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
closeHandler();
|
||||
} else {
|
||||
@@ -107,7 +107,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
|
||||
title: 'Error occurred setting jwt keys',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -116,7 +116,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
|
||||
title: 'JWT keys updated successfully',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
closeHandler();
|
||||
};
|
||||
@@ -167,7 +167,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
|
||||
) : (
|
||||
<>
|
||||
{Object.values(HMACEncryptionType).includes(
|
||||
stateVariables.JWT_TYPE
|
||||
stateVariables.JWT_TYPE,
|
||||
) ? (
|
||||
<Flex marginTop="8">
|
||||
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
@@ -13,6 +13,12 @@ import {
|
||||
Textarea,
|
||||
Switch,
|
||||
Text,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItemOption,
|
||||
MenuOptionGroup,
|
||||
Button,
|
||||
Menu,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
FaRegClone,
|
||||
@@ -20,6 +26,7 @@ import {
|
||||
FaRegEyeSlash,
|
||||
FaPlus,
|
||||
FaTimes,
|
||||
FaAngleDown,
|
||||
} from 'react-icons/fa';
|
||||
import {
|
||||
ArrayInputOperations,
|
||||
@@ -30,6 +37,7 @@ import {
|
||||
TextAreaInputType,
|
||||
SwitchInputType,
|
||||
DateInputType,
|
||||
MultiSelectInputType,
|
||||
} from '../constants';
|
||||
import { copyTextToClipboard } from '../utils';
|
||||
|
||||
@@ -39,13 +47,16 @@ const InputField = ({
|
||||
setVariables,
|
||||
fieldVisibility,
|
||||
setFieldVisibility,
|
||||
availableRoles,
|
||||
...downshiftProps
|
||||
}: any) => {
|
||||
const props = {
|
||||
size: 'sm',
|
||||
...downshiftProps,
|
||||
};
|
||||
const [inputFieldVisibility, setInputFieldVisibility] = React.useState<
|
||||
const [availableUserRoles, setAvailableUserRoles] =
|
||||
useState<string[]>(availableRoles);
|
||||
const [inputFieldVisibility, setInputFieldVisibility] = useState<
|
||||
Record<string, boolean>
|
||||
>({
|
||||
ROLES: false,
|
||||
@@ -54,7 +65,7 @@ const InputField = ({
|
||||
ALLOWED_ORIGINS: false,
|
||||
roles: false,
|
||||
});
|
||||
const [inputData, setInputData] = React.useState<Record<string, string>>({
|
||||
const [inputData, setInputData] = useState<Record<string, string>>({
|
||||
ROLES: '',
|
||||
DEFAULT_ROLES: '',
|
||||
PROTECTED_ROLES: '',
|
||||
@@ -64,7 +75,7 @@ const InputField = ({
|
||||
const updateInputHandler = (
|
||||
type: string,
|
||||
operation: any,
|
||||
role: string = ''
|
||||
role: string = '',
|
||||
) => {
|
||||
if (operation === ArrayInputOperations.APPEND) {
|
||||
if (inputData[type] !== '') {
|
||||
@@ -78,7 +89,7 @@ const InputField = ({
|
||||
}
|
||||
if (operation === ArrayInputOperations.REMOVE) {
|
||||
let updatedEnvVars = variables[type].filter(
|
||||
(item: string) => item !== role
|
||||
(item: string) => item !== role,
|
||||
);
|
||||
setVariables({
|
||||
...variables,
|
||||
@@ -95,7 +106,7 @@ const InputField = ({
|
||||
onChange={(
|
||||
event: Event & {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
},
|
||||
) =>
|
||||
setVariables({
|
||||
...variables,
|
||||
@@ -116,11 +127,11 @@ const InputField = ({
|
||||
<InputGroup size="sm">
|
||||
<Input
|
||||
{...props}
|
||||
value={variables[inputType] ?? ''}
|
||||
value={variables[inputType] || ''}
|
||||
onChange={(
|
||||
event: Event & {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
},
|
||||
) =>
|
||||
setVariables({
|
||||
...variables,
|
||||
@@ -207,7 +218,7 @@ const InputField = ({
|
||||
updateInputHandler(
|
||||
inputType,
|
||||
ArrayInputOperations.REMOVE,
|
||||
role
|
||||
role,
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -221,7 +232,7 @@ const InputField = ({
|
||||
size="xs"
|
||||
minW="150px"
|
||||
placeholder="add a new value"
|
||||
value={inputData[inputType] ?? ''}
|
||||
value={inputData[inputType] || ''}
|
||||
onChange={(e: any) => {
|
||||
setInputData({ ...inputData, [inputType]: e.target.value });
|
||||
}}
|
||||
@@ -278,6 +289,87 @@ const InputField = ({
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
if (Object.values(MultiSelectInputType).includes(inputType)) {
|
||||
return (
|
||||
<Flex w="100%" style={{ position: 'relative' }}>
|
||||
<Flex
|
||||
border="1px solid #e2e8f0"
|
||||
w="100%"
|
||||
borderRadius="var(--chakra-radii-sm)"
|
||||
p="1% 0 0 2.5%"
|
||||
overflowX={variables[inputType].length > 3 ? 'scroll' : 'hidden'}
|
||||
overflowY="hidden"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex justifyContent="start" alignItems="center" w="100%" wrap="wrap">
|
||||
{variables[inputType].map((role: string, index: number) => (
|
||||
<Box key={index} margin="0.5%" role="group">
|
||||
<Tag
|
||||
size="sm"
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
minW="fit-content"
|
||||
>
|
||||
<TagLabel cursor="default">{role}</TagLabel>
|
||||
<TagRightIcon
|
||||
boxSize="12px"
|
||||
as={FaTimes}
|
||||
display="none"
|
||||
cursor="pointer"
|
||||
_groupHover={{ display: 'block' }}
|
||||
onClick={() =>
|
||||
updateInputHandler(
|
||||
inputType,
|
||||
ArrayInputOperations.REMOVE,
|
||||
role,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Tag>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
<Menu matchWidth={true}>
|
||||
<MenuButton px="10px" py="7.5px">
|
||||
<FaAngleDown />
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
position="absolute"
|
||||
top="0"
|
||||
right="0"
|
||||
zIndex="10"
|
||||
maxH="150"
|
||||
overflowX="scroll"
|
||||
>
|
||||
<MenuOptionGroup
|
||||
title={undefined}
|
||||
value={variables[inputType]}
|
||||
type="checkbox"
|
||||
onChange={(values: string[] | string) => {
|
||||
setVariables({
|
||||
...variables,
|
||||
[inputType]: values,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{availableUserRoles.map((role) => {
|
||||
return (
|
||||
<MenuItemOption
|
||||
key={`multiselect-menu-${role}`}
|
||||
value={role}
|
||||
>
|
||||
{role}
|
||||
</MenuItemOption>
|
||||
);
|
||||
})}
|
||||
</MenuOptionGroup>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
if (Object.values(TextAreaInputType).includes(inputType)) {
|
||||
return (
|
||||
<Textarea
|
||||
@@ -288,7 +380,7 @@ const InputField = ({
|
||||
onChange={(
|
||||
event: Event & {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
},
|
||||
) =>
|
||||
setVariables({
|
||||
...variables,
|
||||
|
@@ -105,7 +105,7 @@ const InviteMembersModal = ({
|
||||
title: 'Invites sent successfully!',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
setLoading(false);
|
||||
updateUserList();
|
||||
@@ -117,7 +117,7 @@ const InviteMembersModal = ({
|
||||
title: error?.message || 'Error occurred, try again!',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -304,7 +304,7 @@ const InviteMembersModal = ({
|
||||
onClick={() =>
|
||||
updateEmailListHandler(
|
||||
ArrayInputOperations.REMOVE,
|
||||
index
|
||||
index,
|
||||
)
|
||||
}
|
||||
>
|
||||
|
@@ -218,7 +218,7 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
||||
</NavItem>{' '}
|
||||
</Text>
|
||||
</NavLink>
|
||||
)
|
||||
),
|
||||
)}
|
||||
<Link
|
||||
href="/playground"
|
||||
|
@@ -29,6 +29,10 @@ import {
|
||||
Tbody,
|
||||
Td,
|
||||
Code,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Stack,
|
||||
Textarea,
|
||||
} from '@chakra-ui/react';
|
||||
import { FaPlus, FaAngleDown, FaAngleUp } from 'react-icons/fa';
|
||||
import { useClient } from 'urql';
|
||||
@@ -38,6 +42,7 @@ import {
|
||||
EmailTemplateInputDataFields,
|
||||
emailTemplateEventNames,
|
||||
emailTemplateVariables,
|
||||
EmailTemplateEditors,
|
||||
} from '../constants';
|
||||
import { capitalizeFirstLetter } from '../utils';
|
||||
import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation';
|
||||
@@ -66,6 +71,8 @@ interface templateVariableDataTypes {
|
||||
interface emailTemplateDataType {
|
||||
[EmailTemplateInputDataFields.EVENT_NAME]: string;
|
||||
[EmailTemplateInputDataFields.SUBJECT]: string;
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: string;
|
||||
[EmailTemplateInputDataFields.DESIGN]: string;
|
||||
}
|
||||
|
||||
interface validatorDataType {
|
||||
@@ -75,6 +82,8 @@ interface validatorDataType {
|
||||
const initTemplateData: emailTemplateDataType = {
|
||||
[EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup,
|
||||
[EmailTemplateInputDataFields.SUBJECT]: '',
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: '',
|
||||
[EmailTemplateInputDataFields.DESIGN]: '',
|
||||
};
|
||||
|
||||
const initTemplateValidatorData: validatorDataType = {
|
||||
@@ -91,6 +100,9 @@ const UpdateEmailTemplate = ({
|
||||
const emailEditorRef = useRef(null);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [editor, setEditor] = useState<string>(
|
||||
EmailTemplateEditors.PLAIN_HTML_EDITOR,
|
||||
);
|
||||
const [templateVariables, setTemplateVariables] = useState<
|
||||
templateVariableDataTypes[]
|
||||
>([]);
|
||||
@@ -107,9 +119,11 @@ const UpdateEmailTemplate = ({
|
||||
if (selectedTemplate) {
|
||||
const { design } = selectedTemplate;
|
||||
try {
|
||||
const designData = JSON.parse(design);
|
||||
// @ts-ignore
|
||||
emailEditorRef.current.editor.loadDesign(designData);
|
||||
if (design) {
|
||||
const designData = JSON.parse(design);
|
||||
// @ts-ignore
|
||||
emailEditorRef.current.editor.loadDesign(designData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
onClose();
|
||||
@@ -136,70 +150,85 @@ const UpdateEmailTemplate = ({
|
||||
);
|
||||
};
|
||||
|
||||
const updateTemplate = async (params: emailTemplateDataType) => {
|
||||
let res: any = {};
|
||||
if (
|
||||
view === UpdateModalViews.Edit &&
|
||||
selectedTemplate?.[EmailTemplateInputDataFields.ID]
|
||||
) {
|
||||
res = await client
|
||||
.mutation(EditEmailTemplate, {
|
||||
params: {
|
||||
...params,
|
||||
id: selectedTemplate[EmailTemplateInputDataFields.ID],
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
} else {
|
||||
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
|
||||
}
|
||||
setLoading(false);
|
||||
if (res.error) {
|
||||
toast({
|
||||
title: capitalizeFirstLetter(res.error.message),
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'top-right',
|
||||
});
|
||||
} else if (
|
||||
res.data?._add_email_template ||
|
||||
res.data?._update_email_template
|
||||
) {
|
||||
toast({
|
||||
title: capitalizeFirstLetter(
|
||||
res.data?._add_email_template?.message ||
|
||||
res.data?._update_email_template?.message,
|
||||
),
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'top-right',
|
||||
});
|
||||
setTemplateData({
|
||||
...initTemplateData,
|
||||
});
|
||||
setValidator({ ...initTemplateValidatorData });
|
||||
fetchEmailTemplatesData();
|
||||
}
|
||||
};
|
||||
|
||||
const saveData = async () => {
|
||||
if (!validateData()) return;
|
||||
setLoading(true);
|
||||
// @ts-ignore
|
||||
return await emailEditorRef.current.editor.exportHtml(async (data) => {
|
||||
const { design, html } = data;
|
||||
if (!html || !design) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
[EmailTemplateInputDataFields.EVENT_NAME]:
|
||||
templateData[EmailTemplateInputDataFields.EVENT_NAME],
|
||||
[EmailTemplateInputDataFields.SUBJECT]:
|
||||
templateData[EmailTemplateInputDataFields.SUBJECT],
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: html.trim(),
|
||||
[EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design),
|
||||
};
|
||||
let res: any = {};
|
||||
if (
|
||||
view === UpdateModalViews.Edit &&
|
||||
selectedTemplate?.[EmailTemplateInputDataFields.ID]
|
||||
) {
|
||||
res = await client
|
||||
.mutation(EditEmailTemplate, {
|
||||
params: {
|
||||
...params,
|
||||
id: selectedTemplate[EmailTemplateInputDataFields.ID],
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
} else {
|
||||
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
|
||||
}
|
||||
setLoading(false);
|
||||
if (res.error) {
|
||||
toast({
|
||||
title: capitalizeFirstLetter(res.error.message),
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
} else if (
|
||||
res.data?._add_email_template ||
|
||||
res.data?._update_email_template
|
||||
) {
|
||||
toast({
|
||||
title: capitalizeFirstLetter(
|
||||
res.data?._add_email_template?.message ||
|
||||
res.data?._update_email_template?.message
|
||||
),
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
setTemplateData({
|
||||
...initTemplateData,
|
||||
});
|
||||
setValidator({ ...initTemplateValidatorData });
|
||||
fetchEmailTemplatesData();
|
||||
}
|
||||
view === UpdateModalViews.ADD && onClose();
|
||||
});
|
||||
let params: emailTemplateDataType = {
|
||||
[EmailTemplateInputDataFields.EVENT_NAME]:
|
||||
templateData[EmailTemplateInputDataFields.EVENT_NAME],
|
||||
[EmailTemplateInputDataFields.SUBJECT]:
|
||||
templateData[EmailTemplateInputDataFields.SUBJECT],
|
||||
[EmailTemplateInputDataFields.TEMPLATE]:
|
||||
templateData[EmailTemplateInputDataFields.TEMPLATE],
|
||||
[EmailTemplateInputDataFields.DESIGN]: '',
|
||||
};
|
||||
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
|
||||
// @ts-ignore
|
||||
await emailEditorRef.current.editor.exportHtml(async (data) => {
|
||||
const { design, html } = data;
|
||||
if (!html || !design) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
params = {
|
||||
...params,
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: html.trim(),
|
||||
[EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design),
|
||||
};
|
||||
await updateTemplate(params);
|
||||
});
|
||||
} else {
|
||||
await updateTemplate(params);
|
||||
}
|
||||
view === UpdateModalViews.ADD && onClose();
|
||||
};
|
||||
|
||||
const resetData = () => {
|
||||
if (selectedTemplate) {
|
||||
setTemplateData(selectedTemplate);
|
||||
@@ -207,6 +236,8 @@ const UpdateEmailTemplate = ({
|
||||
setTemplateData({ ...initTemplateData });
|
||||
}
|
||||
};
|
||||
|
||||
// set template data if edit modal is open
|
||||
useEffect(() => {
|
||||
if (
|
||||
isOpen &&
|
||||
@@ -214,13 +245,15 @@ const UpdateEmailTemplate = ({
|
||||
selectedTemplate &&
|
||||
Object.keys(selectedTemplate || {}).length
|
||||
) {
|
||||
const { id, created_at, template, design, ...rest } = selectedTemplate;
|
||||
const { id, created_at, ...rest } = selectedTemplate;
|
||||
setTemplateData(rest);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// set template variables
|
||||
useEffect(() => {
|
||||
const updatedTemplateVariables = Object.entries(
|
||||
emailTemplateVariables
|
||||
emailTemplateVariables,
|
||||
).reduce((acc, [key, val]): any => {
|
||||
if (
|
||||
(templateData[EmailTemplateInputDataFields.EVENT_NAME] !==
|
||||
@@ -244,6 +277,51 @@ const UpdateEmailTemplate = ({
|
||||
setTemplateVariables(updatedTemplateVariables);
|
||||
}, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]);
|
||||
|
||||
// change editor
|
||||
useEffect(() => {
|
||||
if (isOpen && selectedTemplate) {
|
||||
const { design } = selectedTemplate;
|
||||
if (design) {
|
||||
setEditor(EmailTemplateEditors.UNLAYER_EDITOR);
|
||||
} else {
|
||||
setEditor(EmailTemplateEditors.PLAIN_HTML_EDITOR);
|
||||
}
|
||||
}
|
||||
}, [isOpen, selectedTemplate]);
|
||||
|
||||
// reset fields when editor is changed
|
||||
useEffect(() => {
|
||||
if (selectedTemplate?.design) {
|
||||
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
|
||||
setTemplateData({
|
||||
...templateData,
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate.template,
|
||||
[EmailTemplateInputDataFields.DESIGN]: selectedTemplate.design,
|
||||
});
|
||||
} else {
|
||||
setTemplateData({
|
||||
...templateData,
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: '',
|
||||
[EmailTemplateInputDataFields.DESIGN]: '',
|
||||
});
|
||||
}
|
||||
} else if (selectedTemplate?.template) {
|
||||
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
|
||||
setTemplateData({
|
||||
...templateData,
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: '',
|
||||
[EmailTemplateInputDataFields.DESIGN]: '',
|
||||
});
|
||||
} else {
|
||||
setTemplateData({
|
||||
...templateData,
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate?.template,
|
||||
[EmailTemplateInputDataFields.DESIGN]: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{view === UpdateModalViews.ADD ? (
|
||||
@@ -367,7 +445,7 @@ const UpdateEmailTemplate = ({
|
||||
onChange={(e) =>
|
||||
inputChangehandler(
|
||||
EmailTemplateInputDataFields.EVENT_NAME,
|
||||
e.currentTarget.value
|
||||
e.currentTarget.value,
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -376,7 +454,7 @@ const UpdateEmailTemplate = ({
|
||||
<option value={value} key={key}>
|
||||
{key}
|
||||
</option>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</Select>
|
||||
</Flex>
|
||||
@@ -401,7 +479,7 @@ const UpdateEmailTemplate = ({
|
||||
onChange={(e) =>
|
||||
inputChangehandler(
|
||||
EmailTemplateInputDataFields.SUBJECT,
|
||||
e.currentTarget.value
|
||||
e.currentTarget.value,
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -414,7 +492,22 @@ const UpdateEmailTemplate = ({
|
||||
alignItems="center"
|
||||
marginBottom="2%"
|
||||
>
|
||||
Template Body
|
||||
<Flex flex="1">Template Body</Flex>
|
||||
<Flex flex="3">
|
||||
<RadioGroup
|
||||
onChange={(value) => setEditor(value)}
|
||||
value={editor}
|
||||
>
|
||||
<Stack direction="row" spacing="50px">
|
||||
<Radio value={EmailTemplateEditors.PLAIN_HTML_EDITOR}>
|
||||
Plain HTML
|
||||
</Radio>
|
||||
<Radio value={EmailTemplateEditors.UNLAYER_EDITOR}>
|
||||
Unlayer Editor
|
||||
</Radio>
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex
|
||||
width="100%"
|
||||
@@ -423,7 +516,22 @@ const UpdateEmailTemplate = ({
|
||||
border="1px solid"
|
||||
borderColor="gray.200"
|
||||
>
|
||||
<EmailEditor ref={emailEditorRef} onReady={onReady} />
|
||||
{editor === EmailTemplateEditors.UNLAYER_EDITOR ? (
|
||||
<EmailEditor ref={emailEditorRef} onReady={onReady} />
|
||||
) : (
|
||||
<Textarea
|
||||
value={templateData.template}
|
||||
onChange={(e) => {
|
||||
setTemplateData({
|
||||
...templateData,
|
||||
[EmailTemplateInputDataFields.TEMPLATE]: e.target.value,
|
||||
});
|
||||
}}
|
||||
placeholder="Template HTML"
|
||||
border="0"
|
||||
height="500px"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
@@ -126,13 +126,13 @@ const UpdateWebhookModal = ({
|
||||
...initWebhookValidatorData,
|
||||
});
|
||||
const [verifiedStatus, setVerifiedStatus] = useState<webhookVerifiedStatus>(
|
||||
webhookVerifiedStatus.PENDING
|
||||
webhookVerifiedStatus.PENDING,
|
||||
);
|
||||
const inputChangehandler = (
|
||||
inputType: string,
|
||||
value: any,
|
||||
headerInputType: string = WebhookInputHeaderFields.KEY,
|
||||
headerIndex: number = 0
|
||||
headerIndex: number = 0,
|
||||
) => {
|
||||
if (
|
||||
verifiedStatus !== webhookVerifiedStatus.PENDING &&
|
||||
@@ -238,7 +238,7 @@ const UpdateWebhookModal = ({
|
||||
validator[WebhookInputDataFields.ENDPOINT] &&
|
||||
!validator[WebhookInputDataFields.HEADERS].some(
|
||||
(headerData: headersValidatorDataType) =>
|
||||
!headerData.key || !headerData.value
|
||||
!headerData.key || !headerData.value,
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -256,7 +256,7 @@ const UpdateWebhookModal = ({
|
||||
(acc, data) => {
|
||||
return data.key ? { ...acc, [data.key]: data.value } : acc;
|
||||
},
|
||||
{}
|
||||
{},
|
||||
);
|
||||
if (Object.keys(headers).length) {
|
||||
params[WebhookInputDataFields.HEADERS] = headers;
|
||||
@@ -290,16 +290,16 @@ const UpdateWebhookModal = ({
|
||||
title: capitalizeFirstLetter(res.error.message),
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
} else if (res.data?._add_webhook || res.data?._update_webhook) {
|
||||
toast({
|
||||
title: capitalizeFirstLetter(
|
||||
res.data?._add_webhook?.message || res.data?._update_webhook?.message
|
||||
res.data?._add_webhook?.message || res.data?._update_webhook?.message,
|
||||
),
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
setWebhook({
|
||||
...initWebhookData,
|
||||
@@ -333,7 +333,7 @@ const UpdateWebhookModal = ({
|
||||
setValidator({
|
||||
...validator,
|
||||
[WebhookInputDataFields.HEADERS]: new Array(
|
||||
formattedHeadersData.length
|
||||
formattedHeadersData.length,
|
||||
)
|
||||
.fill({})
|
||||
.map(() => ({ ...initHeadersValidatorData })),
|
||||
@@ -406,7 +406,7 @@ const UpdateWebhookModal = ({
|
||||
onChange={(e) =>
|
||||
inputChangehandler(
|
||||
WebhookInputDataFields.EVENT_NAME,
|
||||
e.currentTarget.value
|
||||
e.currentTarget.value,
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -415,7 +415,7 @@ const UpdateWebhookModal = ({
|
||||
<option value={value} key={key}>
|
||||
{key}
|
||||
</option>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</Select>
|
||||
</Flex>
|
||||
@@ -438,7 +438,7 @@ const UpdateWebhookModal = ({
|
||||
onChange={(e) =>
|
||||
inputChangehandler(
|
||||
WebhookInputDataFields.ENDPOINT,
|
||||
e.currentTarget.value
|
||||
e.currentTarget.value,
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -462,7 +462,7 @@ const UpdateWebhookModal = ({
|
||||
onChange={() =>
|
||||
inputChangehandler(
|
||||
WebhookInputDataFields.ENABLED,
|
||||
!webhook[WebhookInputDataFields.ENABLED]
|
||||
!webhook[WebhookInputDataFields.ENABLED],
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -517,7 +517,7 @@ const UpdateWebhookModal = ({
|
||||
WebhookInputDataFields.HEADERS,
|
||||
e.target.value,
|
||||
WebhookInputHeaderFields.KEY,
|
||||
index
|
||||
index,
|
||||
)
|
||||
}
|
||||
width="30%"
|
||||
@@ -540,7 +540,7 @@ const UpdateWebhookModal = ({
|
||||
WebhookInputDataFields.HEADERS,
|
||||
e.target.value,
|
||||
WebhookInputHeaderFields.VALUE,
|
||||
index
|
||||
index,
|
||||
)
|
||||
}
|
||||
width="65%"
|
||||
@@ -560,7 +560,7 @@ const UpdateWebhookModal = ({
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</Flex>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</Flex>
|
||||
<Divider marginY={5} />
|
||||
|
@@ -161,15 +161,15 @@ const ViewWebhookLogsModal = ({
|
||||
<Td>
|
||||
<Text fontSize="sm">{`${logData.id.substring(
|
||||
0,
|
||||
5
|
||||
5,
|
||||
)}***${logData.id.substring(
|
||||
logData.id.length - 5,
|
||||
logData.id.length
|
||||
logData.id.length,
|
||||
)}`}</Text>
|
||||
</Td>
|
||||
<Td>
|
||||
{dayjs(logData.created_at * 1000).format(
|
||||
'MMM DD, YYYY'
|
||||
'MMM DD, YYYY',
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
|
@@ -10,11 +10,14 @@ export const TextInputType = {
|
||||
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
|
||||
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
|
||||
TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID',
|
||||
MICROSOFT_CLIENT_ID: 'MICROSOFT_CLIENT_ID',
|
||||
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: 'MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID',
|
||||
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
|
||||
REDIS_URL: 'REDIS_URL',
|
||||
SMTP_HOST: 'SMTP_HOST',
|
||||
SMTP_PORT: 'SMTP_PORT',
|
||||
SMTP_USERNAME: 'SMTP_USERNAME',
|
||||
SMTP_LOCAL_NAME: 'SMTP_LOCAL_NAME',
|
||||
SENDER_EMAIL: 'SENDER_EMAIL',
|
||||
ORGANIZATION_NAME: 'ORGANIZATION_NAME',
|
||||
ORGANIZATION_LOGO: 'ORGANIZATION_LOGO',
|
||||
@@ -37,6 +40,7 @@ export const HiddenInputType = {
|
||||
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
|
||||
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
|
||||
TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET',
|
||||
MICROSOFT_CLIENT_SECRET: 'MICROSOFT_CLIENT_SECRET',
|
||||
JWT_SECRET: 'JWT_SECRET',
|
||||
SMTP_PASSWORD: 'SMTP_PASSWORD',
|
||||
ADMIN_SECRET: 'ADMIN_SECRET',
|
||||
@@ -48,7 +52,6 @@ export const ArrayInputType = {
|
||||
DEFAULT_ROLES: 'DEFAULT_ROLES',
|
||||
PROTECTED_ROLES: 'PROTECTED_ROLES',
|
||||
ALLOWED_ORIGINS: 'ALLOWED_ORIGINS',
|
||||
USER_ROLES: 'roles',
|
||||
};
|
||||
|
||||
export const SelectInputType = {
|
||||
@@ -56,6 +59,10 @@ export const SelectInputType = {
|
||||
GENDER: 'gender',
|
||||
};
|
||||
|
||||
export const MultiSelectInputType = {
|
||||
USER_ROLES: 'roles',
|
||||
};
|
||||
|
||||
export const TextAreaInputType = {
|
||||
CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT',
|
||||
JWT_PRIVATE_KEY: 'JWT_PRIVATE_KEY',
|
||||
@@ -63,6 +70,8 @@ export const TextAreaInputType = {
|
||||
};
|
||||
|
||||
export const SwitchInputType = {
|
||||
APP_COOKIE_SECURE: 'APP_COOKIE_SECURE',
|
||||
ADMIN_COOKIE_SECURE: 'ADMIN_COOKIE_SECURE',
|
||||
DISABLE_LOGIN_PAGE: 'DISABLE_LOGIN_PAGE',
|
||||
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
|
||||
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
|
||||
@@ -114,6 +123,9 @@ export interface envVarTypes {
|
||||
APPLE_CLIENT_SECRET: string;
|
||||
TWITTER_CLIENT_ID: string;
|
||||
TWITTER_CLIENT_SECRET: string;
|
||||
MICROSOFT_CLIENT_ID: string;
|
||||
MICROSOFT_CLIENT_SECRET: string;
|
||||
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: string;
|
||||
ROLES: [string] | [];
|
||||
DEFAULT_ROLES: [string] | [];
|
||||
PROTECTED_ROLES: [string] | [];
|
||||
@@ -127,12 +139,15 @@ export interface envVarTypes {
|
||||
SMTP_PORT: string;
|
||||
SMTP_USERNAME: string;
|
||||
SMTP_PASSWORD: string;
|
||||
SMTP_LOCAL_NAME: string;
|
||||
SENDER_EMAIL: string;
|
||||
ALLOWED_ORIGINS: [string] | [];
|
||||
ORGANIZATION_NAME: string;
|
||||
ORGANIZATION_LOGO: string;
|
||||
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
|
||||
ADMIN_SECRET: string;
|
||||
APP_COOKIE_SECURE: boolean;
|
||||
ADMIN_COOKIE_SECURE: boolean;
|
||||
DISABLE_LOGIN_PAGE: boolean;
|
||||
DISABLE_MAGIC_LINK_LOGIN: boolean;
|
||||
DISABLE_EMAIL_VERIFICATION: boolean;
|
||||
@@ -328,3 +343,8 @@ export const webhookPayloadExample: string = `{
|
||||
},
|
||||
"auth_recipe":"google"
|
||||
}`;
|
||||
|
||||
export enum EmailTemplateEditors {
|
||||
UNLAYER_EDITOR = 'unlayer_editor',
|
||||
PLAIN_HTML_EDITOR = 'plain_html_editor',
|
||||
}
|
||||
|
@@ -32,6 +32,9 @@ export const EnvVariablesQuery = `
|
||||
APPLE_CLIENT_SECRET
|
||||
TWITTER_CLIENT_ID
|
||||
TWITTER_CLIENT_SECRET
|
||||
MICROSOFT_CLIENT_ID
|
||||
MICROSOFT_CLIENT_SECRET
|
||||
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID
|
||||
DEFAULT_ROLES
|
||||
PROTECTED_ROLES
|
||||
ROLES
|
||||
@@ -45,11 +48,14 @@ export const EnvVariablesQuery = `
|
||||
SMTP_PORT
|
||||
SMTP_USERNAME
|
||||
SMTP_PASSWORD
|
||||
SMTP_LOCAL_NAME
|
||||
SENDER_EMAIL
|
||||
ALLOWED_ORIGINS
|
||||
ORGANIZATION_NAME
|
||||
ORGANIZATION_LOGO
|
||||
ADMIN_SECRET
|
||||
APP_COOKIE_SECURE
|
||||
ADMIN_COOKIE_SECURE
|
||||
DISABLE_LOGIN_PAGE
|
||||
DISABLE_MAGIC_LINK_LOGIN
|
||||
DISABLE_EMAIL_VERIFICATION
|
||||
@@ -167,3 +173,12 @@ export const WebhookLogsQuery = `
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GetAvailableRolesQuery = `
|
||||
query {
|
||||
_env {
|
||||
ROLES
|
||||
PROTECTED_ROLES
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@@ -6,5 +6,5 @@ ReactDOM.render(
|
||||
<div>
|
||||
<App />
|
||||
</div>,
|
||||
document.getElementById('root')
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
@@ -57,7 +57,7 @@ export default function Auth() {
|
||||
title: capitalizeFirstLetter(error.message),
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -154,7 +154,7 @@ const EmailTemplates = () => {
|
||||
<Td>{templateData[EmailTemplateInputDataFields.SUBJECT]}</Td>
|
||||
<Td>
|
||||
{dayjs(templateData.created_at * 1000).format(
|
||||
'MMM DD, YYYY'
|
||||
'MMM DD, YYYY',
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
|
@@ -52,6 +52,9 @@ const Environment = () => {
|
||||
APPLE_CLIENT_SECRET: '',
|
||||
TWITTER_CLIENT_ID: '',
|
||||
TWITTER_CLIENT_SECRET: '',
|
||||
MICROSOFT_CLIENT_ID: '',
|
||||
MICROSOFT_CLIENT_SECRET: '',
|
||||
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: '',
|
||||
ROLES: [],
|
||||
DEFAULT_ROLES: [],
|
||||
PROTECTED_ROLES: [],
|
||||
@@ -65,12 +68,15 @@ const Environment = () => {
|
||||
SMTP_PORT: '',
|
||||
SMTP_USERNAME: '',
|
||||
SMTP_PASSWORD: '',
|
||||
SMTP_LOCAL_NAME: '',
|
||||
SENDER_EMAIL: '',
|
||||
ALLOWED_ORIGINS: [],
|
||||
ORGANIZATION_NAME: '',
|
||||
ORGANIZATION_LOGO: '',
|
||||
CUSTOM_ACCESS_TOKEN_SCRIPT: '',
|
||||
ADMIN_SECRET: '',
|
||||
APP_COOKIE_SECURE: false,
|
||||
ADMIN_COOKIE_SECURE: false,
|
||||
DISABLE_LOGIN_PAGE: false,
|
||||
DISABLE_MAGIC_LINK_LOGIN: false,
|
||||
DISABLE_EMAIL_VERIFICATION: false,
|
||||
@@ -155,7 +161,7 @@ const Environment = () => {
|
||||
// @ts-ignore
|
||||
[property]: envVariables[property],
|
||||
}),
|
||||
{}
|
||||
{},
|
||||
);
|
||||
if (
|
||||
updatedEnvVariables[HiddenInputType.ADMIN_SECRET] === '' ||
|
||||
@@ -200,7 +206,7 @@ const Environment = () => {
|
||||
} variables`,
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
};
|
||||
|
||||
|
@@ -29,7 +29,7 @@ import {
|
||||
MenuItem,
|
||||
useToast,
|
||||
Spinner,
|
||||
TableContainer
|
||||
TableContainer,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
FaAngleLeft,
|
||||
@@ -180,14 +180,14 @@ export default function Users() {
|
||||
title: 'User verification failed',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
} else if (res.data?._update_user?.id) {
|
||||
toast({
|
||||
title: 'User verification successful',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
updateUserList();
|
||||
@@ -195,7 +195,7 @@ export default function Users() {
|
||||
|
||||
const updateAccessHandler = async (
|
||||
id: string,
|
||||
action: updateAccessActions
|
||||
action: updateAccessActions,
|
||||
) => {
|
||||
switch (action) {
|
||||
case updateAccessActions.ENABLE:
|
||||
@@ -211,14 +211,14 @@ export default function Users() {
|
||||
title: 'User access enable failed',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'User access enabled successfully',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
updateUserList();
|
||||
@@ -236,14 +236,14 @@ export default function Users() {
|
||||
title: 'User access revoke failed',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'User access revoked successfully',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
}
|
||||
updateUserList();
|
||||
@@ -263,11 +263,12 @@ export default function Users() {
|
||||
.toPromise();
|
||||
if (res.data?._update_user?.id) {
|
||||
toast({
|
||||
title: `Multi factor authentication ${user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled'
|
||||
} for user`,
|
||||
title: `Multi factor authentication ${
|
||||
user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled'
|
||||
} for user`,
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
updateUserList();
|
||||
return;
|
||||
@@ -276,7 +277,7 @@ export default function Users() {
|
||||
title: 'Multi factor authentication update failed for user',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
position: 'top-right',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -387,7 +388,7 @@ export default function Users() {
|
||||
onClick={() =>
|
||||
updateAccessHandler(
|
||||
user.id,
|
||||
updateAccessActions.ENABLE
|
||||
updateAccessActions.ENABLE,
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -398,7 +399,7 @@ export default function Users() {
|
||||
onClick={() =>
|
||||
updateAccessHandler(
|
||||
user.id,
|
||||
updateAccessActions.REVOKE
|
||||
updateAccessActions.REVOKE,
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -407,13 +408,17 @@ export default function Users() {
|
||||
)}
|
||||
{user.is_multi_factor_auth_enabled ? (
|
||||
<MenuItem
|
||||
onClick={() => multiFactorAuthUpdateHandler(user)}
|
||||
onClick={() =>
|
||||
multiFactorAuthUpdateHandler(user)
|
||||
}
|
||||
>
|
||||
Disable MultiFactor Authentication
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem
|
||||
onClick={() => multiFactorAuthUpdateHandler(user)}
|
||||
onClick={() =>
|
||||
multiFactorAuthUpdateHandler(user)
|
||||
}
|
||||
>
|
||||
Enable MultiFactor Authentication
|
||||
</MenuItem>
|
||||
|
@@ -170,12 +170,12 @@ const Webhooks = () => {
|
||||
label={JSON.stringify(
|
||||
webhook[WebhookInputDataFields.HEADERS],
|
||||
null,
|
||||
' '
|
||||
' ',
|
||||
)}
|
||||
>
|
||||
<Tag size="sm" variant="outline" colorScheme="gray">
|
||||
{Object.keys(
|
||||
webhook[WebhookInputDataFields.HEADERS] || {}
|
||||
webhook[WebhookInputDataFields.HEADERS] || {},
|
||||
)?.length.toString()}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
|
@@ -67,7 +67,7 @@ export const validateEmail = (email: string) => {
|
||||
return email
|
||||
.toLowerCase()
|
||||
.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
)
|
||||
? true
|
||||
: false;
|
||||
@@ -78,7 +78,7 @@ export const validateURI = (uri: string) => {
|
||||
return uri
|
||||
.toLowerCase()
|
||||
.match(
|
||||
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/
|
||||
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/,
|
||||
)
|
||||
? true
|
||||
: false;
|
||||
|
@@ -27,7 +27,7 @@ const parseCSV = (file: File, delimiter: string): Promise<dataTypes[]> => {
|
||||
value: email.trim(),
|
||||
isInvalid: !validateEmail(email.trim()),
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
|
1622
dashboard/yarn.lock
Normal file
1622
dashboard/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
39
scripts/couchbase-test.sh
Normal file
39
scripts/couchbase-test.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -x
|
||||
set -m
|
||||
|
||||
sleep 15
|
||||
|
||||
# Setup index and memory quota
|
||||
# curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=300 -d indexMemoryQuota=300
|
||||
|
||||
# Setup services
|
||||
curl -v http://127.0.0.1:8091/node/controller/setupServices -d services=kv%2Cn1ql%2Cindex
|
||||
|
||||
# Setup credentials
|
||||
curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=Administrator -d password=password
|
||||
|
||||
# Setup Memory Optimized Indexes
|
||||
curl -i -u Administrator:password -X POST http://127.0.0.1:8091/settings/indexes -d 'storageMode=memory_optimized'
|
||||
|
||||
# Load travel-sample bucket
|
||||
#curl -v -u Administrator:password -X POST http://127.0.0.1:8091/sampleBuckets/install -d '["travel-sample"]'
|
||||
|
||||
echo "Type: $TYPE"
|
||||
|
||||
if [ "$TYPE" = "WORKER" ]; then
|
||||
echo "Sleeping ..."
|
||||
sleep 15
|
||||
|
||||
#IP=`hostname -s`
|
||||
IP=`hostname -I | cut -d ' ' -f1`
|
||||
echo "IP: " $IP
|
||||
|
||||
echo "Auto Rebalance: $AUTO_REBALANCE"
|
||||
if [ "$AUTO_REBALANCE" = "true" ]; then
|
||||
couchbase-cli rebalance --cluster=$COUCHBASE_MASTER:8091 --user=Administrator --password=password --server-add=$IP --server-add-username=Administrator --server-add-password=password
|
||||
else
|
||||
couchbase-cli server-add --cluster=$COUCHBASE_MASTER:8091 --user=Administrator --password=password --server-add=$IP --server-add-username=Administrator --server-add-password=password
|
||||
fi;
|
||||
fi;
|
@@ -3,6 +3,8 @@ package constants
|
||||
const (
|
||||
// AuthRecipeMethodBasicAuth is the basic_auth auth method
|
||||
AuthRecipeMethodBasicAuth = "basic_auth"
|
||||
// AuthRecipeMethodMobileBasicAuth is the mobile basic_auth method, where user can signup using mobile number and password
|
||||
AuthRecipeMethodMobileBasicAuth = "mobile_basic_auth"
|
||||
// AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
|
||||
AuthRecipeMethodMagicLinkLogin = "magic_link_login"
|
||||
// AuthRecipeMethodGoogle is the google auth method
|
||||
@@ -17,4 +19,6 @@ const (
|
||||
AuthRecipeMethodApple = "apple"
|
||||
// AuthRecipeMethodTwitter is the twitter auth method
|
||||
AuthRecipeMethodTwitter = "twitter"
|
||||
// AuthRecipeMethodMicrosoft is the microsoft auth method
|
||||
AuthRecipeMethodMicrosoft = "microsoft"
|
||||
)
|
||||
|
@@ -25,4 +25,8 @@ const (
|
||||
DbTypeCockroachDB = "cockroachdb"
|
||||
// DbTypePlanetScaleDB is the planetscale database type
|
||||
DbTypePlanetScaleDB = "planetscale"
|
||||
// DbTypeDynamoDB is the Dynamo database type
|
||||
DbTypeDynamoDB = "dynamodb"
|
||||
// DbTypeCouchbaseDB is the Couchbase database type
|
||||
DbTypeCouchbaseDB = "couchbase"
|
||||
)
|
||||
|
@@ -21,6 +21,12 @@ const (
|
||||
EnvKeyDatabaseType = "DATABASE_TYPE"
|
||||
// EnvKeyDatabaseURL key for env variable DATABASE_URL
|
||||
EnvKeyDatabaseURL = "DATABASE_URL"
|
||||
// EnvAwsRegion key for env variable AWS REGION
|
||||
EnvAwsRegion = "AWS_REGION"
|
||||
// EnvAwsAccessKeyID key for env variable AWS_ACCESS_KEY_ID
|
||||
EnvAwsAccessKeyID = "AWS_ACCESS_KEY_ID"
|
||||
// EnvAwsAccessKey key for env variable AWS_SECRET_ACCESS_KEY
|
||||
EnvAwsSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
|
||||
// EnvKeyDatabaseName key for env variable DATABASE_NAME
|
||||
EnvKeyDatabaseName = "DATABASE_NAME"
|
||||
// EnvKeyDatabaseUsername key for env variable DATABASE_USERNAME
|
||||
@@ -37,6 +43,13 @@ const (
|
||||
EnvKeyDatabaseCertKey = "DATABASE_CERT_KEY"
|
||||
// EnvKeyDatabaseCACert key for env variable DATABASE_CA_CERT
|
||||
EnvKeyDatabaseCACert = "DATABASE_CA_CERT"
|
||||
// EnvCouchbaseBucket key for env variable COUCHBASE_BUCKET
|
||||
EnvCouchbaseBucket = "COUCHBASE_BUCKET"
|
||||
// EnvCouchbaseBucketRAMQuotaMB key for env variable COUCHBASE_BUCKET_RAM_QUOTA
|
||||
// This value should be parsed as number
|
||||
EnvCouchbaseBucketRAMQuotaMB = "COUCHBASE_BUCKET_RAM_QUOTA"
|
||||
// EnvCouchbaseBucket key for env variable COUCHBASE_SCOPE
|
||||
EnvCouchbaseScope = "COUCHBASE_SCOPE"
|
||||
// EnvKeySmtpHost key for env variable SMTP_HOST
|
||||
EnvKeySmtpHost = "SMTP_HOST"
|
||||
// EnvKeySmtpPort key for env variable SMTP_PORT
|
||||
@@ -45,6 +58,8 @@ const (
|
||||
EnvKeySmtpUsername = "SMTP_USERNAME"
|
||||
// EnvKeySmtpPassword key for env variable SMTP_PASSWORD
|
||||
EnvKeySmtpPassword = "SMTP_PASSWORD"
|
||||
// EnvKeySmtpLocalName key for env variable SMTP_LOCAL_NAME
|
||||
EnvKeySmtpLocalName = "SMTP_LOCAL_NAME"
|
||||
// EnvKeySenderEmail key for env variable SENDER_EMAIL
|
||||
EnvKeySenderEmail = "SENDER_EMAIL"
|
||||
// EnvKeyIsEmailServiceEnabled key for env variable IS_EMAIL_SERVICE_ENABLED
|
||||
@@ -93,6 +108,12 @@ const (
|
||||
EnvKeyTwitterClientID = "TWITTER_CLIENT_ID"
|
||||
// EnvKeyTwitterClientSecret key for env variable TWITTER_CLIENT_SECRET
|
||||
EnvKeyTwitterClientSecret = "TWITTER_CLIENT_SECRET"
|
||||
// EnvKeyMicrosoftClientID key for env variable MICROSOFT_CLIENT_ID
|
||||
EnvKeyMicrosoftClientID = "MICROSOFT_CLIENT_ID"
|
||||
// EnvKeyMicrosoftActiveDirectoryTenantID key for env variable MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID
|
||||
EnvKeyMicrosoftActiveDirectoryTenantID = "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID"
|
||||
// EnvKeyMicrosoftClientSecret key for env variable MICROSOFT_CLIENT_SECRET
|
||||
EnvKeyMicrosoftClientSecret = "MICROSOFT_CLIENT_SECRET"
|
||||
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
|
||||
EnvKeyOrganizationName = "ORGANIZATION_NAME"
|
||||
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
|
||||
@@ -117,6 +138,8 @@ const (
|
||||
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
|
||||
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
|
||||
EnvKeyDisableBasicAuthentication = "DISABLE_BASIC_AUTHENTICATION"
|
||||
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_MOBILE_BASIC_AUTH
|
||||
EnvKeyDisableMobileBasicAuthentication = "DISABLE_MOBILE_BASIC_AUTHENTICATION"
|
||||
// EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN
|
||||
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
|
||||
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
|
||||
|
19
server/constants/oauth2.go
Normal file
19
server/constants/oauth2.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
// - query: for Authorization Code grant. 302 Found triggers redirect.
|
||||
ResponseModeQuery = "query"
|
||||
// - fragment: for Implicit grant. 302 Found triggers redirect.
|
||||
ResponseModeFragment = "fragment"
|
||||
// - form_post: 200 OK with response parameters embedded in an HTML form as hidden parameters.
|
||||
ResponseModeFormPost = "form_post"
|
||||
// - web_message: For Silent Authentication. Uses HTML5 web messaging.
|
||||
ResponseModeWebMessage = "web_message"
|
||||
|
||||
// For the Authorization Code grant, use response_type=code to include the authorization code.
|
||||
ResponseTypeCode = "code"
|
||||
// For the Implicit grant, use response_type=token to include an access token.
|
||||
ResponseTypeToken = "token"
|
||||
// For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
|
||||
ResponseTypeIDToken = "id_token"
|
||||
)
|
@@ -16,4 +16,8 @@ const (
|
||||
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
|
||||
|
||||
TwitterUserInfoURL = "https://api.twitter.com/2/users/me?user.fields=id,name,profile_image_url,username"
|
||||
|
||||
// Get microsoft user info.
|
||||
// Ref: https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo
|
||||
MicrosoftUserInfoURL = "https://graph.microsoft.com/oidc/userinfo"
|
||||
)
|
||||
|
@@ -29,8 +29,7 @@ func SetSession(gc *gin.Context, sessionID string) {
|
||||
domain = "." + domain
|
||||
}
|
||||
|
||||
// Use sameSite = lax by default
|
||||
// Since app cookie can come from cross site it becomes important to set this in lax mode.
|
||||
// Since app cookie can come from cross site it becomes important to set this in lax mode when insecure.
|
||||
// Example person using custom UI on their app domain and making request to authorizer domain.
|
||||
// For more information check:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
|
||||
|
@@ -7,6 +7,8 @@ import (
|
||||
"github.com/authorizerdev/authorizer/server/db/providers"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/cassandradb"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/couchbase"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/dynamodb"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/mongodb"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/sql"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
@@ -20,10 +22,12 @@ func InitDB() error {
|
||||
|
||||
envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv()
|
||||
|
||||
isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB
|
||||
isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB && envs.DatabaseType != constants.DbTypeDynamoDB && envs.DatabaseType != constants.DbTypeCouchbaseDB
|
||||
isArangoDB := envs.DatabaseType == constants.DbTypeArangodb
|
||||
isMongoDB := envs.DatabaseType == constants.DbTypeMongodb
|
||||
isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB || envs.DatabaseType == constants.DbTypeScyllaDB
|
||||
isDynamoDB := envs.DatabaseType == constants.DbTypeDynamoDB
|
||||
isCouchbaseDB := envs.DatabaseType == constants.DbTypeCouchbaseDB
|
||||
|
||||
if isSQL {
|
||||
log.Info("Initializing SQL Driver for: ", envs.DatabaseType)
|
||||
@@ -61,5 +65,23 @@ func InitDB() error {
|
||||
}
|
||||
}
|
||||
|
||||
if isDynamoDB {
|
||||
log.Info("Initializing DynamoDB Driver for: ", envs.DatabaseType)
|
||||
Provider, err = dynamodb.NewProvider()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to initialize DynamoDB driver: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isCouchbaseDB {
|
||||
log.Info("Initializing CouchbaseDB Driver for: ", envs.DatabaseType)
|
||||
Provider, err = couchbase.NewProvider()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to initialize Couchbase driver: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -9,14 +9,14 @@ import (
|
||||
|
||||
// EmailTemplate model for database
|
||||
type EmailTemplate struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
|
||||
Subject string `gorm:"type:text" json:"subject" bson:"subject" cql:"subject"`
|
||||
Template string `gorm:"type:text" json:"template" bson:"template" cql:"template"`
|
||||
Design string `gorm:"type:text" json:"design" bson:"design" cql:"design"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
|
||||
Subject string `json:"subject" bson:"subject" cql:"subject" dynamo:"subject"`
|
||||
Template string `json:"template" bson:"template" cql:"template" dynamo:"template"`
|
||||
Design string `json:"design" bson:"design" cql:"design" dynamo:"design"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
}
|
||||
|
||||
// AsAPIEmailTemplate to return email template as graphql response object
|
||||
|
@@ -4,10 +4,11 @@ package models
|
||||
|
||||
// Env model for db
|
||||
type Env struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
EnvData string `gorm:"type:text" json:"env" bson:"env" cql:"env"`
|
||||
Hash string `gorm:"type:text" json:"hash" bson:"hash" cql:"hash"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
EnvData string `json:"env" bson:"env" cql:"env" dynamo:"env"`
|
||||
Hash string `json:"hash" bson:"hash" cql:"hash" dynamo:"hash"`
|
||||
EncryptionKey string `json:"encryption_key" bson:"encryption_key" cql:"encryption_key" dynamo:"encryption_key"` // couchbase has "hash" as reserved keyword so we cannot use it. This will be empty for other dbs.
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
}
|
||||
|
@@ -2,11 +2,15 @@ package models
|
||||
|
||||
// OTP model for database
|
||||
type OTP struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
|
||||
Otp string `json:"otp" bson:"otp" cql:"otp"`
|
||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
|
||||
Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
|
||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
}
|
||||
|
||||
type Paging struct {
|
||||
ID string `json:"id,omitempty" dynamo:"id,hash"`
|
||||
}
|
||||
|
@@ -4,11 +4,11 @@ package models
|
||||
|
||||
// Session model for db
|
||||
type Session struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id"`
|
||||
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
|
||||
IP string `json:"ip" bson:"ip" cql:"ip"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" index:"user_id,hash"`
|
||||
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent" dynamo:"user_agent"`
|
||||
IP string `json:"ip" bson:"ip" cql:"ip" dynamo:"ip"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
}
|
||||
|
@@ -12,27 +12,27 @@ import (
|
||||
|
||||
// User model for db
|
||||
type User struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
|
||||
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
|
||||
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at"`
|
||||
Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password"`
|
||||
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"`
|
||||
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"`
|
||||
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"`
|
||||
MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name"`
|
||||
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname"`
|
||||
Gender *string `json:"gender" bson:"gender" cql:"gender"`
|
||||
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"`
|
||||
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number"`
|
||||
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at"`
|
||||
Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture"`
|
||||
Roles string `json:"roles" bson:"roles" cql:"roles"`
|
||||
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
|
||||
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
|
||||
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at" dynamo:"email_verified_at"`
|
||||
Password *string `json:"password" bson:"password" cql:"password" dynamo:"password"`
|
||||
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods" dynamo:"signup_methods"`
|
||||
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name" dynamo:"given_name"`
|
||||
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name" dynamo:"family_name"`
|
||||
MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name" dynamo:"middle_name"`
|
||||
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname" dynamo:"nickname"`
|
||||
Gender *string `json:"gender" bson:"gender" cql:"gender" dynamo:"gender"`
|
||||
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate" dynamo:"birthdate"`
|
||||
PhoneNumber *string `gorm:"index" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
|
||||
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at" dynamo:"phone_number_verified_at"`
|
||||
Picture *string `json:"picture" bson:"picture" cql:"picture" dynamo:"picture"`
|
||||
Roles string `json:"roles" bson:"roles" cql:"roles" dynamo:"roles"`
|
||||
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp" dynamo:"revoked_timestamp"`
|
||||
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled" dynamo:"is_multi_factor_auth_enabled"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
}
|
||||
|
||||
func (user *User) AsAPIUser() *model.User {
|
||||
|
@@ -11,16 +11,16 @@ import (
|
||||
|
||||
// VerificationRequest model for db
|
||||
type VerificationRequest struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token"` // token is reserved keyword in cassandra
|
||||
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier"`
|
||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
|
||||
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email"`
|
||||
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce"`
|
||||
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
Token string `json:"token" bson:"token" cql:"jwt_token" dynamo:"token" index:"token,hash"`
|
||||
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier" dynamo:"identifier"`
|
||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
|
||||
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email" dynamo:"email"`
|
||||
Nonce string `json:"nonce" bson:"nonce" cql:"nonce" dynamo:"nonce"`
|
||||
RedirectURI string `json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri" dynamo:"redirect_uri"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
}
|
||||
|
||||
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
||||
|
@@ -12,14 +12,14 @@ import (
|
||||
|
||||
// Webhook model for db
|
||||
type Webhook struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
|
||||
EndPoint string `gorm:"type:text" json:"endpoint" bson:"endpoint" cql:"endpoint"`
|
||||
Headers string `gorm:"type:text" json:"headers" bson:"headers" cql:"headers"`
|
||||
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
|
||||
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
|
||||
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
|
||||
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
}
|
||||
|
||||
// AsAPIWebhook to return webhook as graphql response object
|
||||
|
@@ -11,14 +11,14 @@ import (
|
||||
|
||||
// WebhookLog model for db
|
||||
type WebhookLog struct {
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||
HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status"`
|
||||
Response string `gorm:"type:text" json:"response" bson:"response" cql:"response"`
|
||||
Request string `gorm:"type:text" json:"request" bson:"request" cql:"request"`
|
||||
WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
|
||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
|
||||
HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status" dynamo:"http_status"`
|
||||
Response string `json:"response" bson:"response" cql:"response" dynamo:"response"`
|
||||
Request string `json:"request" bson:"request" cql:"request" dynamo:"request"`
|
||||
WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id" dynamo:"webhook_id" index:"webhook_id,hash"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
}
|
||||
|
||||
// AsAPIWebhookLog to return webhook log as graphql response object
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
arangoDriver "github.com/arangodb/go-driver"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
@@ -52,7 +51,7 @@ func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagin
|
||||
|
||||
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.EmailTemplate, pagination.Offset, pagination.Limit)
|
||||
|
||||
sctx := driver.WithQueryFullCount(ctx)
|
||||
sctx := arangoDriver.WithQueryFullCount(ctx)
|
||||
cursor, err := p.db.Query(sctx, query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -2,8 +2,11 @@ package arangodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
arangoDriver "github.com/arangodb/go-driver"
|
||||
"github.com/arangodb/go-driver/http"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
@@ -22,44 +25,75 @@ type provider struct {
|
||||
func NewProvider() (*provider, error) {
|
||||
ctx := context.Background()
|
||||
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
|
||||
conn, err := http.NewConnection(http.ConnectionConfig{
|
||||
dbUsername := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseUsername
|
||||
dbPassword := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePassword
|
||||
dbCACertificate := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseCACert
|
||||
httpConfig := http.ConnectionConfig{
|
||||
Endpoints: []string{dbURL},
|
||||
})
|
||||
}
|
||||
// If ca certificate if present, create tls config
|
||||
if dbCACertificate != "" {
|
||||
caCert, err := base64.StdEncoding.DecodeString(dbCACertificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Prepare TLS Config
|
||||
tlsConfig := &tls.Config{}
|
||||
certPool := x509.NewCertPool()
|
||||
if success := certPool.AppendCertsFromPEM(caCert); !success {
|
||||
return nil, fmt.Errorf("invalid certificate")
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
httpConfig.TLSConfig = tlsConfig
|
||||
}
|
||||
// Create new http connection
|
||||
conn, err := http.NewConnection(httpConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arangoClient, err := arangoDriver.NewClient(arangoDriver.ClientConfig{
|
||||
clientConfig := arangoDriver.ClientConfig{
|
||||
Connection: conn,
|
||||
})
|
||||
}
|
||||
if dbUsername != "" && dbPassword != "" {
|
||||
clientConfig.Authentication = arangoDriver.BasicAuthentication(dbUsername, dbPassword)
|
||||
}
|
||||
arangoClient, err := arangoDriver.NewClient(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var arangodb driver.Database
|
||||
var arangodb arangoDriver.Database
|
||||
dbName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseName
|
||||
arangodb_exists, err := arangoClient.DatabaseExists(nil, dbName)
|
||||
|
||||
arangodb_exists, err := arangoClient.DatabaseExists(ctx, dbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if arangodb_exists {
|
||||
arangodb, err = arangoClient.Database(nil, dbName)
|
||||
arangodb, err = arangoClient.Database(ctx, dbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
arangodb, err = arangoClient.CreateDatabase(nil, dbName, nil)
|
||||
arangodb, err = arangoClient.CreateDatabase(ctx, dbName, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
userCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !userCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.User, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
userCollection, _ := arangodb.Collection(nil, models.Collections.User)
|
||||
userCollection, err := arangodb.Collection(ctx, models.Collections.User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
|
||||
Unique: true,
|
||||
Sparse: true,
|
||||
@@ -70,6 +104,9 @@ func NewProvider() (*provider, error) {
|
||||
})
|
||||
|
||||
verificationRequestCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.VerificationRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !verificationRequestCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.VerificationRequest, nil)
|
||||
if err != nil {
|
||||
@@ -77,7 +114,10 @@ func NewProvider() (*provider, error) {
|
||||
}
|
||||
}
|
||||
|
||||
verificationRequestCollection, _ := arangodb.Collection(nil, models.Collections.VerificationRequest)
|
||||
verificationRequestCollection, err := arangodb.Collection(ctx, models.Collections.VerificationRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{
|
||||
Unique: true,
|
||||
Sparse: true,
|
||||
@@ -87,6 +127,9 @@ func NewProvider() (*provider, error) {
|
||||
})
|
||||
|
||||
sessionCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !sessionCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.Session, nil)
|
||||
if err != nil {
|
||||
@@ -94,13 +137,19 @@ func NewProvider() (*provider, error) {
|
||||
}
|
||||
}
|
||||
|
||||
sessionCollection, _ := arangodb.Collection(nil, models.Collections.Session)
|
||||
sessionCollection, err := arangodb.Collection(ctx, models.Collections.Session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessionCollection.EnsureHashIndex(ctx, []string{"user_id"}, &arangoDriver.EnsureHashIndexOptions{
|
||||
Sparse: true,
|
||||
})
|
||||
|
||||
configCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Env)
|
||||
if !configCollectionExists {
|
||||
envCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !envCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.Env, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -108,6 +157,9 @@ func NewProvider() (*provider, error) {
|
||||
}
|
||||
|
||||
webhookCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !webhookCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.Webhook, nil)
|
||||
if err != nil {
|
||||
@@ -115,13 +167,19 @@ func NewProvider() (*provider, error) {
|
||||
}
|
||||
}
|
||||
|
||||
webhookCollection, _ := arangodb.Collection(nil, models.Collections.Webhook)
|
||||
webhookCollection, err := arangodb.Collection(ctx, models.Collections.Webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhookCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
|
||||
Unique: true,
|
||||
Sparse: true,
|
||||
})
|
||||
|
||||
webhookLogCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.WebhookLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !webhookLogCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.WebhookLog, nil)
|
||||
if err != nil {
|
||||
@@ -129,12 +187,18 @@ func NewProvider() (*provider, error) {
|
||||
}
|
||||
}
|
||||
|
||||
webhookLogCollection, _ := arangodb.Collection(nil, models.Collections.WebhookLog)
|
||||
webhookLogCollection, err := arangodb.Collection(ctx, models.Collections.WebhookLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhookLogCollection.EnsureHashIndex(ctx, []string{"webhook_id"}, &arangoDriver.EnsureHashIndexOptions{
|
||||
Sparse: true,
|
||||
})
|
||||
|
||||
emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !emailTemplateCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.EmailTemplate, nil)
|
||||
if err != nil {
|
||||
@@ -142,13 +206,19 @@ func NewProvider() (*provider, error) {
|
||||
}
|
||||
}
|
||||
|
||||
emailTemplateCollection, _ := arangodb.Collection(nil, models.Collections.EmailTemplate)
|
||||
emailTemplateCollection, err := arangodb.Collection(ctx, models.Collections.EmailTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emailTemplateCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
|
||||
Unique: true,
|
||||
Sparse: true,
|
||||
})
|
||||
|
||||
otpCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.OTP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !otpCollectionExists {
|
||||
_, err = arangodb.CreateCollection(ctx, models.Collections.OTP, nil)
|
||||
if err != nil {
|
||||
@@ -156,7 +226,10 @@ func NewProvider() (*provider, error) {
|
||||
}
|
||||
}
|
||||
|
||||
otpCollection, _ := arangodb.Collection(nil, models.Collections.OTP)
|
||||
otpCollection, err := arangodb.Collection(ctx, models.Collections.OTP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
otpCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
|
||||
Unique: true,
|
||||
Sparse: true,
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
arangoDriver "github.com/arangodb/go-driver"
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/authorizerdev/authorizer/server/refs"
|
||||
)
|
||||
|
||||
// AddUser to save user information in database
|
||||
@@ -32,6 +32,12 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
|
||||
user.Roles = defaultRoles
|
||||
}
|
||||
|
||||
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
|
||||
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID {
|
||||
return user, fmt.Errorf("user with given phone number already exists")
|
||||
}
|
||||
}
|
||||
|
||||
user.CreatedAt = time.Now().Unix()
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
userCollection, _ := p.db.Collection(ctx, models.Collections.User)
|
||||
@@ -48,6 +54,7 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
|
||||
// UpdateUser to update user information in database
|
||||
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
|
||||
collection, _ := p.db.Collection(ctx, models.Collections.User)
|
||||
meta, err := collection.UpdateDocument(ctx, user.Key, user)
|
||||
if err != nil {
|
||||
@@ -83,7 +90,7 @@ func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||
// ListUsers to get list of users from database
|
||||
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
|
||||
var users []*model.User
|
||||
sctx := driver.WithQueryFullCount(ctx)
|
||||
sctx := arangoDriver.WithQueryFullCount(ctx)
|
||||
|
||||
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.User, pagination.Offset, pagination.Limit)
|
||||
|
||||
@@ -191,7 +198,7 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
|
||||
}
|
||||
|
||||
query := ""
|
||||
if ids != nil && len(ids) > 0 {
|
||||
if len(ids) > 0 {
|
||||
keysArray := ""
|
||||
for _, id := range ids {
|
||||
keysArray += fmt.Sprintf("'%s', ", id)
|
||||
@@ -204,10 +211,40 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
|
||||
}
|
||||
|
||||
_, err = p.db.Query(ctx, query, nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
|
||||
var user models.User
|
||||
|
||||
query := fmt.Sprintf("FOR d in %s FILTER d.phone_number == @phone_number RETURN d", models.Collections.User)
|
||||
bindVars := map[string]interface{}{
|
||||
"phone_number": phoneNumber,
|
||||
}
|
||||
|
||||
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close()
|
||||
|
||||
for {
|
||||
if !cursor.HasMore() {
|
||||
if user.Key == "" {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
break
|
||||
}
|
||||
_, err := cursor.ReadDocument(ctx, &user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
arangoDriver "github.com/arangodb/go-driver"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/google/uuid"
|
||||
@@ -96,7 +96,7 @@ func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email stri
|
||||
// ListVerificationRequests to get list of verification requests from database
|
||||
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||
var verificationRequests []*model.VerificationRequest
|
||||
sctx := driver.WithQueryFullCount(ctx)
|
||||
sctx := arangoDriver.WithQueryFullCount(ctx)
|
||||
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.VerificationRequest, pagination.Offset, pagination.Limit)
|
||||
|
||||
cursor, err := p.db.Query(sctx, query, nil)
|
||||
@@ -112,7 +112,7 @@ func (p *provider) ListVerificationRequests(ctx context.Context, pagination mode
|
||||
var verificationRequest models.VerificationRequest
|
||||
meta, err := cursor.ReadDocument(ctx, &verificationRequest)
|
||||
|
||||
if driver.IsNoMoreDocuments(err) {
|
||||
if arangoDriver.IsNoMoreDocuments(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
@@ -132,8 +132,8 @@ func (p *provider) ListVerificationRequests(ctx context.Context, pagination mode
|
||||
|
||||
// DeleteVerificationRequest to delete verification request from database
|
||||
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
|
||||
collection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
|
||||
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
|
||||
collection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest)
|
||||
_, err := collection.RemoveDocument(ctx, verificationRequest.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
arangoDriver "github.com/arangodb/go-driver"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
@@ -50,7 +49,7 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
|
||||
|
||||
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.Webhook, pagination.Offset, pagination.Limit)
|
||||
|
||||
sctx := driver.WithQueryFullCount(ctx)
|
||||
sctx := arangoDriver.WithQueryFullCount(ctx)
|
||||
cursor, err := p.db.Query(sctx, query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
arangoDriver "github.com/arangodb/go-driver"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
@@ -44,7 +43,7 @@ func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Paginat
|
||||
}
|
||||
}
|
||||
|
||||
sctx := driver.WithQueryFullCount(ctx)
|
||||
sctx := arangoDriver.WithQueryFullCount(ctx)
|
||||
cursor, err := p.db.Query(sctx, query, bindVariables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -161,6 +161,12 @@ func NewProvider() (*provider, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userPhoneNumberIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_user_phone_number ON %s.%s (phone_number)", KeySpace, models.Collections.User)
|
||||
err = session.Query(userPhoneNumberIndexQuery).Exec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// add is_multi_factor_auth_enabled on users table
|
||||
userTableAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD is_multi_factor_auth_enabled boolean`, KeySpace, models.Collections.User)
|
||||
err = session.Query(userTableAlterQuery).Exec()
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/authorizerdev/authorizer/server/refs"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -30,6 +31,12 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
|
||||
user.Roles = defaultRoles
|
||||
}
|
||||
|
||||
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
|
||||
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID {
|
||||
return user, fmt.Errorf("user with given phone number already exists")
|
||||
}
|
||||
}
|
||||
|
||||
user.CreatedAt = time.Now().Unix()
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
|
||||
@@ -299,3 +306,14 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
|
||||
var user models.User
|
||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE phone_number = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, phoneNumber)
|
||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
166
server/db/providers/couchbase/email_template.go
Normal file
166
server/db/providers/couchbase/email_template.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddEmailTemplate to add EmailTemplate
|
||||
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||
|
||||
if emailTemplate.ID == "" {
|
||||
emailTemplate.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
emailTemplate.Key = emailTemplate.ID
|
||||
emailTemplate.CreatedAt = time.Now().Unix()
|
||||
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
_, err := p.db.Collection(models.Collections.EmailTemplate).Insert(emailTemplate.ID, emailTemplate, &insertOpt)
|
||||
if err != nil {
|
||||
return emailTemplate.AsAPIEmailTemplate(), err
|
||||
}
|
||||
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
}
|
||||
|
||||
// UpdateEmailTemplate to update EmailTemplate
|
||||
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||
bytes, err := json.Marshal(emailTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
|
||||
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
|
||||
decoder.UseNumber()
|
||||
emailTemplateMap := map[string]interface{}{}
|
||||
err = decoder.Decode(&emailTemplateMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateFields, params := GetSetFields(emailTemplateMap)
|
||||
params["emailId"] = emailTemplate.ID
|
||||
|
||||
query := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id = $emailId", p.scopeName, models.Collections.EmailTemplate, updateFields)
|
||||
|
||||
_, err = p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
NamedParameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
}
|
||||
|
||||
// ListEmailTemplates to list EmailTemplate
|
||||
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||
emailTemplates := []*model.EmailTemplate{}
|
||||
paginationClone := pagination
|
||||
total, err := p.GetTotalDocs(ctx, models.Collections.EmailTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paginationClone.Total = total
|
||||
userQuery := fmt.Sprintf("SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s ORDER BY _id OFFSET $1 LIMIT $2", p.scopeName, models.Collections.EmailTemplate)
|
||||
|
||||
queryResult, err := p.db.Query(userQuery, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for queryResult.Next() {
|
||||
emailTemplate := models.EmailTemplate{}
|
||||
err := queryResult.Row(&emailTemplate)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||
}
|
||||
|
||||
if err := queryResult.Err(); err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
return &model.EmailTemplates{
|
||||
Pagination: &paginationClone,
|
||||
EmailTemplates: emailTemplates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetEmailTemplateByID to get EmailTemplate by id
|
||||
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||
emailTemplate := models.EmailTemplate{}
|
||||
|
||||
query := fmt.Sprintf(`SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1`, p.scopeName, models.Collections.EmailTemplate)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
PositionalParameters: []interface{}{emailTemplateID},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = q.One(&emailTemplate)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
}
|
||||
|
||||
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||
emailTemplate := models.EmailTemplate{}
|
||||
|
||||
query := fmt.Sprintf("SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s WHERE event_name=$1 LIMIT 1", p.scopeName, models.Collections.EmailTemplate)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
PositionalParameters: []interface{}{eventName},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = q.One(&emailTemplate)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
}
|
||||
|
||||
// DeleteEmailTemplate to delete EmailTemplate
|
||||
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||
removeOpt := gocb.RemoveOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.EmailTemplate).Remove(emailTemplate.ID, &removeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
70
server/db/providers/couchbase/env.go
Normal file
70
server/db/providers/couchbase/env.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddEnv to save environment information in database
|
||||
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||
if env.ID == "" {
|
||||
env.ID = uuid.New().String()
|
||||
}
|
||||
env.CreatedAt = time.Now().Unix()
|
||||
env.UpdatedAt = time.Now().Unix()
|
||||
env.Key = env.ID
|
||||
env.EncryptionKey = env.Hash
|
||||
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.Env).Insert(env.ID, env, &insertOpt)
|
||||
if err != nil {
|
||||
return env, err
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// UpdateEnv to update environment information in database
|
||||
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||
env.UpdatedAt = time.Now().Unix()
|
||||
env.EncryptionKey = env.Hash
|
||||
|
||||
updateEnvQuery := fmt.Sprintf("UPDATE %s.%s SET env = $1, updated_at = $2 WHERE _id = $3", p.scopeName, models.Collections.Env)
|
||||
_, err := p.db.Query(updateEnvQuery, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
PositionalParameters: []interface{}{env.EnvData, env.UpdatedAt, env.UpdatedAt, env.ID},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return env, err
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// GetEnv to get environment information from database
|
||||
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
|
||||
var env models.Env
|
||||
|
||||
query := fmt.Sprintf("SELECT _id, env, encryption_key, created_at, updated_at FROM %s.%s LIMIT 1", p.scopeName, models.Collections.Env)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
})
|
||||
if err != nil {
|
||||
return env, err
|
||||
}
|
||||
err = q.One(&env)
|
||||
|
||||
if err != nil {
|
||||
return env, err
|
||||
}
|
||||
env.Hash = env.EncryptionKey
|
||||
return env, nil
|
||||
}
|
84
server/db/providers/couchbase/otp.go
Normal file
84
server/db/providers/couchbase/otp.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// UpsertOTP to add or update otp
|
||||
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
|
||||
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email)
|
||||
|
||||
shouldCreate := false
|
||||
if otp == nil {
|
||||
shouldCreate = true
|
||||
otp = &models.OTP{
|
||||
ID: uuid.NewString(),
|
||||
Otp: otpParam.Otp,
|
||||
Email: otpParam.Email,
|
||||
ExpiresAt: otpParam.ExpiresAt,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
}
|
||||
} else {
|
||||
otp.Otp = otpParam.Otp
|
||||
otp.ExpiresAt = otpParam.ExpiresAt
|
||||
}
|
||||
|
||||
otp.UpdatedAt = time.Now().Unix()
|
||||
if shouldCreate {
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.OTP).Insert(otp.ID, otp, &insertOpt)
|
||||
if err != nil {
|
||||
return otp, err
|
||||
}
|
||||
} else {
|
||||
query := fmt.Sprintf(`UPDATE %s.%s SET otp=$1, expires_at=$2, updated_at=$3 WHERE _id=$4`, p.scopeName, models.Collections.OTP)
|
||||
_, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
PositionalParameters: []interface{}{otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID},
|
||||
})
|
||||
if err != nil {
|
||||
return otp, err
|
||||
}
|
||||
}
|
||||
return otp, nil
|
||||
}
|
||||
|
||||
// GetOTPByEmail to get otp for a given email address
|
||||
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
|
||||
otp := models.OTP{}
|
||||
query := fmt.Sprintf(`SELECT _id, email, otp, expires_at, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1`, p.scopeName, models.Collections.OTP)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
PositionalParameters: []interface{}{emailAddress},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = q.One(&otp)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &otp, nil
|
||||
}
|
||||
|
||||
// DeleteOTP to delete otp
|
||||
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
|
||||
removeOpt := gocb.RemoveOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.OTP).Remove(otp.ID, &removeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
170
server/db/providers/couchbase/provider.go
Normal file
170
server/db/providers/couchbase/provider.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/couchbase/gocb/v2"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBucketName = "authorizer"
|
||||
defaultScope = "_default"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
db *gocb.Scope
|
||||
scopeName string
|
||||
}
|
||||
|
||||
// NewProvider returns a new Couchbase provider
|
||||
func NewProvider() (*provider, error) {
|
||||
bucketName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().CouchbaseBucket
|
||||
scopeName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().CouchbaseScope
|
||||
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
|
||||
userName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseUsername
|
||||
password := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePassword
|
||||
opts := gocb.ClusterOptions{
|
||||
Username: userName,
|
||||
Password: password,
|
||||
}
|
||||
if bucketName == "" {
|
||||
bucketName = defaultBucketName
|
||||
}
|
||||
if scopeName == "" {
|
||||
scopeName = defaultScope
|
||||
}
|
||||
cluster, err := gocb.Connect(dbURL, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// To create the bucket and scope if not exist
|
||||
bucket, err := CreateBucketAndScope(cluster, bucketName, scopeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scope := bucket.Scope(scopeName)
|
||||
scopeIdentifier := fmt.Sprintf("%s.%s", bucketName, scopeName)
|
||||
v := reflect.ValueOf(models.Collections)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
collectionName := v.Field(i)
|
||||
user := gocb.CollectionSpec{
|
||||
Name: collectionName.String(),
|
||||
ScopeName: scopeName,
|
||||
}
|
||||
collectionOpts := gocb.CreateCollectionOptions{
|
||||
Context: context.TODO(),
|
||||
}
|
||||
err = bucket.Collections().CreateCollection(user, &collectionOpts)
|
||||
if err != nil && !errors.Is(err, gocb.ErrCollectionExists) {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: find how to fix this sleep time.
|
||||
// Add wait time for successful collection creation
|
||||
time.Sleep(5 * time.Second)
|
||||
indexQuery := fmt.Sprintf("CREATE PRIMARY INDEX ON %s.%s", scopeIdentifier, collectionName.String())
|
||||
_, err = scope.Query(indexQuery, nil)
|
||||
if err != nil && !strings.Contains(err.Error(), "The index #primary already exists") {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
indices := GetIndex(scopeIdentifier)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
for _, indexQuery := range indices[field.String()] {
|
||||
scope.Query(indexQuery, nil)
|
||||
}
|
||||
}
|
||||
return &provider{
|
||||
db: scope,
|
||||
scopeName: scopeIdentifier,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CreateBucketAndScope(cluster *gocb.Cluster, bucketName string, scopeName string) (*gocb.Bucket, error) {
|
||||
bucketRAMQuotaMB := memorystore.RequiredEnvStoreObj.GetRequiredEnv().CouchbaseBucketRAMQuotaMB
|
||||
if bucketRAMQuotaMB == "" {
|
||||
bucketRAMQuotaMB = "1000"
|
||||
}
|
||||
bucketRAMQuota, err := strconv.ParseInt(bucketRAMQuotaMB, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings := gocb.BucketSettings{
|
||||
Name: bucketName,
|
||||
RAMQuotaMB: uint64(bucketRAMQuota),
|
||||
BucketType: gocb.CouchbaseBucketType,
|
||||
EvictionPolicy: gocb.EvictionPolicyTypeValueOnly,
|
||||
FlushEnabled: true,
|
||||
CompressionMode: gocb.CompressionModeActive,
|
||||
}
|
||||
shouldCreateBucket := false
|
||||
// check if bucket exists
|
||||
_, err = cluster.Buckets().GetBucket(bucketName, nil)
|
||||
if err != nil {
|
||||
// bucket not found
|
||||
shouldCreateBucket = true
|
||||
}
|
||||
if shouldCreateBucket {
|
||||
err = cluster.Buckets().CreateBucket(gocb.CreateBucketSettings{
|
||||
BucketSettings: settings,
|
||||
ConflictResolutionType: gocb.ConflictResolutionTypeSequenceNumber,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
bucket := cluster.Bucket(bucketName)
|
||||
if scopeName != defaultScope {
|
||||
err = bucket.Collections().CreateScope(scopeName, nil)
|
||||
if err != nil && !errors.Is(err, gocb.ErrScopeExists) {
|
||||
return bucket, err
|
||||
}
|
||||
}
|
||||
return bucket, nil
|
||||
}
|
||||
|
||||
func GetIndex(scopeName string) map[string][]string {
|
||||
indices := make(map[string][]string)
|
||||
|
||||
// User Index
|
||||
userIndex1 := fmt.Sprintf("CREATE INDEX userEmailIndex ON %s.%s(email)", scopeName, models.Collections.User)
|
||||
userIndex2 := fmt.Sprintf("CREATE INDEX userPhoneIndex ON %s.%s(phone_number)", scopeName, models.Collections.User)
|
||||
indices[models.Collections.User] = []string{userIndex1, userIndex2}
|
||||
|
||||
// VerificationRequest
|
||||
verificationIndex1 := fmt.Sprintf("CREATE INDEX verificationRequestTokenIndex ON %s.%s(token)", scopeName, models.Collections.VerificationRequest)
|
||||
verificationIndex2 := fmt.Sprintf("CREATE INDEX verificationRequestEmailAndIdentifierIndex ON %s.%s(email,identifier)", scopeName, models.Collections.VerificationRequest)
|
||||
indices[models.Collections.VerificationRequest] = []string{verificationIndex1, verificationIndex2}
|
||||
|
||||
// Session index
|
||||
sessionIndex1 := fmt.Sprintf("CREATE INDEX SessionUserIdIndex ON %s.%s(user_id)", scopeName, models.Collections.Session)
|
||||
indices[models.Collections.Session] = []string{sessionIndex1}
|
||||
|
||||
// Webhook index
|
||||
webhookIndex1 := fmt.Sprintf("CREATE INDEX webhookEventNameIndex ON %s.%s(event_name)", scopeName, models.Collections.Webhook)
|
||||
indices[models.Collections.Webhook] = []string{webhookIndex1}
|
||||
|
||||
// WebhookLog index
|
||||
webhookLogIndex1 := fmt.Sprintf("CREATE INDEX webhookLogIdIndex ON %s.%s(webhook_id)", scopeName, models.Collections.WebhookLog)
|
||||
indices[models.Collections.Webhook] = []string{webhookLogIndex1}
|
||||
|
||||
// WebhookLog index
|
||||
emailTempIndex1 := fmt.Sprintf("CREATE INDEX EmailTemplateEventNameIndex ON %s.%s(event_name)", scopeName, models.Collections.EmailTemplate)
|
||||
indices[models.Collections.EmailTemplate] = []string{emailTempIndex1}
|
||||
|
||||
// OTP index
|
||||
otpIndex1 := fmt.Sprintf("CREATE INDEX OTPEmailIndex ON %s.%s(email)", scopeName, models.Collections.OTP)
|
||||
indices[models.Collections.OTP] = []string{otpIndex1}
|
||||
|
||||
return indices
|
||||
}
|
34
server/db/providers/couchbase/session.go
Normal file
34
server/db/providers/couchbase/session.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddSession to save session information in database
|
||||
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
|
||||
if session.ID == "" {
|
||||
session.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
session.CreatedAt = time.Now().Unix()
|
||||
session.UpdatedAt = time.Now().Unix()
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.Session).Insert(session.ID, session, &insertOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSession to delete session information from database
|
||||
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
|
||||
return nil
|
||||
}
|
65
server/db/providers/couchbase/shared.go
Normal file
65
server/db/providers/couchbase/shared.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/couchbase/gocb/v2"
|
||||
)
|
||||
|
||||
func GetSetFields(webhookMap map[string]interface{}) (string, map[string]interface{}) {
|
||||
params := make(map[string]interface{}, 1)
|
||||
|
||||
updateFields := ""
|
||||
|
||||
for key, value := range webhookMap {
|
||||
if key == "_id" {
|
||||
continue
|
||||
}
|
||||
|
||||
if key == "_key" {
|
||||
continue
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
updateFields += fmt.Sprintf("%s=$%s,", key, key)
|
||||
params[key] = "null"
|
||||
continue
|
||||
}
|
||||
|
||||
valueType := reflect.TypeOf(value)
|
||||
if valueType.Name() == "string" {
|
||||
updateFields += fmt.Sprintf("%s = $%s, ", key, key)
|
||||
params[key] = value.(string)
|
||||
|
||||
} else {
|
||||
updateFields += fmt.Sprintf("%s = $%s, ", key, key)
|
||||
params[key] = value
|
||||
}
|
||||
}
|
||||
updateFields = strings.Trim(updateFields, " ")
|
||||
updateFields = strings.TrimSuffix(updateFields, ",")
|
||||
return updateFields, params
|
||||
}
|
||||
|
||||
func (p *provider) GetTotalDocs(ctx context.Context, collection string) (int64, error) {
|
||||
totalDocs := TotalDocs{}
|
||||
|
||||
countQuery := fmt.Sprintf("SELECT COUNT(*) as Total FROM %s.%s", p.scopeName, collection)
|
||||
queryRes, err := p.db.Query(countQuery, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
})
|
||||
|
||||
queryRes.One(&totalDocs)
|
||||
|
||||
if err != nil {
|
||||
return totalDocs.Total, err
|
||||
}
|
||||
return totalDocs.Total, nil
|
||||
}
|
||||
|
||||
type TotalDocs struct {
|
||||
Total int64
|
||||
}
|
199
server/db/providers/couchbase/user.go
Normal file
199
server/db/providers/couchbase/user.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddUser to save user information in database
|
||||
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
|
||||
if user.ID == "" {
|
||||
user.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
if user.Roles == "" {
|
||||
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
user.Roles = defaultRoles
|
||||
}
|
||||
|
||||
user.CreatedAt = time.Now().Unix()
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.User).Insert(user.ID, user, &insertOpt)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUser to update user information in database
|
||||
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
unsertOpt := gocb.UpsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.User).Upsert(user.ID, user, &unsertOpt)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// DeleteUser to delete user information from database
|
||||
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||
removeOpt := gocb.RemoveOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.User).Remove(user.ID, &removeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUsers to get list of users from database
|
||||
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
|
||||
users := []*model.User{}
|
||||
paginationClone := pagination
|
||||
|
||||
userQuery := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s ORDER BY id OFFSET $1 LIMIT $2", p.scopeName, models.Collections.User)
|
||||
|
||||
queryResult, err := p.db.Query(userQuery, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total, err := p.GetTotalDocs(ctx, models.Collections.User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paginationClone.Total = total
|
||||
for queryResult.Next() {
|
||||
var user models.User
|
||||
err := queryResult.Row(&user)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
users = append(users, user.AsAPIUser())
|
||||
}
|
||||
if err := queryResult.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Users{
|
||||
Pagination: &paginationClone,
|
||||
Users: users,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail to get user information from database using email address
|
||||
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
|
||||
user := models.User{}
|
||||
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.User)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
PositionalParameters: []interface{}{email},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
err = q.One(&user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetUserByID to get user information from database using user ID
|
||||
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
|
||||
user := models.User{}
|
||||
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.User)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
PositionalParameters: []interface{}{id},
|
||||
})
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
err = q.One(&user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUsers to update multiple users, with parameters of user IDs slice
|
||||
// If ids set to nil / empty all the users will be updated
|
||||
func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error {
|
||||
// set updated_at time for all users
|
||||
data["updated_at"] = time.Now().Unix()
|
||||
updateFields, params := GetSetFields(data)
|
||||
if len(ids) > 0 {
|
||||
for _, id := range ids {
|
||||
params["id"] = id
|
||||
userQuery := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id = $id", p.scopeName, models.Collections.User, updateFields)
|
||||
|
||||
_, err := p.db.Query(userQuery, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
NamedParameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userQuery := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id IS NOT NULL", p.scopeName, models.Collections.User, updateFields)
|
||||
_, err := p.db.Query(userQuery, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
NamedParameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
|
||||
var user *models.User
|
||||
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE phone_number = $1 LIMIT 1", p.scopeName, models.Collections.User)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
PositionalParameters: []interface{}{phoneNumber},
|
||||
})
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
err = q.One(&user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
128
server/db/providers/couchbase/verification_requests.go
Normal file
128
server/db/providers/couchbase/verification_requests.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddVerification to save verification request in database
|
||||
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
|
||||
if verificationRequest.ID == "" {
|
||||
verificationRequest.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
verificationRequest.Key = verificationRequest.ID
|
||||
verificationRequest.CreatedAt = time.Now().Unix()
|
||||
verificationRequest.UpdatedAt = time.Now().Unix()
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.VerificationRequest).Insert(verificationRequest.ID, verificationRequest, &insertOpt)
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
// GetVerificationRequestByToken to get verification request from database using token
|
||||
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
|
||||
verificationRequest := models.VerificationRequest{}
|
||||
params := make(map[string]interface{}, 1)
|
||||
params["token"] = token
|
||||
query := fmt.Sprintf("SELECT _id, token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s.%s WHERE token=$1 LIMIT 1", p.scopeName, models.Collections.VerificationRequest)
|
||||
|
||||
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
PositionalParameters: []interface{}{token},
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
err = queryResult.One(&verificationRequest)
|
||||
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
// GetVerificationRequestByEmail to get verification request by email from database
|
||||
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
|
||||
|
||||
query := fmt.Sprintf("SELECT _id, identifier, token, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s.%s WHERE email=$1 AND identifier=$2 LIMIT 1", p.scopeName, models.Collections.VerificationRequest)
|
||||
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
PositionalParameters: []interface{}{email, identifier},
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
})
|
||||
verificationRequest := models.VerificationRequest{}
|
||||
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
|
||||
err = queryResult.One(&verificationRequest)
|
||||
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
// ListVerificationRequests to get list of verification requests from database
|
||||
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||
var verificationRequests []*model.VerificationRequest
|
||||
paginationClone := pagination
|
||||
total, err := p.GetTotalDocs(ctx, models.Collections.VerificationRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paginationClone.Total = total
|
||||
query := fmt.Sprintf("SELECT _id, env, created_at, updated_at FROM %s.%s OFFSET $1 LIMIT $2", p.scopeName, models.Collections.VerificationRequest)
|
||||
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for queryResult.Next() {
|
||||
var verificationRequest models.VerificationRequest
|
||||
err := queryResult.Row(&verificationRequest)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest())
|
||||
}
|
||||
if err := queryResult.Err(); err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
return &model.VerificationRequests{
|
||||
VerificationRequests: verificationRequests,
|
||||
Pagination: &paginationClone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteVerificationRequest to delete verification request from database
|
||||
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
|
||||
removeOpt := gocb.RemoveOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.VerificationRequest).Remove(verificationRequest.ID, &removeOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
185
server/db/providers/couchbase/webhook.go
Normal file
185
server/db/providers/couchbase/webhook.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddWebhook to add webhook
|
||||
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||
if webhook.ID == "" {
|
||||
webhook.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
webhook.Key = webhook.ID
|
||||
webhook.CreatedAt = time.Now().Unix()
|
||||
webhook.UpdatedAt = time.Now().Unix()
|
||||
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.Webhook).Insert(webhook.ID, webhook, &insertOpt)
|
||||
if err != nil {
|
||||
return webhook.AsAPIWebhook(), err
|
||||
}
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// UpdateWebhook to update webhook
|
||||
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||
webhook.UpdatedAt = time.Now().Unix()
|
||||
|
||||
bytes, err := json.Marshal(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
|
||||
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
|
||||
decoder.UseNumber()
|
||||
webhookMap := map[string]interface{}{}
|
||||
err = decoder.Decode(&webhookMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateFields, params := GetSetFields(webhookMap)
|
||||
|
||||
query := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE _id='%s'`, p.scopeName, models.Collections.Webhook, updateFields, webhook.ID)
|
||||
|
||||
_, err = p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
NamedParameters: params,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// ListWebhooks to list webhook
|
||||
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
|
||||
webhooks := []*model.Webhook{}
|
||||
paginationClone := pagination
|
||||
|
||||
params := make(map[string]interface{}, 1)
|
||||
params["offset"] = paginationClone.Offset
|
||||
params["limit"] = paginationClone.Limit
|
||||
total, err := p.GetTotalDocs(ctx, models.Collections.Webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paginationClone.Total = total
|
||||
query := fmt.Sprintf("SELECT _id, env, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.Webhook)
|
||||
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
NamedParameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for queryResult.Next() {
|
||||
var webhook models.Webhook
|
||||
err := queryResult.Row(&webhook)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
webhooks = append(webhooks, webhook.AsAPIWebhook())
|
||||
}
|
||||
if err := queryResult.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Webhooks{
|
||||
Pagination: &paginationClone,
|
||||
Webhooks: webhooks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWebhookByID to get webhook by id
|
||||
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
||||
var webhook models.Webhook
|
||||
|
||||
params := make(map[string]interface{}, 1)
|
||||
params["_id"] = webhookID
|
||||
|
||||
query := fmt.Sprintf(`SELECT _id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE _id=$_id LIMIT 1`, p.scopeName, models.Collections.Webhook)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
NamedParameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = q.One(&webhook)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// GetWebhookByEventName to get webhook by event_name
|
||||
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
||||
var webhook models.Webhook
|
||||
params := make(map[string]interface{}, 1)
|
||||
params["event_name"] = eventName
|
||||
|
||||
query := fmt.Sprintf(`SELECT _id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE event_name=$event_name LIMIT 1`, p.scopeName, models.Collections.Webhook)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
NamedParameters: params,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = q.One(&webhook)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// DeleteWebhook to delete webhook
|
||||
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
|
||||
|
||||
params := make(map[string]interface{}, 1)
|
||||
params["webhook_id"] = webhook.ID
|
||||
removeOpt := gocb.RemoveOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.Webhook).Remove(webhook.ID, &removeOpt)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`DELETE FROM %s.%s WHERE webhook_id=$webhook_id`, p.scopeName, models.Collections.WebhookLog)
|
||||
_, err = p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
NamedParameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
85
server/db/providers/couchbase/webhook_log.go
Normal file
85
server/db/providers/couchbase/webhook_log.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/couchbase/gocb/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddWebhookLog to add webhook log
|
||||
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
||||
if webhookLog.ID == "" {
|
||||
webhookLog.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
webhookLog.Key = webhookLog.ID
|
||||
webhookLog.CreatedAt = time.Now().Unix()
|
||||
webhookLog.UpdatedAt = time.Now().Unix()
|
||||
|
||||
insertOpt := gocb.InsertOptions{
|
||||
Context: ctx,
|
||||
}
|
||||
_, err := p.db.Collection(models.Collections.WebhookLog).Insert(webhookLog.ID, webhookLog, &insertOpt)
|
||||
if err != nil {
|
||||
return webhookLog.AsAPIWebhookLog(), err
|
||||
}
|
||||
|
||||
return webhookLog.AsAPIWebhookLog(), nil
|
||||
}
|
||||
|
||||
// ListWebhookLogs to list webhook logs
|
||||
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
|
||||
var query string
|
||||
var err error
|
||||
|
||||
webhookLogs := []*model.WebhookLog{}
|
||||
params := make(map[string]interface{}, 1)
|
||||
paginationClone := pagination
|
||||
|
||||
params["webhookID"] = webhookID
|
||||
params["offset"] = paginationClone.Offset
|
||||
params["limit"] = paginationClone.Limit
|
||||
total, err := p.GetTotalDocs(ctx, models.Collections.WebhookLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paginationClone.Total = total
|
||||
if webhookID != "" {
|
||||
query = fmt.Sprintf(`SELECT _id, http_status, response, request, webhook_id, created_at, updated_at FROM %s.%s WHERE webhook_id=$webhookID`, p.scopeName, models.Collections.WebhookLog)
|
||||
} else {
|
||||
query = fmt.Sprintf("SELECT _id, http_status, response, request, webhook_id, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.WebhookLog)
|
||||
}
|
||||
|
||||
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
Context: ctx,
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
NamedParameters: params,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for queryResult.Next() {
|
||||
var webhookLog models.WebhookLog
|
||||
err := queryResult.Row(&webhookLog)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
|
||||
}
|
||||
|
||||
if err := queryResult.Err(); err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
return &model.WebhookLogs{
|
||||
Pagination: &paginationClone,
|
||||
WebhookLogs: webhookLogs,
|
||||
}, nil
|
||||
}
|
121
server/db/providers/dynamodb/email_template.go
Normal file
121
server/db/providers/dynamodb/email_template.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/google/uuid"
|
||||
"github.com/guregu/dynamo"
|
||||
)
|
||||
|
||||
// AddEmailTemplate to add EmailTemplate
|
||||
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||
collection := p.db.Table(models.Collections.EmailTemplate)
|
||||
if emailTemplate.ID == "" {
|
||||
emailTemplate.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
emailTemplate.Key = emailTemplate.ID
|
||||
emailTemplate.CreatedAt = time.Now().Unix()
|
||||
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||
err := collection.Put(emailTemplate).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return emailTemplate.AsAPIEmailTemplate(), err
|
||||
}
|
||||
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
}
|
||||
|
||||
// UpdateEmailTemplate to update EmailTemplate
|
||||
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||
collection := p.db.Table(models.Collections.EmailTemplate)
|
||||
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||
err := UpdateByHashKey(collection, "id", emailTemplate.ID, emailTemplate)
|
||||
if err != nil {
|
||||
return emailTemplate.AsAPIEmailTemplate(), err
|
||||
}
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
}
|
||||
|
||||
// ListEmailTemplates to list EmailTemplate
|
||||
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||
|
||||
var emailTemplate models.EmailTemplate
|
||||
var iter dynamo.PagingIter
|
||||
var lastEval dynamo.PagingKey
|
||||
var iteration int64 = 0
|
||||
|
||||
collection := p.db.Table(models.Collections.EmailTemplate)
|
||||
emailTemplates := []*model.EmailTemplate{}
|
||||
paginationClone := pagination
|
||||
scanner := collection.Scan()
|
||||
count, err := scanner.Count()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for (paginationClone.Offset + paginationClone.Limit) > iteration {
|
||||
iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter()
|
||||
for iter.NextWithContext(ctx, &emailTemplate) {
|
||||
if paginationClone.Offset == iteration {
|
||||
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||
}
|
||||
}
|
||||
lastEval = iter.LastEvaluatedKey()
|
||||
iteration += paginationClone.Limit
|
||||
}
|
||||
|
||||
paginationClone.Total = count
|
||||
|
||||
return &model.EmailTemplates{
|
||||
Pagination: &paginationClone,
|
||||
EmailTemplates: emailTemplates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetEmailTemplateByID to get EmailTemplate by id
|
||||
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||
collection := p.db.Table(models.Collections.EmailTemplate)
|
||||
var emailTemplate models.EmailTemplate
|
||||
err := collection.Get("id", emailTemplateID).OneWithContext(ctx, &emailTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
}
|
||||
|
||||
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||
collection := p.db.Table(models.Collections.EmailTemplate)
|
||||
var emailTemplates []models.EmailTemplate
|
||||
var emailTemplate models.EmailTemplate
|
||||
|
||||
err := collection.Scan().Index("event_name").Filter("'event_name' = ?", eventName).Limit(1).AllWithContext(ctx, &emailTemplates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(emailTemplates) > 0 {
|
||||
emailTemplate = emailTemplates[0]
|
||||
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||
} else {
|
||||
return nil, errors.New("no record found")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// DeleteEmailTemplate to delete EmailTemplate
|
||||
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||
collection := p.db.Table(models.Collections.EmailTemplate)
|
||||
err := collection.Delete("id", emailTemplate.ID).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
71
server/db/providers/dynamodb/env.go
Normal file
71
server/db/providers/dynamodb/env.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddEnv to save environment information in database
|
||||
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||
collection := p.db.Table(models.Collections.Env)
|
||||
|
||||
if env.ID == "" {
|
||||
env.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
env.Key = env.ID
|
||||
|
||||
env.CreatedAt = time.Now().Unix()
|
||||
env.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := collection.Put(env).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return env, err
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// UpdateEnv to update environment information in database
|
||||
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||
collection := p.db.Table(models.Collections.Env)
|
||||
env.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := UpdateByHashKey(collection, "id", env.ID, env)
|
||||
|
||||
if err != nil {
|
||||
return env, err
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// GetEnv to get environment information from database
|
||||
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
|
||||
var env models.Env
|
||||
|
||||
collection := p.db.Table(models.Collections.Env)
|
||||
// As there is no Findone supported.
|
||||
iter := collection.Scan().Limit(1).Iter()
|
||||
|
||||
for iter.NextWithContext(ctx, &env) {
|
||||
if env.ID == "" {
|
||||
return env, errors.New("no documets found")
|
||||
} else {
|
||||
return env, nil
|
||||
}
|
||||
}
|
||||
|
||||
err := iter.Err()
|
||||
|
||||
if err != nil {
|
||||
return env, fmt.Errorf("config not found")
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
80
server/db/providers/dynamodb/otp.go
Normal file
80
server/db/providers/dynamodb/otp.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// UpsertOTP to add or update otp
|
||||
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
|
||||
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email)
|
||||
shouldCreate := false
|
||||
if otp == nil {
|
||||
id := uuid.NewString()
|
||||
otp = &models.OTP{
|
||||
ID: id,
|
||||
Key: id,
|
||||
Otp: otpParam.Otp,
|
||||
Email: otpParam.Email,
|
||||
ExpiresAt: otpParam.ExpiresAt,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
shouldCreate = true
|
||||
} else {
|
||||
otp.Otp = otpParam.Otp
|
||||
otp.ExpiresAt = otpParam.ExpiresAt
|
||||
}
|
||||
|
||||
collection := p.db.Table(models.Collections.OTP)
|
||||
otp.UpdatedAt = time.Now().Unix()
|
||||
|
||||
var err error
|
||||
if shouldCreate {
|
||||
err = collection.Put(otp).RunWithContext(ctx)
|
||||
} else {
|
||||
err = UpdateByHashKey(collection, "id", otp.ID, otp)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return otp, nil
|
||||
}
|
||||
|
||||
// GetOTPByEmail to get otp for a given email address
|
||||
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
|
||||
var otps []models.OTP
|
||||
var otp models.OTP
|
||||
|
||||
collection := p.db.Table(models.Collections.OTP)
|
||||
|
||||
err := collection.Scan().Index("email").Filter("'email' = ?", emailAddress).Limit(1).AllWithContext(ctx, &otps)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(otps) > 0 {
|
||||
otp = otps[0]
|
||||
return &otp, nil
|
||||
} else {
|
||||
return nil, errors.New("no docuemnt found")
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteOTP to delete otp
|
||||
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
|
||||
collection := p.db.Table(models.Collections.OTP)
|
||||
|
||||
if otp.ID != "" {
|
||||
err := collection.Delete("id", otp.ID).RunWithContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
61
server/db/providers/dynamodb/provider.go
Normal file
61
server/db/providers/dynamodb/provider.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/guregu/dynamo"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
db *dynamo.DB
|
||||
}
|
||||
|
||||
// NewProvider returns a new Dynamo provider
|
||||
func NewProvider() (*provider, error) {
|
||||
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
|
||||
awsRegion := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AwsRegion
|
||||
awsAccessKeyID := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AwsAccessKeyID
|
||||
awsSecretAccessKey := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AwsSecretAccessKey
|
||||
|
||||
config := aws.Config{
|
||||
MaxRetries: aws.Int(3),
|
||||
CredentialsChainVerboseErrors: aws.Bool(true), // for full error logs
|
||||
}
|
||||
|
||||
if awsRegion != "" {
|
||||
config.Region = aws.String(awsRegion)
|
||||
}
|
||||
|
||||
// custom awsAccessKeyID, awsSecretAccessKey took first priority, if not then fetch config from aws credentials
|
||||
if awsAccessKeyID != "" && awsSecretAccessKey != "" {
|
||||
config.Credentials = credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, "")
|
||||
} else if dbURL != "" {
|
||||
// static config in case of testing or local-setup
|
||||
config.Credentials = credentials.NewStaticCredentials("key", "key", "")
|
||||
config.Endpoint = aws.String(dbURL)
|
||||
} else {
|
||||
log.Debugf("%s or %s or %s not found. Trying to load default credentials from aws config", constants.EnvAwsRegion, constants.EnvAwsAccessKeyID, constants.EnvAwsSecretAccessKey)
|
||||
}
|
||||
|
||||
session := session.Must(session.NewSession(&config))
|
||||
db := dynamo.New(session)
|
||||
|
||||
db.CreateTable(models.Collections.User, models.User{}).Wait()
|
||||
db.CreateTable(models.Collections.Session, models.Session{}).Wait()
|
||||
db.CreateTable(models.Collections.EmailTemplate, models.EmailTemplate{}).Wait()
|
||||
db.CreateTable(models.Collections.Env, models.Env{}).Wait()
|
||||
db.CreateTable(models.Collections.OTP, models.OTP{}).Wait()
|
||||
db.CreateTable(models.Collections.VerificationRequest, models.VerificationRequest{}).Wait()
|
||||
db.CreateTable(models.Collections.Webhook, models.Webhook{}).Wait()
|
||||
db.CreateTable(models.Collections.WebhookLog, models.WebhookLog{}).Wait()
|
||||
|
||||
return &provider{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
28
server/db/providers/dynamodb/session.go
Normal file
28
server/db/providers/dynamodb/session.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddSession to save session information in database
|
||||
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
|
||||
collection := p.db.Table(models.Collections.Session)
|
||||
|
||||
if session.ID == "" {
|
||||
session.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
session.CreatedAt = time.Now().Unix()
|
||||
session.UpdatedAt = time.Now().Unix()
|
||||
err := collection.Put(session).RunWithContext(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteSession to delete session information from database
|
||||
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
|
||||
return nil
|
||||
}
|
46
server/db/providers/dynamodb/shared.go
Normal file
46
server/db/providers/dynamodb/shared.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
|
||||
"github.com/guregu/dynamo"
|
||||
)
|
||||
|
||||
// As updpate all item not supported so set manually via Set and SetNullable for empty field
|
||||
func UpdateByHashKey(table dynamo.Table, hashKey string, hashValue string, item interface{}) error {
|
||||
existingValue, err := dynamo.MarshalItem(item)
|
||||
var i interface{}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nullableValue, err := dynamodbattribute.MarshalMap(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u := table.Update(hashKey, hashValue)
|
||||
for k, v := range existingValue {
|
||||
if k == hashKey {
|
||||
continue
|
||||
}
|
||||
u = u.Set(k, v)
|
||||
}
|
||||
|
||||
for k, v := range nullableValue {
|
||||
if k == hashKey {
|
||||
continue
|
||||
}
|
||||
dynamodbattribute.Unmarshal(v, &i)
|
||||
if i == nil {
|
||||
u = u.SetNullable(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
err = u.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
224
server/db/providers/dynamodb/user.go
Normal file
224
server/db/providers/dynamodb/user.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/authorizerdev/authorizer/server/refs"
|
||||
"github.com/google/uuid"
|
||||
"github.com/guregu/dynamo"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// AddUser to save user information in database
|
||||
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
|
||||
collection := p.db.Table(models.Collections.User)
|
||||
|
||||
if user.ID == "" {
|
||||
user.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
if user.Roles == "" {
|
||||
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
user.Roles = defaultRoles
|
||||
}
|
||||
|
||||
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
|
||||
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil {
|
||||
return user, fmt.Errorf("user with given phone number already exists")
|
||||
}
|
||||
}
|
||||
|
||||
user.CreatedAt = time.Now().Unix()
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := collection.Put(user).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUser to update user information in database
|
||||
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
|
||||
collection := p.db.Table(models.Collections.User)
|
||||
|
||||
if user.ID != "" {
|
||||
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := UpdateByHashKey(collection, "id", user.ID, user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// DeleteUser to delete user information from database
|
||||
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||
collection := p.db.Table(models.Collections.User)
|
||||
sessionCollection := p.db.Table(models.Collections.Session)
|
||||
|
||||
if user.ID != "" {
|
||||
err := collection.Delete("id", user.ID).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sessionCollection.Batch("id").Write().Delete(dynamo.Keys{"user_id", user.ID}).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUsers to get list of users from database
|
||||
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
|
||||
var user models.User
|
||||
var lastEval dynamo.PagingKey
|
||||
var iter dynamo.PagingIter
|
||||
var iteration int64 = 0
|
||||
|
||||
collection := p.db.Table(models.Collections.User)
|
||||
users := []*model.User{}
|
||||
|
||||
paginationClone := pagination
|
||||
scanner := collection.Scan()
|
||||
count, err := scanner.Count()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for (paginationClone.Offset + paginationClone.Limit) > iteration {
|
||||
iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter()
|
||||
for iter.NextWithContext(ctx, &user) {
|
||||
if paginationClone.Offset == iteration {
|
||||
users = append(users, user.AsAPIUser())
|
||||
}
|
||||
}
|
||||
lastEval = iter.LastEvaluatedKey()
|
||||
iteration += paginationClone.Limit
|
||||
}
|
||||
|
||||
err = iter.Err()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paginationClone.Total = count
|
||||
|
||||
return &model.Users{
|
||||
Pagination: &paginationClone,
|
||||
Users: users,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail to get user information from database using email address
|
||||
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
|
||||
var users []models.User
|
||||
var user models.User
|
||||
|
||||
collection := p.db.Table(models.Collections.User)
|
||||
err := collection.Scan().Index("email").Filter("'email' = ?", email).AllWithContext(ctx, &users)
|
||||
|
||||
if err != nil {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
if len(users) > 0 {
|
||||
user = users[0]
|
||||
return user, nil
|
||||
} else {
|
||||
return user, errors.New("no record found")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// GetUserByID to get user information from database using user ID
|
||||
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
|
||||
collection := p.db.Table(models.Collections.User)
|
||||
var user models.User
|
||||
err := collection.Get("id", id).OneWithContext(ctx, &user)
|
||||
|
||||
if err != nil {
|
||||
if user.Email == "" {
|
||||
return user, errors.New("no documets found")
|
||||
} else {
|
||||
return user, nil
|
||||
}
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUsers to update multiple users, with parameters of user IDs slice
|
||||
// If ids set to nil / empty all the users will be updated
|
||||
func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error {
|
||||
// set updated_at time for all users
|
||||
userCollection := p.db.Table(models.Collections.User)
|
||||
var allUsers []models.User
|
||||
var res int64 = 0
|
||||
var err error
|
||||
if len(ids) > 0 {
|
||||
for _, v := range ids {
|
||||
err = UpdateByHashKey(userCollection, "id", v, data)
|
||||
}
|
||||
} else {
|
||||
// as there is no facility to update all doc - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SQLtoNoSQL.UpdateData.html
|
||||
userCollection.Scan().All(&allUsers)
|
||||
|
||||
for _, user := range allUsers {
|
||||
err = UpdateByHashKey(userCollection, "id", user.ID, data)
|
||||
if err == nil {
|
||||
res = res + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
log.Info("Updated users: ", res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
|
||||
var users []models.User
|
||||
var user models.User
|
||||
|
||||
collection := p.db.Table(models.Collections.User)
|
||||
err := collection.Scan().Filter("'phone_number' = ?", phoneNumber).AllWithContext(ctx, &users)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(users) > 0 {
|
||||
user = users[0]
|
||||
return &user, nil
|
||||
} else {
|
||||
return nil, errors.New("no record found")
|
||||
}
|
||||
}
|
116
server/db/providers/dynamodb/verification_requests.go
Normal file
116
server/db/providers/dynamodb/verification_requests.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/google/uuid"
|
||||
"github.com/guregu/dynamo"
|
||||
)
|
||||
|
||||
// AddVerification to save verification request in database
|
||||
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
|
||||
collection := p.db.Table(models.Collections.VerificationRequest)
|
||||
|
||||
if verificationRequest.ID == "" {
|
||||
verificationRequest.ID = uuid.New().String()
|
||||
verificationRequest.CreatedAt = time.Now().Unix()
|
||||
verificationRequest.UpdatedAt = time.Now().Unix()
|
||||
err := collection.Put(verificationRequest).RunWithContext(ctx)
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
}
|
||||
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
// GetVerificationRequestByToken to get verification request from database using token
|
||||
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
|
||||
collection := p.db.Table(models.Collections.VerificationRequest)
|
||||
var verificationRequest models.VerificationRequest
|
||||
|
||||
iter := collection.Scan().Filter("'token' = ?", token).Iter()
|
||||
for iter.NextWithContext(ctx, &verificationRequest) {
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
err := iter.Err()
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
// GetVerificationRequestByEmail to get verification request by email from database
|
||||
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
|
||||
var verificationRequest models.VerificationRequest
|
||||
collection := p.db.Table(models.Collections.VerificationRequest)
|
||||
iter := collection.Scan().Filter("'email' = ?", email).Filter("'identifier' = ?", identifier).Iter()
|
||||
for iter.NextWithContext(ctx, &verificationRequest) {
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
err := iter.Err()
|
||||
if err != nil {
|
||||
return verificationRequest, err
|
||||
}
|
||||
return verificationRequest, nil
|
||||
}
|
||||
|
||||
// ListVerificationRequests to get list of verification requests from database
|
||||
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||
verificationRequests := []*model.VerificationRequest{}
|
||||
var verificationRequest models.VerificationRequest
|
||||
var lastEval dynamo.PagingKey
|
||||
var iter dynamo.PagingIter
|
||||
var iteration int64 = 0
|
||||
|
||||
collection := p.db.Table(models.Collections.VerificationRequest)
|
||||
paginationClone := pagination
|
||||
|
||||
scanner := collection.Scan()
|
||||
count, err := scanner.Count()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for (paginationClone.Offset + paginationClone.Limit) > iteration {
|
||||
iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter()
|
||||
for iter.NextWithContext(ctx, &verificationRequest) {
|
||||
if paginationClone.Offset == iteration {
|
||||
verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest())
|
||||
}
|
||||
}
|
||||
err = iter.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastEval = iter.LastEvaluatedKey()
|
||||
iteration += paginationClone.Limit
|
||||
}
|
||||
|
||||
paginationClone.Total = count
|
||||
|
||||
return &model.VerificationRequests{
|
||||
VerificationRequests: verificationRequests,
|
||||
Pagination: &paginationClone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteVerificationRequest to delete verification request from database
|
||||
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
|
||||
collection := p.db.Table(models.Collections.VerificationRequest)
|
||||
|
||||
if verificationRequest.ID != "" {
|
||||
err := collection.Delete("id", verificationRequest.ID).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
148
server/db/providers/dynamodb/webhook.go
Normal file
148
server/db/providers/dynamodb/webhook.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/google/uuid"
|
||||
"github.com/guregu/dynamo"
|
||||
)
|
||||
|
||||
// AddWebhook to add webhook
|
||||
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||
collection := p.db.Table(models.Collections.Webhook)
|
||||
|
||||
if webhook.ID == "" {
|
||||
webhook.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
webhook.Key = webhook.ID
|
||||
webhook.CreatedAt = time.Now().Unix()
|
||||
webhook.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := collection.Put(webhook).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// UpdateWebhook to update webhook
|
||||
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||
collection := p.db.Table(models.Collections.Webhook)
|
||||
|
||||
webhook.UpdatedAt = time.Now().Unix()
|
||||
err := UpdateByHashKey(collection, "id", webhook.ID, webhook)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// ListWebhooks to list webhook
|
||||
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
|
||||
webhooks := []*model.Webhook{}
|
||||
var webhook models.Webhook
|
||||
var lastEval dynamo.PagingKey
|
||||
var iter dynamo.PagingIter
|
||||
var iteration int64 = 0
|
||||
|
||||
collection := p.db.Table(models.Collections.Webhook)
|
||||
paginationClone := pagination
|
||||
scanner := collection.Scan()
|
||||
count, err := scanner.Count()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for (paginationClone.Offset + paginationClone.Limit) > iteration {
|
||||
iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter()
|
||||
for iter.NextWithContext(ctx, &webhook) {
|
||||
if paginationClone.Offset == iteration {
|
||||
webhooks = append(webhooks, webhook.AsAPIWebhook())
|
||||
}
|
||||
}
|
||||
err = iter.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastEval = iter.LastEvaluatedKey()
|
||||
iteration += paginationClone.Limit
|
||||
}
|
||||
|
||||
paginationClone.Total = count
|
||||
|
||||
return &model.Webhooks{
|
||||
Pagination: &paginationClone,
|
||||
Webhooks: webhooks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWebhookByID to get webhook by id
|
||||
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
||||
collection := p.db.Table(models.Collections.Webhook)
|
||||
var webhook models.Webhook
|
||||
|
||||
err := collection.Get("id", webhookID).OneWithContext(ctx, &webhook)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if webhook.ID == "" {
|
||||
return webhook.AsAPIWebhook(), errors.New("no documets found")
|
||||
}
|
||||
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// GetWebhookByEventName to get webhook by event_name
|
||||
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
||||
var webhook models.Webhook
|
||||
collection := p.db.Table(models.Collections.Webhook)
|
||||
|
||||
iter := collection.Scan().Index("event_name").Filter("'event_name' = ?", eventName).Iter()
|
||||
|
||||
for iter.NextWithContext(ctx, &webhook) {
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
err := iter.Err()
|
||||
|
||||
if err != nil {
|
||||
return webhook.AsAPIWebhook(), err
|
||||
}
|
||||
return webhook.AsAPIWebhook(), nil
|
||||
}
|
||||
|
||||
// DeleteWebhook to delete webhook
|
||||
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
|
||||
// Also delete webhook logs for given webhook id
|
||||
if webhook.ID != "" {
|
||||
webhookCollection := p.db.Table(models.Collections.Webhook)
|
||||
pagination := model.Pagination{}
|
||||
webhookLogCollection := p.db.Table(models.Collections.WebhookLog)
|
||||
err := webhookCollection.Delete("id", webhook.ID).RunWithContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
webhookLogs, errIs := p.ListWebhookLogs(ctx, pagination, webhook.ID)
|
||||
|
||||
for _, webhookLog := range webhookLogs.WebhookLogs {
|
||||
err = webhookLogCollection.Delete("id", webhookLog.ID).RunWithContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if errIs != nil {
|
||||
return errIs
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
78
server/db/providers/dynamodb/webhook_log.go
Normal file
78
server/db/providers/dynamodb/webhook_log.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package dynamodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/google/uuid"
|
||||
"github.com/guregu/dynamo"
|
||||
)
|
||||
|
||||
// AddWebhookLog to add webhook log
|
||||
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
||||
collection := p.db.Table(models.Collections.WebhookLog)
|
||||
|
||||
if webhookLog.ID == "" {
|
||||
webhookLog.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
webhookLog.Key = webhookLog.ID
|
||||
webhookLog.CreatedAt = time.Now().Unix()
|
||||
webhookLog.UpdatedAt = time.Now().Unix()
|
||||
err := collection.Put(webhookLog).RunWithContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webhookLog.AsAPIWebhookLog(), nil
|
||||
}
|
||||
|
||||
// ListWebhookLogs to list webhook logs
|
||||
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
|
||||
webhookLogs := []*model.WebhookLog{}
|
||||
var webhookLog models.WebhookLog
|
||||
var lastEval dynamo.PagingKey
|
||||
var iter dynamo.PagingIter
|
||||
var iteration int64 = 0
|
||||
var err error
|
||||
var count int64
|
||||
|
||||
collection := p.db.Table(models.Collections.WebhookLog)
|
||||
paginationClone := pagination
|
||||
scanner := collection.Scan()
|
||||
|
||||
if webhookID != "" {
|
||||
iter = scanner.Index("webhook_id").Filter("'webhook_id' = ?", webhookID).Iter()
|
||||
for iter.NextWithContext(ctx, &webhookLog) {
|
||||
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
|
||||
}
|
||||
err = iter.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
for (paginationClone.Offset + paginationClone.Limit) > iteration {
|
||||
iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter()
|
||||
for iter.NextWithContext(ctx, &webhookLog) {
|
||||
if paginationClone.Offset == iteration {
|
||||
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
|
||||
}
|
||||
}
|
||||
err = iter.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastEval = iter.LastEvaluatedKey()
|
||||
iteration += paginationClone.Limit
|
||||
}
|
||||
}
|
||||
|
||||
paginationClone.Total = count
|
||||
// paginationClone.Cursor = iter.LastEvaluatedKey()
|
||||
return &model.WebhookLogs{
|
||||
Pagination: &paginationClone,
|
||||
WebhookLogs: webhookLogs,
|
||||
}, nil
|
||||
}
|
@@ -155,3 +155,16 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
|
||||
var user models.User
|
||||
|
||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||
err := userCollection.FindOne(ctx, bson.M{"phone_number": phoneNumber}).Decode(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
@@ -69,3 +69,10 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
|
||||
var user *models.User
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
@@ -18,6 +18,8 @@ type Provider interface {
|
||||
ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error)
|
||||
// GetUserByEmail to get user information from database using email address
|
||||
GetUserByEmail(ctx context.Context, email string) (models.User, error)
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error)
|
||||
// GetUserByID to get user information from database using user ID
|
||||
GetUserByID(ctx context.Context, id string) (models.User, error)
|
||||
// UpdateUsers to update multiple users, with parameters of user IDs slice
|
||||
|
@@ -1,16 +1,15 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/driver/sqlserver"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
@@ -21,17 +20,27 @@ type provider struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
const (
|
||||
phoneNumberIndexName = "UQ_phone_number"
|
||||
phoneNumberColumnName = "phone_number"
|
||||
)
|
||||
|
||||
type indexInfo struct {
|
||||
IndexName string `json:"index_name"`
|
||||
ColumnName string `json:"column_name"`
|
||||
}
|
||||
|
||||
// NewProvider returns a new SQL provider
|
||||
func NewProvider() (*provider, error) {
|
||||
var sqlDB *gorm.DB
|
||||
var err error
|
||||
customLogger := logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
||||
logrus.StandardLogger(),
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // Slow SQL threshold
|
||||
LogLevel: logger.Silent, // Log level
|
||||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
||||
Colorful: false, // Disable color
|
||||
SlowThreshold: time.Second, // Slow SQL threshold
|
||||
LogLevel: logger.Error, // Log level
|
||||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
||||
Colorful: false, // Disable color
|
||||
},
|
||||
)
|
||||
|
||||
@@ -50,7 +59,7 @@ func NewProvider() (*provider, error) {
|
||||
case constants.DbTypePostgres, constants.DbTypeYugabyte, constants.DbTypeCockroachDB:
|
||||
sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig)
|
||||
case constants.DbTypeSqlite:
|
||||
sqlDB, err = gorm.Open(sqlite.Open(dbURL), ormConfig)
|
||||
sqlDB, err = gorm.Open(sqlite.Open(dbURL+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)"), ormConfig)
|
||||
case constants.DbTypeMysql, constants.DbTypeMariaDB, constants.DbTypePlanetScaleDB:
|
||||
sqlDB, err = gorm.Open(mysql.Open(dbURL), ormConfig)
|
||||
case constants.DbTypeSqlserver:
|
||||
@@ -61,10 +70,44 @@ func NewProvider() (*provider, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For sqlserver, handle uniqueness of phone_number manually via extra db call
|
||||
// during create and update mutation.
|
||||
if sqlDB.Migrator().HasConstraint(&models.User{}, "authorizer_users_phone_number_key") {
|
||||
err = sqlDB.Migrator().DropConstraint(&models.User{}, "authorizer_users_phone_number_key")
|
||||
logrus.Debug("Failed to drop phone number constraint:", err)
|
||||
}
|
||||
|
||||
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}, &models.OTP{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// IMPACT: Request user to manually delete: UQ_phone_number constraint
|
||||
// unique constraint on phone number does not work with multiple null values for sqlserver
|
||||
// for more information check https://stackoverflow.com/a/767702
|
||||
// if dbType == constants.DbTypeSqlserver {
|
||||
// var indexInfos []indexInfo
|
||||
// // remove index on phone number if present with different name
|
||||
// res := sqlDB.Raw("SELECT i.name AS index_name, i.type_desc AS index_algorithm, CASE i.is_unique WHEN 1 THEN 'TRUE' ELSE 'FALSE' END AS is_unique, ac.Name AS column_name FROM sys.tables AS t INNER JOIN sys.indexes AS i ON t.object_id = i.object_id INNER JOIN sys.index_columns AS ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id INNER JOIN sys.all_columns AS ac ON ic.object_id = ac.object_id AND ic.column_id = ac.column_id WHERE t.name = 'authorizer_users' AND SCHEMA_NAME(t.schema_id) = 'dbo';").Scan(&indexInfos)
|
||||
// if res.Error != nil {
|
||||
// return nil, res.Error
|
||||
// }
|
||||
|
||||
// for _, val := range indexInfos {
|
||||
// if val.ColumnName == phoneNumberColumnName && val.IndexName != phoneNumberIndexName {
|
||||
// // drop index & create new
|
||||
// if res := sqlDB.Exec(fmt.Sprintf(`ALTER TABLE authorizer_users DROP CONSTRAINT "%s";`, val.IndexName)); res.Error != nil {
|
||||
// return nil, res.Error
|
||||
// }
|
||||
|
||||
// // create index
|
||||
// if res := sqlDB.Exec(fmt.Sprintf("CREATE UNIQUE NONCLUSTERED INDEX %s ON authorizer_users(phone_number) WHERE phone_number IS NOT NULL;", phoneNumberIndexName)); res.Error != nil {
|
||||
// return nil, res.Error
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return &provider{
|
||||
db: sqlDB,
|
||||
}, nil
|
||||
|
@@ -2,12 +2,15 @@ package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/authorizerdev/authorizer/server/refs"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
@@ -27,6 +30,12 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
|
||||
user.Roles = defaultRoles
|
||||
}
|
||||
|
||||
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
|
||||
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil {
|
||||
return user, fmt.Errorf("user with given phone number already exists")
|
||||
}
|
||||
}
|
||||
|
||||
user.CreatedAt = time.Now().Unix()
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.Key = user.ID
|
||||
@@ -58,13 +67,12 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
|
||||
|
||||
// DeleteUser to delete user information from database
|
||||
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||
result := p.db.Delete(&user)
|
||||
|
||||
result := p.db.Where("user_id = ?", user.ID).Delete(&models.Session{})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
result = p.db.Where("user_id = ?", user.ID).Delete(&models.Session{})
|
||||
result = p.db.Delete(&user)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
@@ -141,3 +149,15 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByPhoneNumber to get user information from database using phone number
|
||||
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
|
||||
var user *models.User
|
||||
result := p.db.Where("phone_number = ?", phoneNumber).First(&user)
|
||||
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -126,6 +127,12 @@ func SendEmail(to []string, event string, data map[string]interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
smtpLocalName, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySmtpLocalName)
|
||||
if err != nil {
|
||||
log.Debugf("Error while getting smtp localname from env variable: %v", err)
|
||||
smtpLocalName = ""
|
||||
}
|
||||
|
||||
isProd, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsProd)
|
||||
if err != nil {
|
||||
log.Errorf("Error while getting env variable: %v", err)
|
||||
@@ -141,6 +148,11 @@ func SendEmail(to []string, event string, data map[string]interface{}) error {
|
||||
if !isProd {
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
if strings.TrimSpace(smtpLocalName) != "" {
|
||||
d.LocalName = smtpLocalName
|
||||
}
|
||||
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
log.Debug("SMTP Failed: ", err)
|
||||
return err
|
||||
|
96
server/env/env.go
vendored
96
server/env/env.go
vendored
@@ -55,6 +55,7 @@ func InitAllEnv() error {
|
||||
osSmtpPort := os.Getenv(constants.EnvKeySmtpPort)
|
||||
osSmtpUsername := os.Getenv(constants.EnvKeySmtpUsername)
|
||||
osSmtpPassword := os.Getenv(constants.EnvKeySmtpPassword)
|
||||
osSmtpLocalName := os.Getenv(constants.EnvKeySmtpLocalName)
|
||||
osSenderEmail := os.Getenv(constants.EnvKeySenderEmail)
|
||||
osJwtType := os.Getenv(constants.EnvKeyJwtType)
|
||||
osJwtSecret := os.Getenv(constants.EnvKeyJwtSecret)
|
||||
@@ -74,14 +75,24 @@ func InitAllEnv() error {
|
||||
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
|
||||
osTwitterClientID := os.Getenv(constants.EnvKeyTwitterClientID)
|
||||
osTwitterClientSecret := os.Getenv(constants.EnvKeyTwitterClientSecret)
|
||||
osMicrosoftClientID := os.Getenv(constants.EnvKeyMicrosoftClientID)
|
||||
osMicrosoftClientSecret := os.Getenv(constants.EnvKeyMicrosoftClientSecret)
|
||||
osMicrosoftActiveDirectoryTenantID := os.Getenv(constants.EnvKeyMicrosoftActiveDirectoryTenantID)
|
||||
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
|
||||
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
|
||||
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
|
||||
osAwsRegion := os.Getenv(constants.EnvAwsRegion)
|
||||
osAwsAccessKey := os.Getenv(constants.EnvAwsAccessKeyID)
|
||||
osAwsSecretKey := os.Getenv(constants.EnvAwsSecretAccessKey)
|
||||
osCouchbaseBucket := os.Getenv(constants.EnvCouchbaseBucket)
|
||||
osCouchbaseScope := os.Getenv(constants.EnvCouchbaseScope)
|
||||
osCouchbaseBucketRAMQuotaMB := os.Getenv(constants.EnvCouchbaseBucketRAMQuotaMB)
|
||||
|
||||
// os bool vars
|
||||
osAppCookieSecure := os.Getenv(constants.EnvKeyAppCookieSecure)
|
||||
osAdminCookieSecure := os.Getenv(constants.EnvKeyAdminCookieSecure)
|
||||
osDisableBasicAuthentication := os.Getenv(constants.EnvKeyDisableBasicAuthentication)
|
||||
osDisableMobileBasicAuthentication := os.Getenv(constants.AuthRecipeMethodMobileBasicAuth)
|
||||
osDisableEmailVerification := os.Getenv(constants.EnvKeyDisableEmailVerification)
|
||||
osDisableMagicLinkLogin := os.Getenv(constants.EnvKeyDisableMagicLinkLogin)
|
||||
osDisableLoginPage := os.Getenv(constants.EnvKeyDisableLoginPage)
|
||||
@@ -119,6 +130,48 @@ func InitAllEnv() error {
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvAwsRegion]; !ok || val == "" {
|
||||
envData[constants.EnvAwsRegion] = osAwsRegion
|
||||
}
|
||||
if osAwsRegion != "" && envData[constants.EnvAwsRegion] != osAwsRegion {
|
||||
envData[constants.EnvAwsRegion] = osAwsRegion
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvAwsAccessKeyID]; !ok || val == "" {
|
||||
envData[constants.EnvAwsAccessKeyID] = osAwsAccessKey
|
||||
}
|
||||
if osAwsAccessKey != "" && envData[constants.EnvAwsAccessKeyID] != osAwsAccessKey {
|
||||
envData[constants.EnvAwsAccessKeyID] = osAwsAccessKey
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvAwsSecretAccessKey]; !ok || val == "" {
|
||||
envData[constants.EnvAwsSecretAccessKey] = osAwsSecretKey
|
||||
}
|
||||
if osAwsSecretKey != "" && envData[constants.EnvAwsSecretAccessKey] != osAwsSecretKey {
|
||||
envData[constants.EnvAwsSecretAccessKey] = osAwsSecretKey
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvCouchbaseBucket]; !ok || val == "" {
|
||||
envData[constants.EnvCouchbaseBucket] = osCouchbaseBucket
|
||||
}
|
||||
if osCouchbaseBucket != "" && envData[constants.EnvCouchbaseBucket] != osCouchbaseBucket {
|
||||
envData[constants.EnvCouchbaseBucket] = osCouchbaseBucket
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvCouchbaseBucketRAMQuotaMB]; !ok || val == "" {
|
||||
envData[constants.EnvCouchbaseBucketRAMQuotaMB] = osCouchbaseBucketRAMQuotaMB
|
||||
}
|
||||
if osCouchbaseBucketRAMQuotaMB != "" && envData[constants.EnvCouchbaseBucketRAMQuotaMB] != osCouchbaseBucketRAMQuotaMB {
|
||||
envData[constants.EnvCouchbaseBucketRAMQuotaMB] = osCouchbaseBucketRAMQuotaMB
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvCouchbaseScope]; !ok || val == "" {
|
||||
envData[constants.EnvCouchbaseScope] = osCouchbaseScope
|
||||
}
|
||||
if osCouchbaseScope != "" && envData[constants.EnvCouchbaseScope] != osCouchbaseScope {
|
||||
envData[constants.EnvCouchbaseScope] = osCouchbaseScope
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvKeyAppURL]; !ok || val == "" {
|
||||
envData[constants.EnvKeyAppURL] = osAppURL
|
||||
}
|
||||
@@ -181,6 +234,13 @@ func InitAllEnv() error {
|
||||
envData[constants.EnvKeySmtpUsername] = osSmtpUsername
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvKeySmtpLocalName]; !ok || val == "" {
|
||||
envData[constants.EnvKeySmtpLocalName] = osSmtpLocalName
|
||||
}
|
||||
if osSmtpLocalName != "" && envData[constants.EnvKeySmtpLocalName] != osSmtpLocalName {
|
||||
envData[constants.EnvKeySmtpLocalName] = osSmtpLocalName
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvKeySmtpPassword]; !ok || val == "" {
|
||||
envData[constants.EnvKeySmtpPassword] = osSmtpPassword
|
||||
}
|
||||
@@ -300,7 +360,7 @@ func InitAllEnv() error {
|
||||
envData[constants.EnvKeyJwtRoleClaim] = osJwtRoleClaim
|
||||
|
||||
if envData[constants.EnvKeyJwtRoleClaim] == "" {
|
||||
envData[constants.EnvKeyJwtRoleClaim] = "role"
|
||||
envData[constants.EnvKeyJwtRoleClaim] = "roles"
|
||||
}
|
||||
}
|
||||
if osJwtRoleClaim != "" && envData[constants.EnvKeyJwtRoleClaim] != osJwtRoleClaim {
|
||||
@@ -398,6 +458,27 @@ func InitAllEnv() error {
|
||||
envData[constants.EnvKeyTwitterClientSecret] = osTwitterClientSecret
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvKeyMicrosoftClientID]; !ok || val == "" {
|
||||
envData[constants.EnvKeyMicrosoftClientID] = osMicrosoftClientID
|
||||
}
|
||||
if osMicrosoftClientID != "" && envData[constants.EnvKeyMicrosoftClientID] != osMicrosoftClientID {
|
||||
envData[constants.EnvKeyMicrosoftClientID] = osMicrosoftClientID
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvKeyMicrosoftClientSecret]; !ok || val == "" {
|
||||
envData[constants.EnvKeyMicrosoftClientSecret] = osMicrosoftClientSecret
|
||||
}
|
||||
if osMicrosoftClientSecret != "" && envData[constants.EnvKeyMicrosoftClientSecret] != osMicrosoftClientSecret {
|
||||
envData[constants.EnvKeyMicrosoftClientSecret] = osMicrosoftClientSecret
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvKeyMicrosoftActiveDirectoryTenantID]; !ok || val == "" {
|
||||
envData[constants.EnvKeyMicrosoftActiveDirectoryTenantID] = osMicrosoftActiveDirectoryTenantID
|
||||
}
|
||||
if osMicrosoftActiveDirectoryTenantID != "" && envData[constants.EnvKeyMicrosoftActiveDirectoryTenantID] != osMicrosoftActiveDirectoryTenantID {
|
||||
envData[constants.EnvKeyMicrosoftActiveDirectoryTenantID] = osMicrosoftActiveDirectoryTenantID
|
||||
}
|
||||
|
||||
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
|
||||
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
|
||||
}
|
||||
@@ -466,6 +547,19 @@ func InitAllEnv() error {
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := envData[constants.EnvKeyDisableMobileBasicAuthentication]; !ok {
|
||||
envData[constants.EnvKeyDisableMobileBasicAuthentication] = osDisableBasicAuthentication == "true"
|
||||
}
|
||||
if osDisableMobileBasicAuthentication != "" {
|
||||
boolValue, err := strconv.ParseBool(osDisableMobileBasicAuthentication)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if boolValue != envData[constants.EnvKeyDisableMobileBasicAuthentication].(bool) {
|
||||
envData[constants.EnvKeyDisableMobileBasicAuthentication] = boolValue
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := envData[constants.EnvKeyDisableEmailVerification]; !ok {
|
||||
envData[constants.EnvKeyDisableEmailVerification] = osDisableEmailVerification == "true"
|
||||
}
|
||||
|
3
server/env/persist_env.go
vendored
3
server/env/persist_env.go
vendored
@@ -75,7 +75,6 @@ func GetEnvData() (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
|
||||
|
||||
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
|
||||
if err != nil {
|
||||
log.Debug("Error while decrypting env data from B64: ", err)
|
||||
@@ -201,7 +200,7 @@ func PersistEnv() error {
|
||||
envValue := strings.TrimSpace(os.Getenv(key))
|
||||
if envValue != "" {
|
||||
switch key {
|
||||
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure:
|
||||
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure:
|
||||
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
|
||||
if value.(bool) != envValueBool {
|
||||
storeData[key] = envValueBool
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user