Compare commits
114 Commits
feat/add-e
...
1.1.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
21b70e4b26 | ||
![]() |
993693884d | ||
![]() |
ed849fa6f6 | ||
![]() |
aec1f5df53 | ||
![]() |
45b4c41bca | ||
![]() |
63d486821e | ||
![]() |
4b56afdc98 | ||
![]() |
6455ff956a | ||
![]() |
3898e43fff | ||
![]() |
2c305e5bde | ||
![]() |
b8fd08e576 | ||
![]() |
6dafa45051 | ||
![]() |
ead3514113 | ||
![]() |
75a413e5f2 | ||
![]() |
91bf0e2478 | ||
![]() |
7a1305cf96 | ||
![]() |
ff5a6ec301 | ||
![]() |
b7b97b4f8d | ||
![]() |
d9bc989c74 | ||
![]() |
d1f80d4088 | ||
![]() |
4b299f0da2 | ||
![]() |
ed8006db4c | ||
![]() |
97f6c7d50a | ||
![]() |
5e3f68a180 | ||
![]() |
f73d1fc588 | ||
![]() |
aa232de426 | ||
![]() |
34ce754ef6 | ||
![]() |
5f385b2016 | ||
![]() |
da7c17271e | ||
![]() |
69fbd631ff | ||
![]() |
deb209e358 | ||
![]() |
ea6b4cbc8d | ||
![]() |
2f21a09b2e | ||
![]() |
4ab775f2c1 | ||
![]() |
b6e8023104 | ||
![]() |
4f1597e5d2 | ||
![]() |
4f81d1969e | ||
![]() |
ad3e615ac7 | ||
![]() |
e9a2301d2b | ||
![]() |
48bbfa31af | ||
![]() |
d7f5f563cc | ||
![]() |
6c29149fbe | ||
![]() |
bbd4d43317 | ||
![]() |
c4d2f62657 | ||
![]() |
5d78bf178f | ||
![]() |
58749497bd | ||
![]() |
5c6e643efb | ||
![]() |
7792cdbc5e | ||
![]() |
65803c3763 | ||
![]() |
81fce1a471 | ||
![]() |
0714b4360b | ||
![]() |
8f69d5746e | ||
![]() |
ebc11906ef | ||
![]() |
465a92de22 | ||
![]() |
a890013317 | ||
![]() |
587828b59b | ||
![]() |
85630a59c1 | ||
![]() |
b4ef196bfb | ||
![]() |
099b2a39b4 | ||
![]() |
2d07baedf4 | ||
![]() |
8b34e001ef | ||
![]() |
617dcdde53 | ||
![]() |
f2fb800323 | ||
![]() |
236045ac54 | ||
![]() |
d89be44fe5 | ||
![]() |
db4d711cba | ||
![]() |
0fc9e8ccaa | ||
![]() |
4e3d73e767 | ||
![]() |
e3c58ffbb0 | ||
![]() |
f12491e42d | ||
![]() |
d653fac340 | ||
![]() |
9fae8215d2 | ||
![]() |
4e23e49de4 | ||
![]() |
ef22318d5c | ||
![]() |
480438fb7a | ||
![]() |
8db6649e5c | ||
![]() |
49cc6033ab | ||
![]() |
5d903ca170 | ||
![]() |
44280be25a | ||
![]() |
f6029fb7bf | ||
![]() |
22ae3bca54 | ||
![]() |
1a27d91957 | ||
![]() |
f6c67243b9 | ||
![]() |
9ba1239c11 | ||
![]() |
ed7ed73980 | ||
![]() |
9ef5f33f7a | ||
![]() |
0f081ac3c8 | ||
![]() |
3aa0fb20ce | ||
![]() |
891c885f20 | ||
![]() |
89606615dc | ||
![]() |
ecab47b2ea | ||
![]() |
882756ef3a | ||
![]() |
a208c87c29 | ||
![]() |
70ea463f60 | ||
![]() |
79c94fcaf0 | ||
![]() |
3b925bb072 | ||
![]() |
df17ea8f40 | ||
![]() |
94066d4408 | ||
![]() |
41468b5b60 | ||
![]() |
1c61fcc17a | ||
![]() |
390846c85f | ||
![]() |
150b1e5712 | ||
![]() |
8b1511a07b | ||
![]() |
a69dd95992 | ||
![]() |
d3260f4f32 | ||
![]() |
301bde4da2 | ||
![]() |
913c5c94fb | ||
![]() |
33f79872be | ||
![]() |
8fc52d76dc | ||
![]() |
eabc943452 | ||
![]() |
41a0f15e16 | ||
![]() |
a3c0a0422c | ||
![]() |
d837b1590a | ||
![]() |
8e655daa71 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -49,7 +49,7 @@ Please ask as many questions as you need, either directly in the issue or on [Di
|
|||||||
6. Build Dashboard `make build-dashboard`
|
6. Build Dashboard `make build-dashboard`
|
||||||
7. Build App `make build-app`
|
7. Build App `make build-app`
|
||||||
8. Build Server `make clean && make`
|
8. Build Server `make clean && make`
|
||||||
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
|
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command. In that case you will have to build `dashboard` & `app` manually using `npm run build` on both dirs.
|
||||||
9. Run binary `./build/server`
|
9. Run binary `./build/server`
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
14
Dockerfile
14
Dockerfile
@@ -21,13 +21,15 @@ RUN apk add build-base &&\
|
|||||||
make build-dashboard
|
make build-dashboard
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
WORKDIR /root/
|
RUN adduser -D -h /authorizer -u 1000 -k /dev/null authorizer
|
||||||
|
WORKDIR /authorizer
|
||||||
RUN mkdir app dashboard
|
RUN mkdir app dashboard
|
||||||
COPY --from=node-builder /authorizer/app/build app/build
|
COPY --from=node-builder --chown=nobody:nobody /authorizer/app/build app/build
|
||||||
COPY --from=node-builder /authorizer/app/favicon_io app/favicon_io
|
COPY --from=node-builder --chown=nobody:nobody /authorizer/app/favicon_io app/favicon_io
|
||||||
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
|
COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/build dashboard/build
|
||||||
COPY --from=node-builder /authorizer/dashboard/favicon_io dashboard/favicon_io
|
COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/favicon_io dashboard/favicon_io
|
||||||
COPY --from=go-builder /authorizer/build build
|
COPY --from=go-builder --chown=nobody:nobody /authorizer/build build
|
||||||
COPY templates templates
|
COPY templates templates
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
USER authorizer
|
||||||
CMD [ "./build/server" ]
|
CMD [ "./build/server" ]
|
||||||
|
14
Makefile
14
Makefile
@@ -11,14 +11,26 @@ clean:
|
|||||||
rm -rf build
|
rm -rf build
|
||||||
test:
|
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 && rm -rf test.db && 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
|
||||||
|
docker rm -vf authorizer_mongodb_db
|
||||||
|
test-scylladb:
|
||||||
|
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
|
||||||
|
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
|
||||||
|
cd server && go clean --testcache && TEST_DBS="arangodb" go test -p 1 -v ./test
|
||||||
|
docker rm -vf authorizer_arangodb
|
||||||
test-all-db:
|
test-all-db:
|
||||||
rm -rf server/test/test.db && rm -rf test.db
|
rm -rf server/test/test.db && rm -rf test.db
|
||||||
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
|
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_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
|
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
|
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test
|
||||||
docker rm -vf authorizer_mongodb_db
|
|
||||||
docker rm -vf authorizer_scylla_db
|
docker rm -vf authorizer_scylla_db
|
||||||
|
docker rm -vf authorizer_mongodb_db
|
||||||
docker rm -vf authorizer_arangodb
|
docker rm -vf authorizer_arangodb
|
||||||
generate:
|
generate:
|
||||||
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate
|
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate
|
||||||
|
22
README.md
22
README.md
@@ -7,7 +7,7 @@
|
|||||||
Authorizer
|
Authorizer
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/), [ArangoDB](https://www.arangodb.com/)).
|
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports 11+ databases including [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [YugaByte](https://www.yugabyte.com/), [MariaDB](https://mariadb.org/), [PlanetScale](https://planetscale.com/), [CassandraDB](https://cassandra.apache.org/_/index.html), [ScyllaDB](https://www.scylladb.com/), [MongoDB](https://mongodb.com/), [ArangoDB](https://www.arangodb.com/)).
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
<img src="https://github.com/authorizerdev/authorizer/blob/main/assets/authorizer-architecture.png" style="height:20em"/>
|
<img src="https://docs.authorizer.dev/images/authorizer-arch.png" style="height:20em"/>
|
||||||
|
|
||||||
#### We offer the following functionality
|
#### We offer the following functionality
|
||||||
|
|
||||||
@@ -29,13 +29,15 @@
|
|||||||
- ✅ OAuth2 and OpenID compatible APIs
|
- ✅ OAuth2 and OpenID compatible APIs
|
||||||
- ✅ APIs to update profile securely
|
- ✅ APIs to update profile securely
|
||||||
- ✅ Forgot password flow using email
|
- ✅ Forgot password flow using email
|
||||||
- ✅ Social logins (Google, Github, Facebook, more coming soon)
|
- ✅ Social logins (Google, Github, Facebook, LinkedIn, Apple more coming soon)
|
||||||
- ✅ Role-based access management
|
- ✅ Role-based access management
|
||||||
- ✅ Password-less login with magic link login
|
- ✅ Password-less login with magic link login
|
||||||
|
- ✅ Multi factor authentication
|
||||||
|
- ✅ Email templating
|
||||||
|
- ✅ Webhooks
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- 2 Factor authentication
|
|
||||||
- VueJS SDK
|
- VueJS SDK
|
||||||
- Svelte SDK
|
- Svelte SDK
|
||||||
- React Native SDK
|
- React Native SDK
|
||||||
@@ -63,11 +65,11 @@
|
|||||||
|
|
||||||
Deploy production ready Authorizer instance using one click deployment options available below
|
Deploy production ready Authorizer instance using one click deployment options available below
|
||||||
|
|
||||||
| **Infra provider** | **One-click link** | **Additional information** |
|
| **Infra provider** | **One-click link** | **Additional information** |
|
||||||
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
|
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
|
||||||
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
|
| Railway.app | <a href="https://railway.app/new/template/nwXp1C?referralCode=FEF4uT"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
|
||||||
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
|
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
|
||||||
| Render | [](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
|
| Render | [](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
|
||||||
|
|
||||||
### Deploy Authorizer Using Source Code
|
### Deploy Authorizer Using Source Code
|
||||||
|
|
||||||
@@ -89,7 +91,7 @@ This guide helps you practice using Authorizer to evaluate it before you use it
|
|||||||
5. Build Dashboard `make build-dashboard`
|
5. Build Dashboard `make build-dashboard`
|
||||||
6. Build App `make build-app`
|
6. Build App `make build-app`
|
||||||
7. Build Server `make clean && make`
|
7. Build Server `make clean && make`
|
||||||
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
|
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command. In that case you will have to build `dashboard` & `app` manually using `npm run build` on both dirs.
|
||||||
8. Run binary `./build/server`
|
8. Run binary `./build/server`
|
||||||
|
|
||||||
### Deploy Authorizer using binaries
|
### Deploy Authorizer using binaries
|
||||||
|
50
app/package-lock.json
generated
50
app/package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "^0.25.0",
|
"@authorizerdev/authorizer-react": "^1.1.0",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
@@ -26,22 +26,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-js": {
|
"node_modules/@authorizerdev/authorizer-js": {
|
||||||
"version": "0.14.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
||||||
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
|
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-fetch": "^2.6.1"
|
"cross-fetch": "^3.1.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-react": {
|
"node_modules/@authorizerdev/authorizer-react": {
|
||||||
"version": "0.25.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.0.tgz",
|
||||||
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
|
"integrity": "sha512-8ooyBREFI6ohHApVOPQitFr7T0w0SlpEVZruvU9oqa8OQ77UBxLQh+PCRKKPw7FeQRdCdh/VQyl17W7Xphp1NA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": "^0.14.0",
|
"@authorizerdev/authorizer-js": "^1.1.0",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
@@ -404,6 +404,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css-color-keywords": {
|
"node_modules/css-color-keywords": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||||
@@ -852,19 +860,19 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": {
|
"@authorizerdev/authorizer-js": {
|
||||||
"version": "0.14.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
||||||
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
|
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-fetch": "^2.6.1"
|
"cross-fetch": "^3.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@authorizerdev/authorizer-react": {
|
"@authorizerdev/authorizer-react": {
|
||||||
"version": "0.25.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.0.tgz",
|
||||||
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
|
"integrity": "sha512-8ooyBREFI6ohHApVOPQitFr7T0w0SlpEVZruvU9oqa8OQ77UBxLQh+PCRKKPw7FeQRdCdh/VQyl17W7Xphp1NA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@authorizerdev/authorizer-js": "^0.14.0",
|
"@authorizerdev/authorizer-js": "^1.1.0",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
@@ -1161,6 +1169,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
},
|
},
|
||||||
|
"cross-fetch": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||||
|
"requires": {
|
||||||
|
"node-fetch": "2.6.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"css-color-keywords": {
|
"css-color-keywords": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
"author": "Lakhan Samani",
|
"author": "Lakhan Samani",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "^0.25.0",
|
"@authorizerdev/authorizer-react": "^1.1.0",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
|
@@ -4,6 +4,12 @@ import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
|
|||||||
import Root from './Root';
|
import Root from './Root';
|
||||||
import { createRandomString } from './utils/common';
|
import { createRandomString } from './utils/common';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__authorizer__: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
const state = searchParams.get('state') || createRandomString();
|
const state = searchParams.get('state') || createRandomString();
|
||||||
@@ -24,7 +30,6 @@ export default function App() {
|
|||||||
urlProps.redirectURL = window.location.origin + '/app';
|
urlProps.redirectURL = window.location.origin + '/app';
|
||||||
}
|
}
|
||||||
const globalState: Record<string, string> = {
|
const globalState: Record<string, string> = {
|
||||||
// @ts-ignore
|
|
||||||
...window['__authorizer__'],
|
...window['__authorizer__'],
|
||||||
...urlProps,
|
...urlProps,
|
||||||
};
|
};
|
||||||
|
133
dashboard/package-lock.json
generated
133
dashboard/package-lock.json
generated
@@ -24,11 +24,16 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-draft-wysiwyg": "^1.15.0",
|
||||||
"react-dropzone": "^12.0.4",
|
"react-dropzone": "^12.0.4",
|
||||||
|
"react-email-editor": "^1.6.1",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"urql": "^2.0.6"
|
"urql": "^2.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react-email-editor": "^1.1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -1191,6 +1196,15 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-email-editor": {
|
||||||
|
"version": "1.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-email-editor/-/react-email-editor-1.1.7.tgz",
|
||||||
|
"integrity": "sha512-OURTAgaE9pjA6KiU97k13fPdoglI1ZyowUuZ0nu5tTSyrw5PiZoYzYEf9y25YTjmw/ohxT5yqoP0tt+AjSh1qQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-router": {
|
"node_modules/@types/react-router": {
|
||||||
"version": "5.1.17",
|
"version": "5.1.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
||||||
@@ -1306,6 +1320,11 @@
|
|||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classnames": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -1383,6 +1402,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/draftjs-utils": {
|
||||||
|
"version": "0.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
|
||||||
|
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": "^0.11.x",
|
||||||
|
"immutable": "3.x.x || 4.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/error-ex": {
|
"node_modules/error-ex": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
@@ -1802,6 +1830,15 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/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==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": "^0.10.x || ^0.11.x",
|
||||||
|
"immutable": "3.x.x || 4.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@@ -1856,6 +1893,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/linkify-it": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||||
|
"dependencies": {
|
||||||
|
"uc.micro": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@@ -1991,6 +2036,24 @@
|
|||||||
"react": "17.0.2"
|
"react": "17.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-draft-wysiwyg": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"draftjs-utils": "^0.10.2",
|
||||||
|
"html-to-draftjs": "^1.5.0",
|
||||||
|
"linkify-it": "^2.2.0",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": "^0.10.x || ^0.11.x",
|
||||||
|
"immutable": "3.x.x || 4.x.x",
|
||||||
|
"react": "0.13.x || 0.14.x || ^15.0.0-0 || 15.x.x || ^16.0.0-0 || ^16.x.x || ^17.x.x || ^18.x.x",
|
||||||
|
"react-dom": "0.13.x || 0.14.x || ^15.0.0-0 || 15.x.x || ^16.0.0-0 || ^16.x.x || ^17.x.x || ^18.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dropzone": {
|
"node_modules/react-dropzone": {
|
||||||
"version": "12.0.4",
|
"version": "12.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
@@ -2007,6 +2070,14 @@
|
|||||||
"react": ">= 16.8"
|
"react": ">= 16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/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==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "15.x || 16.x || 17.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-fast-compare": {
|
"node_modules/react-fast-compare": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||||
@@ -2275,6 +2346,11 @@
|
|||||||
"node": ">=4.2.0"
|
"node": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uc.micro": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
|
||||||
|
},
|
||||||
"node_modules/urql": {
|
"node_modules/urql": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
||||||
@@ -3218,6 +3294,15 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-email-editor": {
|
||||||
|
"version": "1.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-email-editor/-/react-email-editor-1.1.7.tgz",
|
||||||
|
"integrity": "sha512-OURTAgaE9pjA6KiU97k13fPdoglI1ZyowUuZ0nu5tTSyrw5PiZoYzYEf9y25YTjmw/ohxT5yqoP0tt+AjSh1qQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-router": {
|
"@types/react-router": {
|
||||||
"version": "5.1.17",
|
"version": "5.1.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
||||||
@@ -3316,6 +3401,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"classnames": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||||
|
},
|
||||||
"color-convert": {
|
"color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -3390,6 +3480,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||||
},
|
},
|
||||||
|
"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": {}
|
||||||
|
},
|
||||||
"error-ex": {
|
"error-ex": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
@@ -3659,6 +3755,12 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {}
|
||||||
|
},
|
||||||
"import-fresh": {
|
"import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@@ -3704,6 +3806,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||||
},
|
},
|
||||||
|
"linkify-it": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||||
|
"requires": {
|
||||||
|
"uc.micro": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@@ -3814,6 +3924,18 @@
|
|||||||
"scheduler": "^0.20.2"
|
"scheduler": "^0.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-draft-wysiwyg": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA==",
|
||||||
|
"requires": {
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"draftjs-utils": "^0.10.2",
|
||||||
|
"html-to-draftjs": "^1.5.0",
|
||||||
|
"linkify-it": "^2.2.0",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-dropzone": {
|
"react-dropzone": {
|
||||||
"version": "12.0.4",
|
"version": "12.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
@@ -3824,6 +3946,12 @@
|
|||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {}
|
||||||
|
},
|
||||||
"react-fast-compare": {
|
"react-fast-compare": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||||
@@ -4020,6 +4148,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
|
||||||
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg=="
|
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg=="
|
||||||
},
|
},
|
||||||
|
"uc.micro": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
|
||||||
|
},
|
||||||
"urql": {
|
"urql": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
||||||
|
@@ -26,10 +26,15 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-draft-wysiwyg": "^1.15.0",
|
||||||
"react-dropzone": "^12.0.4",
|
"react-dropzone": "^12.0.4",
|
||||||
|
"react-email-editor": "^1.6.1",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"urql": "^2.0.6"
|
"urql": "^2.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react-email-editor": "^1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
106
dashboard/src/components/DeleteEmailTemplateModal.tsx
Normal file
106
dashboard/src/components/DeleteEmailTemplateModal.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaRegTrashAlt } from 'react-icons/fa';
|
||||||
|
import { DeleteEmailTemplate } from '../graphql/mutation';
|
||||||
|
import { capitalizeFirstLetter } from '../utils';
|
||||||
|
|
||||||
|
interface deleteEmailTemplateModalInputPropTypes {
|
||||||
|
emailTemplateId: string;
|
||||||
|
eventName: string;
|
||||||
|
fetchEmailTemplatesData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteEmailTemplateModal = ({
|
||||||
|
emailTemplateId,
|
||||||
|
eventName,
|
||||||
|
fetchEmailTemplatesData,
|
||||||
|
}: deleteEmailTemplateModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
const deleteHandler = async () => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(DeleteEmailTemplate, { params: { id: emailTemplateId } })
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (res.data?._delete_email_template) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.data?._delete_email_template.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
fetchEmailTemplatesData();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>Delete</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Delete Email Template</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Text fontSize="md">Are you sure?</Text>
|
||||||
|
<Flex
|
||||||
|
padding="5%"
|
||||||
|
marginTop="5%"
|
||||||
|
marginBottom="2%"
|
||||||
|
border="1px solid #ff7875"
|
||||||
|
borderRadius="5px"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Email template for event <b>{eventName}</b> will be deleted
|
||||||
|
permanently!
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaRegTrashAlt />}
|
||||||
|
colorScheme="red"
|
||||||
|
variant="solid"
|
||||||
|
onClick={deleteHandler}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Delete
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteEmailTemplateModal;
|
106
dashboard/src/components/DeleteWebhookModal.tsx
Normal file
106
dashboard/src/components/DeleteWebhookModal.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaRegTrashAlt } from 'react-icons/fa';
|
||||||
|
import { DeleteWebhook } from '../graphql/mutation';
|
||||||
|
import { capitalizeFirstLetter } from '../utils';
|
||||||
|
|
||||||
|
interface deleteWebhookModalInputPropTypes {
|
||||||
|
webhookId: string;
|
||||||
|
eventName: string;
|
||||||
|
fetchWebookData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteWebhookModal = ({
|
||||||
|
webhookId,
|
||||||
|
eventName,
|
||||||
|
fetchWebookData,
|
||||||
|
}: deleteWebhookModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
const deleteHandler = async () => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(DeleteWebhook, { params: { id: webhookId } })
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (res.data?._delete_webhook) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.data?._delete_webhook.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
fetchWebookData();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>Delete</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Delete Webhook</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Text fontSize="md">Are you sure?</Text>
|
||||||
|
<Flex
|
||||||
|
padding="5%"
|
||||||
|
marginTop="5%"
|
||||||
|
marginBottom="2%"
|
||||||
|
border="1px solid #ff7875"
|
||||||
|
borderRadius="5px"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Webhook for event <b>{eventName}</b> will be deleted
|
||||||
|
permanently!
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaRegTrashAlt />}
|
||||||
|
colorScheme="red"
|
||||||
|
variant="solid"
|
||||||
|
onClick={deleteHandler}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Delete
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteWebhookModal;
|
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Flex, Stack, Text } from '@chakra-ui/react';
|
import { Divider, Flex, Stack, Text } from '@chakra-ui/react';
|
||||||
import InputField from '../InputField';
|
import InputField from '../InputField';
|
||||||
import { SwitchInputType } from '../../constants';
|
import { SwitchInputType } from '../../constants';
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ const Features = ({ variables, setVariables }: any) => {
|
|||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||||
Disable Features
|
Disable Features
|
||||||
</Text>
|
</Text>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6}>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Flex w="100%" justifyContent="start" alignItems="center">
|
<Flex w="100%" justifyContent="start" alignItems="center">
|
||||||
<Text fontSize="sm">Disable Login Page:</Text>
|
<Text fontSize="sm">Disable Login Page:</Text>
|
||||||
@@ -83,6 +83,48 @@ const Features = ({ variables, setVariables }: any) => {
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Disable Multi Factor Authentication (MFA):
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="x-small">
|
||||||
|
Note: Enabling this will ignore Enforcing MFA shown below and will
|
||||||
|
also ignore the user MFA setting.
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" mb={3}>
|
||||||
|
<InputField
|
||||||
|
variables={variables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={SwitchInputType.DISABLE_MULTI_FACTOR_AUTHENTICATION}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider paddingY={5} />
|
||||||
|
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
|
||||||
|
Enable Features
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6}>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Enforce Multi Factor Authentication (MFA):
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="x-small">
|
||||||
|
Note: If you disable enforcing after it was enabled, it will still
|
||||||
|
keep MFA enabled for older users.
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" mb={3}>
|
||||||
|
<InputField
|
||||||
|
variables={variables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={SwitchInputType.ENFORCE_MULTI_FACTOR_AUTHENTICATION}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -1,154 +1,201 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
|
||||||
import {
|
import {
|
||||||
HiddenInputType,
|
Flex,
|
||||||
TextInputType,
|
Stack,
|
||||||
TextAreaInputType,
|
Center,
|
||||||
} from "../../constants";
|
Text,
|
||||||
import GenerateKeysModal from "../GenerateKeysModal";
|
useMediaQuery,
|
||||||
import InputField from "../InputField";
|
Button,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
HiddenInputType,
|
||||||
|
TextInputType,
|
||||||
|
TextAreaInputType,
|
||||||
|
} from '../../constants';
|
||||||
|
import GenerateKeysModal from '../GenerateKeysModal';
|
||||||
|
import InputField from '../InputField';
|
||||||
|
import { copyTextToClipboard } from '../../utils';
|
||||||
|
|
||||||
const JSTConfigurations = ({
|
const JSTConfigurations = ({
|
||||||
variables,
|
variables,
|
||||||
setVariables,
|
setVariables,
|
||||||
fieldVisibility,
|
fieldVisibility,
|
||||||
setFieldVisibility,
|
setFieldVisibility,
|
||||||
SelectInputType,
|
SelectInputType,
|
||||||
getData,
|
getData,
|
||||||
HMACEncryptionType,
|
HMACEncryptionType,
|
||||||
RSAEncryptionType,
|
RSAEncryptionType,
|
||||||
ECDSAEncryptionType,
|
ECDSAEncryptionType,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
return (
|
const copyJSON = async () => {
|
||||||
<div>
|
try {
|
||||||
{" "}
|
await copyTextToClipboard(
|
||||||
<Flex
|
JSON.stringify({
|
||||||
borderRadius={5}
|
type: variables.JWT_TYPE,
|
||||||
width="100%"
|
key: variables.JWT_PUBLIC_KEY || variables.JWT_SECRET,
|
||||||
justifyContent="space-between"
|
})
|
||||||
alignItems="center"
|
);
|
||||||
paddingTop="2%"
|
toast({
|
||||||
>
|
title: `JWT config copied successfully`,
|
||||||
<Text
|
isClosable: true,
|
||||||
fontSize={isNotSmallerScreen ? "md" : "sm"}
|
status: 'success',
|
||||||
fontWeight="bold"
|
position: 'bottom-right',
|
||||||
mb={5}
|
});
|
||||||
>
|
} catch (err) {
|
||||||
JWT (JSON Web Tokens) Configurations
|
console.error({
|
||||||
</Text>
|
message: `Failed to copy JWT config`,
|
||||||
<Flex mb={7}>
|
error: err,
|
||||||
<GenerateKeysModal jwtType={variables.JWT_TYPE} getData={getData} />
|
});
|
||||||
</Flex>
|
toast({
|
||||||
</Flex>
|
title: `Failed to copy JWT config`,
|
||||||
<Stack spacing={6} padding="2% 0%">
|
isClosable: true,
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
status: 'error',
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
position: 'bottom-right',
|
||||||
<Text fontSize="sm">JWT Type:</Text>
|
});
|
||||||
</Flex>
|
}
|
||||||
<Flex
|
};
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
|
||||||
mt={isNotSmallerScreen ? "0" : "2"}
|
return (
|
||||||
>
|
<div>
|
||||||
<InputField
|
{' '}
|
||||||
borderRadius={5}
|
<Flex
|
||||||
variables={variables}
|
borderRadius={5}
|
||||||
setVariables={setVariables}
|
width="100%"
|
||||||
inputType={SelectInputType}
|
justifyContent="space-between"
|
||||||
value={SelectInputType}
|
alignItems="center"
|
||||||
options={{
|
paddingTop="2%"
|
||||||
...HMACEncryptionType,
|
>
|
||||||
...RSAEncryptionType,
|
<Text
|
||||||
...ECDSAEncryptionType,
|
fontSize={isNotSmallerScreen ? 'md' : 'sm'}
|
||||||
}}
|
fontWeight="bold"
|
||||||
/>
|
mb={5}
|
||||||
</Flex>
|
>
|
||||||
</Flex>
|
JWT (JSON Web Tokens) Configurations
|
||||||
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
|
</Text>
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex mb={7}>
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
<Button
|
||||||
<Text fontSize="sm">JWT Secret</Text>
|
colorScheme="blue"
|
||||||
</Flex>
|
h="1.75rem"
|
||||||
<Center
|
size="sm"
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
variant="ghost"
|
||||||
mt={isNotSmallerScreen ? "0" : "2"}
|
onClick={copyJSON}
|
||||||
>
|
>
|
||||||
<InputField
|
Copy As JSON Config
|
||||||
borderRadius={5}
|
</Button>
|
||||||
variables={variables}
|
<GenerateKeysModal jwtType={variables.JWT_TYPE} getData={getData} />
|
||||||
setVariables={setVariables}
|
</Flex>
|
||||||
fieldVisibility={fieldVisibility}
|
</Flex>
|
||||||
setFieldVisibility={setFieldVisibility}
|
<Stack spacing={6} padding="2% 0%">
|
||||||
inputType={HiddenInputType.JWT_SECRET}
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
/>
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
</Center>
|
<Text fontSize="sm">JWT Type:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
<Flex
|
||||||
<>
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
mt={isNotSmallerScreen ? '0' : '2'}
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
>
|
||||||
<Text fontSize="sm">Public Key</Text>
|
<InputField
|
||||||
</Flex>
|
borderRadius={5}
|
||||||
<Center
|
variables={variables}
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
setVariables={setVariables}
|
||||||
mt={isNotSmallerScreen ? "0" : "2"}
|
inputType={SelectInputType}
|
||||||
>
|
value={SelectInputType}
|
||||||
<InputField
|
options={{
|
||||||
borderRadius={5}
|
...HMACEncryptionType,
|
||||||
variables={variables}
|
...RSAEncryptionType,
|
||||||
setVariables={setVariables}
|
...ECDSAEncryptionType,
|
||||||
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
|
}}
|
||||||
placeholder="Add public key here"
|
/>
|
||||||
minH="25vh"
|
</Flex>
|
||||||
/>
|
</Flex>
|
||||||
</Center>
|
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
|
||||||
</Flex>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
<Text fontSize="sm">JWT Secret</Text>
|
||||||
<Text fontSize="sm">Private Key</Text>
|
</Flex>
|
||||||
</Flex>
|
<Center
|
||||||
<Center
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
mt={isNotSmallerScreen ? '0' : '2'}
|
||||||
mt={isNotSmallerScreen ? "0" : "2"}
|
>
|
||||||
>
|
<InputField
|
||||||
<InputField
|
borderRadius={5}
|
||||||
borderRadius={5}
|
variables={variables}
|
||||||
variables={variables}
|
setVariables={setVariables}
|
||||||
setVariables={setVariables}
|
fieldVisibility={fieldVisibility}
|
||||||
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
|
setFieldVisibility={setFieldVisibility}
|
||||||
placeholder="Add private key here"
|
inputType={HiddenInputType.JWT_SECRET}
|
||||||
minH="25vh"
|
/>
|
||||||
/>
|
</Center>
|
||||||
</Center>
|
</Flex>
|
||||||
</Flex>
|
) : (
|
||||||
</>
|
<>
|
||||||
)}
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
<Flex
|
<Text fontSize="sm">Public Key</Text>
|
||||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
</Flex>
|
||||||
justifyContent="start"
|
<Center
|
||||||
alignItems="center"
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
>
|
mt={isNotSmallerScreen ? '0' : '2'}
|
||||||
<Text fontSize="sm" orientation="vertical">
|
>
|
||||||
JWT Role Claim:
|
<InputField
|
||||||
</Text>
|
borderRadius={5}
|
||||||
</Flex>
|
variables={variables}
|
||||||
<Center
|
setVariables={setVariables}
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
|
||||||
mt={isNotSmallerScreen ? "0" : "2"}
|
placeholder="Add public key here"
|
||||||
>
|
minH="25vh"
|
||||||
<InputField
|
/>
|
||||||
borderRadius={5}
|
</Center>
|
||||||
variables={variables}
|
</Flex>
|
||||||
setVariables={setVariables}
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
inputType={TextInputType.JWT_ROLE_CLAIM}
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
/>
|
<Text fontSize="sm">Private Key</Text>
|
||||||
</Center>
|
</Flex>
|
||||||
</Flex>
|
<Center
|
||||||
</Stack>
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
</div>
|
mt={isNotSmallerScreen ? '0' : '2'}
|
||||||
);
|
>
|
||||||
|
<InputField
|
||||||
|
borderRadius={5}
|
||||||
|
variables={variables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
|
||||||
|
placeholder="Add private key here"
|
||||||
|
minH="25vh"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
|
<Flex
|
||||||
|
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm" orientation="vertical">
|
||||||
|
JWT Role Claim:
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center
|
||||||
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
|
mt={isNotSmallerScreen ? '0' : '2'}
|
||||||
|
>
|
||||||
|
<InputField
|
||||||
|
borderRadius={5}
|
||||||
|
variables={variables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={TextInputType.JWT_ROLE_CLAIM}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default JSTConfigurations;
|
export default JSTConfigurations;
|
||||||
|
@@ -15,6 +15,7 @@ import {
|
|||||||
FaFacebookF,
|
FaFacebookF,
|
||||||
FaLinkedin,
|
FaLinkedin,
|
||||||
FaApple,
|
FaApple,
|
||||||
|
FaTwitter,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { TextInputType, HiddenInputType } from '../../constants';
|
import { TextInputType, HiddenInputType } from '../../constants';
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ const OAuthConfig = ({
|
|||||||
fieldVisibility={fieldVisibility}
|
fieldVisibility={fieldVisibility}
|
||||||
setFieldVisibility={setFieldVisibility}
|
setFieldVisibility={setFieldVisibility}
|
||||||
inputType={HiddenInputType.GOOGLE_CLIENT_SECRET}
|
inputType={HiddenInputType.GOOGLE_CLIENT_SECRET}
|
||||||
placeholder="Google Secret"
|
placeholder="Google Client Secret"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -146,7 +147,7 @@ const OAuthConfig = ({
|
|||||||
fieldVisibility={fieldVisibility}
|
fieldVisibility={fieldVisibility}
|
||||||
setFieldVisibility={setFieldVisibility}
|
setFieldVisibility={setFieldVisibility}
|
||||||
inputType={HiddenInputType.GITHUB_CLIENT_SECRET}
|
inputType={HiddenInputType.GITHUB_CLIENT_SECRET}
|
||||||
placeholder="Github Secret"
|
placeholder="Github Client Secret"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -184,7 +185,7 @@ const OAuthConfig = ({
|
|||||||
fieldVisibility={fieldVisibility}
|
fieldVisibility={fieldVisibility}
|
||||||
setFieldVisibility={setFieldVisibility}
|
setFieldVisibility={setFieldVisibility}
|
||||||
inputType={HiddenInputType.FACEBOOK_CLIENT_SECRET}
|
inputType={HiddenInputType.FACEBOOK_CLIENT_SECRET}
|
||||||
placeholder="Facebook Secret"
|
placeholder="Facebook Client Secret"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -260,7 +261,45 @@ const OAuthConfig = ({
|
|||||||
fieldVisibility={fieldVisibility}
|
fieldVisibility={fieldVisibility}
|
||||||
setFieldVisibility={setFieldVisibility}
|
setFieldVisibility={setFieldVisibility}
|
||||||
inputType={HiddenInputType.APPLE_CLIENT_SECRET}
|
inputType={HiddenInputType.APPLE_CLIENT_SECRET}
|
||||||
placeholder="Apple CLient Secret"
|
placeholder="Apple Client Secret"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
|
<Center
|
||||||
|
w={isNotSmallerScreen ? '55px' : '35px'}
|
||||||
|
h="35px"
|
||||||
|
marginRight="1.5%"
|
||||||
|
border="1px solid #3b5998"
|
||||||
|
borderRadius="5px"
|
||||||
|
>
|
||||||
|
<FaTwitter />
|
||||||
|
</Center>
|
||||||
|
<Center
|
||||||
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
|
marginRight="1.5%"
|
||||||
|
>
|
||||||
|
<InputField
|
||||||
|
borderRadius={5}
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={TextInputType.TWITTER_CLIENT_ID}
|
||||||
|
placeholder="Twitter 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.TWITTER_CLIENT_SECRET}
|
||||||
|
placeholder="Twitter Client Secret"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@@ -12,7 +12,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Textarea,
|
Textarea,
|
||||||
Switch,
|
Switch,
|
||||||
Code,
|
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
|
@@ -30,6 +30,8 @@ import {
|
|||||||
FiMenu,
|
FiMenu,
|
||||||
FiUsers,
|
FiUsers,
|
||||||
FiChevronDown,
|
FiChevronDown,
|
||||||
|
FiLink,
|
||||||
|
FiFileText,
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
import { BiCustomize } from 'react-icons/bi';
|
import { BiCustomize } from 'react-icons/bi';
|
||||||
import { AiOutlineKey } from 'react-icons/ai';
|
import { AiOutlineKey } from 'react-icons/ai';
|
||||||
@@ -111,6 +113,8 @@ const LinkItems: Array<LinkItemProps> = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ name: 'Users', icon: FiUsers, route: '/users' },
|
{ name: 'Users', icon: FiUsers, route: '/users' },
|
||||||
|
{ name: 'Webhooks', icon: FiLink, route: '/webhooks' },
|
||||||
|
{ name: 'Email Templates', icon: FiFileText, route: '/email-templates' },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface SidebarProps extends BoxProps {
|
interface SidebarProps extends BoxProps {
|
||||||
|
457
dashboard/src/components/UpdateEmailTemplateModal.tsx
Normal file
457
dashboard/src/components/UpdateEmailTemplateModal.tsx
Normal file
@@ -0,0 +1,457 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Select,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
Alert,
|
||||||
|
AlertIcon,
|
||||||
|
Collapse,
|
||||||
|
Box,
|
||||||
|
TableContainer,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Code,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { FaPlus, FaAngleDown, FaAngleUp } from 'react-icons/fa';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import EmailEditor from 'react-email-editor';
|
||||||
|
import {
|
||||||
|
UpdateModalViews,
|
||||||
|
EmailTemplateInputDataFields,
|
||||||
|
emailTemplateEventNames,
|
||||||
|
emailTemplateVariables,
|
||||||
|
} from '../constants';
|
||||||
|
import { capitalizeFirstLetter } from '../utils';
|
||||||
|
import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation';
|
||||||
|
|
||||||
|
interface selectedEmailTemplateDataTypes {
|
||||||
|
[EmailTemplateInputDataFields.ID]: string;
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]: string;
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: string;
|
||||||
|
[EmailTemplateInputDataFields.CREATED_AT]: number;
|
||||||
|
[EmailTemplateInputDataFields.TEMPLATE]: string;
|
||||||
|
[EmailTemplateInputDataFields.DESIGN]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateEmailTemplateInputPropTypes {
|
||||||
|
view: UpdateModalViews;
|
||||||
|
selectedTemplate?: selectedEmailTemplateDataTypes;
|
||||||
|
fetchEmailTemplatesData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface templateVariableDataTypes {
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface emailTemplateDataType {
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]: string;
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface validatorDataType {
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initTemplateData: emailTemplateDataType = {
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup,
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTemplateValidatorData: validatorDataType = {
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateEmailTemplate = ({
|
||||||
|
view,
|
||||||
|
selectedTemplate,
|
||||||
|
fetchEmailTemplatesData,
|
||||||
|
}: UpdateEmailTemplateInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const emailEditorRef = useRef(null);
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [templateVariables, setTemplateVariables] = useState<
|
||||||
|
templateVariableDataTypes[]
|
||||||
|
>([]);
|
||||||
|
const [templateData, setTemplateData] = useState<emailTemplateDataType>({
|
||||||
|
...initTemplateData,
|
||||||
|
});
|
||||||
|
const [validator, setValidator] = useState<validatorDataType>({
|
||||||
|
...initTemplateValidatorData,
|
||||||
|
});
|
||||||
|
const [isDynamicVariableInfoOpen, setIsDynamicVariableInfoOpen] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
|
||||||
|
const onReady = () => {
|
||||||
|
if (selectedTemplate) {
|
||||||
|
const { design } = selectedTemplate;
|
||||||
|
try {
|
||||||
|
const designData = JSON.parse(design);
|
||||||
|
// @ts-ignore
|
||||||
|
emailEditorRef.current.editor.loadDesign(designData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputChangehandler = (inputType: string, value: any) => {
|
||||||
|
if (inputType !== EmailTemplateInputDataFields.EVENT_NAME) {
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[inputType]: value?.trim().length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTemplateData({ ...templateData, [inputType]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateData = () => {
|
||||||
|
return (
|
||||||
|
!loading &&
|
||||||
|
templateData[EmailTemplateInputDataFields.EVENT_NAME].length > 0 &&
|
||||||
|
templateData[EmailTemplateInputDataFields.SUBJECT].length > 0 &&
|
||||||
|
validator[EmailTemplateInputDataFields.SUBJECT]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const resetData = () => {
|
||||||
|
if (selectedTemplate) {
|
||||||
|
setTemplateData(selectedTemplate);
|
||||||
|
} else {
|
||||||
|
setTemplateData({ ...initTemplateData });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isOpen &&
|
||||||
|
view === UpdateModalViews.Edit &&
|
||||||
|
selectedTemplate &&
|
||||||
|
Object.keys(selectedTemplate || {}).length
|
||||||
|
) {
|
||||||
|
const { id, created_at, template, design, ...rest } = selectedTemplate;
|
||||||
|
setTemplateData(rest);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
useEffect(() => {
|
||||||
|
const updatedTemplateVariables = Object.entries(
|
||||||
|
emailTemplateVariables
|
||||||
|
).reduce((acc, [key, val]): any => {
|
||||||
|
if (
|
||||||
|
(templateData[EmailTemplateInputDataFields.EVENT_NAME] !==
|
||||||
|
emailTemplateEventNames['Verify Otp'] &&
|
||||||
|
val === emailTemplateVariables.otp) ||
|
||||||
|
(templateData[EmailTemplateInputDataFields.EVENT_NAME] ===
|
||||||
|
emailTemplateEventNames['Verify Otp'] &&
|
||||||
|
val === emailTemplateVariables.verification_url)
|
||||||
|
) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...acc,
|
||||||
|
{
|
||||||
|
text: key,
|
||||||
|
value: val.value,
|
||||||
|
description: val.description,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
setTemplateVariables(updatedTemplateVariables);
|
||||||
|
}, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{view === UpdateModalViews.ADD ? (
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onOpen}
|
||||||
|
isDisabled={false}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Center h="100%">Add Template</Center>{' '}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<MenuItem onClick={onOpen}>Edit</MenuItem>
|
||||||
|
)}
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
resetData();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
size="6xl"
|
||||||
|
>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
{view === UpdateModalViews.ADD
|
||||||
|
? 'Add New Email Template'
|
||||||
|
: 'Edit Email Template'}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
border="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor="gray.200"
|
||||||
|
p="5"
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
status="info"
|
||||||
|
onClick={() =>
|
||||||
|
setIsDynamicVariableInfoOpen(!isDynamicVariableInfoOpen)
|
||||||
|
}
|
||||||
|
borderRadius="5"
|
||||||
|
marginBottom={5}
|
||||||
|
cursor="pointer"
|
||||||
|
fontSize="sm"
|
||||||
|
>
|
||||||
|
<AlertIcon />
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Box width="85%">
|
||||||
|
<b>Note:</b> You can add set of dynamic variables to subject
|
||||||
|
and email body. Click here to see the set of dynamic
|
||||||
|
variables.
|
||||||
|
</Box>
|
||||||
|
{isDynamicVariableInfoOpen ? <FaAngleUp /> : <FaAngleDown />}
|
||||||
|
</Flex>
|
||||||
|
</Alert>
|
||||||
|
<Collapse
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
in={isDynamicVariableInfoOpen}
|
||||||
|
>
|
||||||
|
<TableContainer
|
||||||
|
background="gray.100"
|
||||||
|
borderRadius={5}
|
||||||
|
height={200}
|
||||||
|
width="100%"
|
||||||
|
overflowY="auto"
|
||||||
|
overflowWrap="break-word"
|
||||||
|
>
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Variable</Th>
|
||||||
|
<Th>Description</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{templateVariables.map((i) => (
|
||||||
|
<Tr key={i.text}>
|
||||||
|
<Td>
|
||||||
|
<Code fontSize="sm">{`{{.${i.text}}}`}</Code>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Text
|
||||||
|
size="sm"
|
||||||
|
fontSize="sm"
|
||||||
|
overflowWrap="break-word"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
{i.description}
|
||||||
|
</Text>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Collapse>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Event Name</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<Select
|
||||||
|
size="md"
|
||||||
|
value={
|
||||||
|
templateData[EmailTemplateInputDataFields.EVENT_NAME]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
EmailTemplateInputDataFields.EVENT_NAME,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{Object.entries(emailTemplateEventNames).map(
|
||||||
|
([key, value]: any) => (
|
||||||
|
<option value={value} key={key}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Subject</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<InputGroup size="md">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="Subject Line"
|
||||||
|
value={templateData[EmailTemplateInputDataFields.SUBJECT]}
|
||||||
|
isInvalid={
|
||||||
|
!validator[EmailTemplateInputDataFields.SUBJECT]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
EmailTemplateInputDataFields.SUBJECT,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
Template Body
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
alignItems="center"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="gray.200"
|
||||||
|
>
|
||||||
|
<EmailEditor ref={emailEditorRef} onReady={onReady} />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={resetData}
|
||||||
|
isDisabled={loading}
|
||||||
|
marginRight="5"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
isLoading={loading}
|
||||||
|
onClick={saveData}
|
||||||
|
isDisabled={!validateData()}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Save
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateEmailTemplate;
|
663
dashboard/src/components/UpdateWebhookModal.tsx
Normal file
663
dashboard/src/components/UpdateWebhookModal.tsx
Normal file
@@ -0,0 +1,663 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Code,
|
||||||
|
Collapse,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputRightElement,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
Alert,
|
||||||
|
AlertIcon,
|
||||||
|
Divider,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaAngleDown,
|
||||||
|
FaAngleUp,
|
||||||
|
FaMinusCircle,
|
||||||
|
FaPlus,
|
||||||
|
FaRegClone,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
webhookEventNames,
|
||||||
|
ArrayInputOperations,
|
||||||
|
WebhookInputDataFields,
|
||||||
|
WebhookInputHeaderFields,
|
||||||
|
UpdateModalViews,
|
||||||
|
webhookVerifiedStatus,
|
||||||
|
webhookPayloadExample,
|
||||||
|
} from '../constants';
|
||||||
|
import {
|
||||||
|
capitalizeFirstLetter,
|
||||||
|
copyTextToClipboard,
|
||||||
|
validateURI,
|
||||||
|
} from '../utils';
|
||||||
|
import { AddWebhook, EditWebhook, TestEndpoint } from '../graphql/mutation';
|
||||||
|
import { BiCheckCircle, BiError, BiErrorCircle } from 'react-icons/bi';
|
||||||
|
|
||||||
|
interface headersDataType {
|
||||||
|
[WebhookInputHeaderFields.KEY]: string;
|
||||||
|
[WebhookInputHeaderFields.VALUE]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface headersValidatorDataType {
|
||||||
|
[WebhookInputHeaderFields.KEY]: boolean;
|
||||||
|
[WebhookInputHeaderFields.VALUE]: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface selecetdWebhookDataTypes {
|
||||||
|
[WebhookInputDataFields.ID]: string;
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: string;
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: string;
|
||||||
|
[WebhookInputDataFields.ENABLED]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateWebhookModalInputPropTypes {
|
||||||
|
view: UpdateModalViews;
|
||||||
|
selectedWebhook?: selecetdWebhookDataTypes;
|
||||||
|
fetchWebookData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initHeadersData: headersDataType = {
|
||||||
|
[WebhookInputHeaderFields.KEY]: '',
|
||||||
|
[WebhookInputHeaderFields.VALUE]: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const initHeadersValidatorData: headersValidatorDataType = {
|
||||||
|
[WebhookInputHeaderFields.KEY]: true,
|
||||||
|
[WebhookInputHeaderFields.VALUE]: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface webhookDataType {
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: string;
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: string;
|
||||||
|
[WebhookInputDataFields.ENABLED]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]: headersDataType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface validatorDataType {
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]: headersValidatorDataType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initWebhookData: webhookDataType = {
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames['User login'],
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: '',
|
||||||
|
[WebhookInputDataFields.ENABLED]: true,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const initWebhookValidatorData: validatorDataType = {
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: true,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersValidatorData }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateWebhookModal = ({
|
||||||
|
view,
|
||||||
|
selectedWebhook,
|
||||||
|
fetchWebookData,
|
||||||
|
}: UpdateWebhookModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [verifyingEndpoint, setVerifyingEndpoint] = useState<boolean>(false);
|
||||||
|
const [isShowingPayload, setIsShowingPayload] = useState<boolean>(false);
|
||||||
|
const [webhook, setWebhook] = useState<webhookDataType>({
|
||||||
|
...initWebhookData,
|
||||||
|
});
|
||||||
|
const [validator, setValidator] = useState<validatorDataType>({
|
||||||
|
...initWebhookValidatorData,
|
||||||
|
});
|
||||||
|
const [verifiedStatus, setVerifiedStatus] = useState<webhookVerifiedStatus>(
|
||||||
|
webhookVerifiedStatus.PENDING
|
||||||
|
);
|
||||||
|
const inputChangehandler = (
|
||||||
|
inputType: string,
|
||||||
|
value: any,
|
||||||
|
headerInputType: string = WebhookInputHeaderFields.KEY,
|
||||||
|
headerIndex: number = 0
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
verifiedStatus !== webhookVerifiedStatus.PENDING &&
|
||||||
|
inputType !== WebhookInputDataFields.ENABLED
|
||||||
|
) {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.PENDING);
|
||||||
|
}
|
||||||
|
switch (inputType) {
|
||||||
|
case WebhookInputDataFields.EVENT_NAME:
|
||||||
|
setWebhook({ ...webhook, [inputType]: value });
|
||||||
|
break;
|
||||||
|
case WebhookInputDataFields.ENDPOINT:
|
||||||
|
setWebhook({ ...webhook, [inputType]: value });
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: validateURI(value),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case WebhookInputDataFields.ENABLED:
|
||||||
|
setWebhook({ ...webhook, [inputType]: value });
|
||||||
|
break;
|
||||||
|
case WebhookInputDataFields.HEADERS:
|
||||||
|
const updatedHeaders: any = [
|
||||||
|
...webhook[WebhookInputDataFields.HEADERS],
|
||||||
|
];
|
||||||
|
const updatedHeadersValidatorData: any = [
|
||||||
|
...validator[WebhookInputDataFields.HEADERS],
|
||||||
|
];
|
||||||
|
const otherHeaderInputType =
|
||||||
|
headerInputType === WebhookInputHeaderFields.KEY
|
||||||
|
? WebhookInputHeaderFields.VALUE
|
||||||
|
: WebhookInputHeaderFields.KEY;
|
||||||
|
updatedHeaders[headerIndex][headerInputType] = value;
|
||||||
|
updatedHeadersValidatorData[headerIndex][headerInputType] =
|
||||||
|
value.length > 0
|
||||||
|
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
|
||||||
|
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
|
||||||
|
updatedHeadersValidatorData[headerIndex][otherHeaderInputType] =
|
||||||
|
value.length > 0
|
||||||
|
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
|
||||||
|
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
|
||||||
|
setWebhook({ ...webhook, [inputType]: updatedHeaders });
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[inputType]: updatedHeadersValidatorData,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const updateHeaders = (operation: string, index: number = 0) => {
|
||||||
|
if (verifiedStatus !== webhookVerifiedStatus.PENDING) {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.PENDING);
|
||||||
|
}
|
||||||
|
switch (operation) {
|
||||||
|
case ArrayInputOperations.APPEND:
|
||||||
|
setWebhook({
|
||||||
|
...webhook,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [
|
||||||
|
...(webhook?.[WebhookInputDataFields.HEADERS] || []),
|
||||||
|
{ ...initHeadersData },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [
|
||||||
|
...(validator?.[WebhookInputDataFields.HEADERS] || []),
|
||||||
|
{ ...initHeadersValidatorData },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ArrayInputOperations.REMOVE:
|
||||||
|
if (webhook?.[WebhookInputDataFields.HEADERS]?.length) {
|
||||||
|
const updatedHeaders = [...webhook[WebhookInputDataFields.HEADERS]];
|
||||||
|
updatedHeaders.splice(index, 1);
|
||||||
|
setWebhook({
|
||||||
|
...webhook,
|
||||||
|
[WebhookInputDataFields.HEADERS]: updatedHeaders,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (validator?.[WebhookInputDataFields.HEADERS]?.length) {
|
||||||
|
const updatedHeadersData = [
|
||||||
|
...validator[WebhookInputDataFields.HEADERS],
|
||||||
|
];
|
||||||
|
updatedHeadersData.splice(index, 1);
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.HEADERS]: updatedHeadersData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const validateData = () => {
|
||||||
|
return (
|
||||||
|
!loading &&
|
||||||
|
!verifyingEndpoint &&
|
||||||
|
webhook[WebhookInputDataFields.EVENT_NAME].length > 0 &&
|
||||||
|
webhook[WebhookInputDataFields.ENDPOINT].length > 0 &&
|
||||||
|
validator[WebhookInputDataFields.ENDPOINT] &&
|
||||||
|
!validator[WebhookInputDataFields.HEADERS].some(
|
||||||
|
(headerData: headersValidatorDataType) =>
|
||||||
|
!headerData.key || !headerData.value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const getParams = () => {
|
||||||
|
let params: any = {
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]:
|
||||||
|
webhook[WebhookInputDataFields.EVENT_NAME],
|
||||||
|
[WebhookInputDataFields.ENDPOINT]:
|
||||||
|
webhook[WebhookInputDataFields.ENDPOINT],
|
||||||
|
[WebhookInputDataFields.ENABLED]: webhook[WebhookInputDataFields.ENABLED],
|
||||||
|
[WebhookInputDataFields.HEADERS]: {},
|
||||||
|
};
|
||||||
|
if (webhook[WebhookInputDataFields.HEADERS].length) {
|
||||||
|
const headers = webhook[WebhookInputDataFields.HEADERS].reduce(
|
||||||
|
(acc, data) => {
|
||||||
|
return data.key ? { ...acc, [data.key]: data.value } : acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
if (Object.keys(headers).length) {
|
||||||
|
params[WebhookInputDataFields.HEADERS] = headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
const saveData = async () => {
|
||||||
|
if (!validateData()) return;
|
||||||
|
setLoading(true);
|
||||||
|
const params = getParams();
|
||||||
|
let res: any = {};
|
||||||
|
if (
|
||||||
|
view === UpdateModalViews.Edit &&
|
||||||
|
selectedWebhook?.[WebhookInputDataFields.ID]
|
||||||
|
) {
|
||||||
|
res = await client
|
||||||
|
.mutation(EditWebhook, {
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
id: selectedWebhook[WebhookInputDataFields.ID],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
} else {
|
||||||
|
res = await client.mutation(AddWebhook, { 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_webhook || res.data?._update_webhook) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(
|
||||||
|
res.data?._add_webhook?.message || res.data?._update_webhook?.message
|
||||||
|
),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
setWebhook({
|
||||||
|
...initWebhookData,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
|
||||||
|
});
|
||||||
|
setValidator({ ...initWebhookValidatorData });
|
||||||
|
fetchWebookData();
|
||||||
|
}
|
||||||
|
view === UpdateModalViews.ADD && onClose();
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isOpen &&
|
||||||
|
view === UpdateModalViews.Edit &&
|
||||||
|
selectedWebhook &&
|
||||||
|
Object.keys(selectedWebhook || {}).length
|
||||||
|
) {
|
||||||
|
const { headers, ...rest } = selectedWebhook;
|
||||||
|
const headerItems = Object.entries(headers || {});
|
||||||
|
if (headerItems.length) {
|
||||||
|
let formattedHeadersData = headerItems.map((headerData) => {
|
||||||
|
return {
|
||||||
|
[WebhookInputHeaderFields.KEY]: headerData[0],
|
||||||
|
[WebhookInputHeaderFields.VALUE]: headerData[1],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setWebhook({
|
||||||
|
...rest,
|
||||||
|
[WebhookInputDataFields.HEADERS]: formattedHeadersData,
|
||||||
|
});
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.HEADERS]: new Array(
|
||||||
|
formattedHeadersData.length
|
||||||
|
)
|
||||||
|
.fill({})
|
||||||
|
.map(() => ({ ...initHeadersValidatorData })),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setWebhook({
|
||||||
|
...rest,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
const verifyEndpoint = async () => {
|
||||||
|
if (!validateData()) return;
|
||||||
|
setVerifyingEndpoint(true);
|
||||||
|
const { [WebhookInputDataFields.ENABLED]: _, ...params } = getParams();
|
||||||
|
const res = await client.mutation(TestEndpoint, { params }).toPromise();
|
||||||
|
if (
|
||||||
|
res.data?._test_endpoint?.http_status >= 200 &&
|
||||||
|
res.data?._test_endpoint?.http_status < 400
|
||||||
|
) {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.VERIFIED);
|
||||||
|
} else {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.NOT_VERIFIED);
|
||||||
|
}
|
||||||
|
setVerifyingEndpoint(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{view === UpdateModalViews.ADD ? (
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onOpen}
|
||||||
|
isDisabled={false}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Center h="100%">Add Webhook</Center>{' '}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<MenuItem onClick={onOpen}>Edit</MenuItem>
|
||||||
|
)}
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} size="3xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
{view === UpdateModalViews.ADD ? 'Add New Webhook' : 'Edit Webhook'}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
border="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor="gray.200"
|
||||||
|
p="5"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Event Name</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<Select
|
||||||
|
size="md"
|
||||||
|
value={webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.EVENT_NAME,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{Object.entries(webhookEventNames).map(
|
||||||
|
([key, value]: any) => (
|
||||||
|
<option value={value} key={key}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="5%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Endpoint</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<InputGroup size="md">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="https://domain.com/webhook"
|
||||||
|
value={webhook[WebhookInputDataFields.ENDPOINT]}
|
||||||
|
isInvalid={!validator[WebhookInputDataFields.ENDPOINT]}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.ENDPOINT,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="5%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Enabled</Flex>
|
||||||
|
<Flex w="25%" justifyContent="space-between">
|
||||||
|
<Text h="75%" fontWeight="bold" marginRight="2">
|
||||||
|
Off
|
||||||
|
</Text>
|
||||||
|
<Switch
|
||||||
|
size="md"
|
||||||
|
isChecked={webhook[WebhookInputDataFields.ENABLED]}
|
||||||
|
onChange={() =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.ENABLED,
|
||||||
|
!webhook[WebhookInputDataFields.ENABLED]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text h="75%" fontWeight="bold" marginLeft="2">
|
||||||
|
On
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="5%"
|
||||||
|
>
|
||||||
|
<Flex>Headers</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
paddingRight="0"
|
||||||
|
onClick={() => updateHeaders(ArrayInputOperations.APPEND)}
|
||||||
|
>
|
||||||
|
Add more Headers
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex flexDirection="column" maxH={220} overflowY="auto">
|
||||||
|
{webhook[WebhookInputDataFields.HEADERS]?.map(
|
||||||
|
(headerData, index) => (
|
||||||
|
<Flex
|
||||||
|
key={`header-data-${index}`}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<InputGroup size="md" marginBottom="2.5%">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="key"
|
||||||
|
value={headerData[WebhookInputHeaderFields.KEY]}
|
||||||
|
isInvalid={
|
||||||
|
!validator[WebhookInputDataFields.HEADERS][index]?.[
|
||||||
|
WebhookInputHeaderFields.KEY
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.HEADERS,
|
||||||
|
e.target.value,
|
||||||
|
WebhookInputHeaderFields.KEY,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
width="30%"
|
||||||
|
marginRight="2%"
|
||||||
|
/>
|
||||||
|
<Center marginRight="2%">
|
||||||
|
<Text fontWeight="bold">:</Text>
|
||||||
|
</Center>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="value"
|
||||||
|
value={headerData[WebhookInputHeaderFields.VALUE]}
|
||||||
|
isInvalid={
|
||||||
|
!validator[WebhookInputDataFields.HEADERS][index]?.[
|
||||||
|
WebhookInputHeaderFields.VALUE
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.HEADERS,
|
||||||
|
e.target.value,
|
||||||
|
WebhookInputHeaderFields.VALUE,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
width="65%"
|
||||||
|
/>
|
||||||
|
<InputRightElement width="3rem">
|
||||||
|
<Button
|
||||||
|
width="6rem"
|
||||||
|
colorScheme="blackAlpha"
|
||||||
|
variant="ghost"
|
||||||
|
padding="0"
|
||||||
|
onClick={() =>
|
||||||
|
updateHeaders(ArrayInputOperations.REMOVE, index)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaMinusCircle />
|
||||||
|
</Button>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Divider marginY={5} />
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
status="info"
|
||||||
|
onClick={() => setIsShowingPayload(!isShowingPayload)}
|
||||||
|
borderRadius="5"
|
||||||
|
cursor="pointer"
|
||||||
|
fontSize="sm"
|
||||||
|
>
|
||||||
|
<AlertIcon />
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
Checkout the example payload
|
||||||
|
{isShowingPayload ? <FaAngleUp /> : <FaAngleDown />}
|
||||||
|
</Flex>
|
||||||
|
</Alert>
|
||||||
|
<Collapse
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
in={isShowingPayload}
|
||||||
|
>
|
||||||
|
<Code
|
||||||
|
width="inherit"
|
||||||
|
borderRadius={5}
|
||||||
|
padding={2}
|
||||||
|
position="relative"
|
||||||
|
>
|
||||||
|
<pre style={{ overflow: 'auto' }}>
|
||||||
|
{webhookPayloadExample}
|
||||||
|
</pre>
|
||||||
|
{isShowingPayload && (
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
top={4}
|
||||||
|
right={4}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => copyTextToClipboard(webhookPayloadExample)}
|
||||||
|
>
|
||||||
|
<FaRegClone color="#bfbfbf" />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Code>
|
||||||
|
</Collapse>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
colorScheme={
|
||||||
|
verifiedStatus === webhookVerifiedStatus.VERIFIED
|
||||||
|
? 'green'
|
||||||
|
: verifiedStatus === webhookVerifiedStatus.PENDING
|
||||||
|
? 'yellow'
|
||||||
|
: 'red'
|
||||||
|
}
|
||||||
|
variant="outline"
|
||||||
|
onClick={verifyEndpoint}
|
||||||
|
isLoading={verifyingEndpoint}
|
||||||
|
isDisabled={!validateData()}
|
||||||
|
marginRight="5"
|
||||||
|
leftIcon={
|
||||||
|
verifiedStatus === webhookVerifiedStatus.VERIFIED ? (
|
||||||
|
<BiCheckCircle />
|
||||||
|
) : verifiedStatus === webhookVerifiedStatus.PENDING ? (
|
||||||
|
<BiErrorCircle />
|
||||||
|
) : (
|
||||||
|
<BiError />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{verifiedStatus === webhookVerifiedStatus.VERIFIED
|
||||||
|
? 'Endpoint Verified'
|
||||||
|
: verifiedStatus === webhookVerifiedStatus.PENDING
|
||||||
|
? 'Test Endpoint'
|
||||||
|
: 'Endpoint Not Verified'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={saveData}
|
||||||
|
isDisabled={!validateData()}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Save
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateWebhookModal;
|
426
dashboard/src/components/ViewWebhookLogsModal.tsx
Normal file
426
dashboard/src/components/ViewWebhookLogsModal.tsx
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
Tbody,
|
||||||
|
IconButton,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
TableCaption,
|
||||||
|
Tooltip,
|
||||||
|
Td,
|
||||||
|
Tag,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
FaAngleDoubleLeft,
|
||||||
|
FaAngleDoubleRight,
|
||||||
|
FaAngleLeft,
|
||||||
|
FaAngleRight,
|
||||||
|
FaExclamationCircle,
|
||||||
|
FaRegClone,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import { copyTextToClipboard } from '../utils';
|
||||||
|
import { WebhookLogsQuery } from '../graphql/queries';
|
||||||
|
import { pageLimits } from '../constants';
|
||||||
|
|
||||||
|
interface paginationPropTypes {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
offset: number;
|
||||||
|
total: number;
|
||||||
|
maxPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface deleteWebhookModalInputPropTypes {
|
||||||
|
webhookId: string;
|
||||||
|
eventName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface webhookLogsDataTypes {
|
||||||
|
id: string;
|
||||||
|
http_status: number;
|
||||||
|
request: string;
|
||||||
|
response: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewWebhookLogsModal = ({
|
||||||
|
webhookId,
|
||||||
|
eventName,
|
||||||
|
}: deleteWebhookModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [webhookLogs, setWebhookLogs] = useState<webhookLogsDataTypes[]>([]);
|
||||||
|
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
|
||||||
|
limit: 5,
|
||||||
|
page: 1,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
maxPages: 1,
|
||||||
|
});
|
||||||
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
const { limit, total } = pagination;
|
||||||
|
if (total > 1) {
|
||||||
|
return total % limit === 0
|
||||||
|
? total / limit
|
||||||
|
: parseInt(`${total / limit}`) + 1;
|
||||||
|
} else return 1;
|
||||||
|
};
|
||||||
|
const fetchWebhookLogsData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await client
|
||||||
|
.query(WebhookLogsQuery, {
|
||||||
|
params: {
|
||||||
|
webhook_id: webhookId,
|
||||||
|
pagination: {
|
||||||
|
limit: paginationProps.limit,
|
||||||
|
page: paginationProps.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.data?._webhook_logs) {
|
||||||
|
const { pagination, webhook_logs } = res.data?._webhook_logs;
|
||||||
|
const maxPages = getMaxPages(pagination);
|
||||||
|
if (webhook_logs?.length) {
|
||||||
|
setWebhookLogs(webhook_logs);
|
||||||
|
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||||
|
} else {
|
||||||
|
if (paginationProps.page !== 1) {
|
||||||
|
setPaginationProps({
|
||||||
|
...paginationProps,
|
||||||
|
...pagination,
|
||||||
|
maxPages,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const paginationHandler = (value: Record<string, number>) => {
|
||||||
|
setPaginationProps({ ...paginationProps, ...value });
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
isOpen && fetchWebhookLogsData();
|
||||||
|
}, [isOpen, paginationProps.page, paginationProps.limit]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>View Logs</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} size="4xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Webhook Logs - {eventName}</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
border="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor="gray.200"
|
||||||
|
p="5"
|
||||||
|
>
|
||||||
|
{!loading ? (
|
||||||
|
webhookLogs.length ? (
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>ID</Th>
|
||||||
|
<Th>Created At</Th>
|
||||||
|
<Th>Http Status</Th>
|
||||||
|
<Th>Request</Th>
|
||||||
|
<Th>Response</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{webhookLogs.map((logData: webhookLogsDataTypes) => (
|
||||||
|
<Tr key={logData.id} style={{ fontSize: 14 }}>
|
||||||
|
<Td>
|
||||||
|
<Text fontSize="sm">{`${logData.id.substring(
|
||||||
|
0,
|
||||||
|
5
|
||||||
|
)}***${logData.id.substring(
|
||||||
|
logData.id.length - 5,
|
||||||
|
logData.id.length
|
||||||
|
)}`}</Text>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{dayjs(logData.created_at * 1000).format(
|
||||||
|
'MMM DD, YYYY'
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
logData.http_status >= 400 ? 'red' : 'green'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{logData.http_status}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Tooltip
|
||||||
|
bg="gray.300"
|
||||||
|
color="black"
|
||||||
|
label={logData.request || 'null'}
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
logData.request ? 'gray' : 'yellow'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{logData.request ? 'Payload' : 'No Data'}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
{logData.request && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
marginLeft="5px"
|
||||||
|
h="21px"
|
||||||
|
onClick={() =>
|
||||||
|
copyTextToClipboard(logData.request)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegClone color="#bfbfbf" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Tooltip
|
||||||
|
bg="gray.300"
|
||||||
|
color="black"
|
||||||
|
label={logData.response || 'null'}
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
logData.response ? 'gray' : 'yellow'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{logData.response ? 'Preview' : 'No Data'}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
{logData.response && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
marginLeft="5px"
|
||||||
|
h="21px"
|
||||||
|
onClick={() =>
|
||||||
|
copyTextToClipboard(logData.response)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegClone color="#bfbfbf" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 ||
|
||||||
|
paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageLimits.map((pageSize) => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >=
|
||||||
|
paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >=
|
||||||
|
paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TableCaption>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
minH="25vh"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center w="50px" marginRight="1.5%">
|
||||||
|
<FaExclamationCircle
|
||||||
|
style={{ color: '#f0f0f0', fontSize: 70 }}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
paddingRight="1%"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#d9d9d9"
|
||||||
|
>
|
||||||
|
No Data
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onClose}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Close
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewWebhookLogsModal;
|
@@ -9,6 +9,7 @@ export const TextInputType = {
|
|||||||
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
|
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
|
||||||
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
|
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
|
||||||
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
|
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
|
||||||
|
TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID',
|
||||||
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
|
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
|
||||||
REDIS_URL: 'REDIS_URL',
|
REDIS_URL: 'REDIS_URL',
|
||||||
SMTP_HOST: 'SMTP_HOST',
|
SMTP_HOST: 'SMTP_HOST',
|
||||||
@@ -35,6 +36,7 @@ export const HiddenInputType = {
|
|||||||
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
|
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
|
||||||
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
|
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
|
||||||
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
|
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
|
||||||
|
TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET',
|
||||||
JWT_SECRET: 'JWT_SECRET',
|
JWT_SECRET: 'JWT_SECRET',
|
||||||
SMTP_PASSWORD: 'SMTP_PASSWORD',
|
SMTP_PASSWORD: 'SMTP_PASSWORD',
|
||||||
ADMIN_SECRET: 'ADMIN_SECRET',
|
ADMIN_SECRET: 'ADMIN_SECRET',
|
||||||
@@ -68,6 +70,8 @@ export const SwitchInputType = {
|
|||||||
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
|
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
|
||||||
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
|
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
|
||||||
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
|
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
|
||||||
|
DISABLE_MULTI_FACTOR_AUTHENTICATION: 'DISABLE_MULTI_FACTOR_AUTHENTICATION',
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION: 'ENFORCE_MULTI_FACTOR_AUTHENTICATION',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DateInputType = {
|
export const DateInputType = {
|
||||||
@@ -108,6 +112,8 @@ export interface envVarTypes {
|
|||||||
LINKEDIN_CLIENT_SECRET: string;
|
LINKEDIN_CLIENT_SECRET: string;
|
||||||
APPLE_CLIENT_ID: string;
|
APPLE_CLIENT_ID: string;
|
||||||
APPLE_CLIENT_SECRET: string;
|
APPLE_CLIENT_SECRET: string;
|
||||||
|
TWITTER_CLIENT_ID: string;
|
||||||
|
TWITTER_CLIENT_SECRET: string;
|
||||||
ROLES: [string] | [];
|
ROLES: [string] | [];
|
||||||
DEFAULT_ROLES: [string] | [];
|
DEFAULT_ROLES: [string] | [];
|
||||||
PROTECTED_ROLES: [string] | [];
|
PROTECTED_ROLES: [string] | [];
|
||||||
@@ -138,6 +144,8 @@ export interface envVarTypes {
|
|||||||
DATABASE_TYPE: string;
|
DATABASE_TYPE: string;
|
||||||
DATABASE_URL: string;
|
DATABASE_URL: string;
|
||||||
ACCESS_TOKEN_EXPIRY_TIME: string;
|
ACCESS_TOKEN_EXPIRY_TIME: string;
|
||||||
|
DISABLE_MULTI_FACTOR_AUTHENTICATION: boolean;
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const envSubViews = {
|
export const envSubViews = {
|
||||||
@@ -153,3 +161,170 @@ export const envSubViews = {
|
|||||||
ADMIN_SECRET: 'admin-secret',
|
ADMIN_SECRET: 'admin-secret',
|
||||||
DB_CRED: 'db-cred',
|
DB_CRED: 'db-cred',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum WebhookInputDataFields {
|
||||||
|
ID = 'id',
|
||||||
|
EVENT_NAME = 'event_name',
|
||||||
|
ENDPOINT = 'endpoint',
|
||||||
|
ENABLED = 'enabled',
|
||||||
|
HEADERS = 'headers',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EmailTemplateInputDataFields {
|
||||||
|
ID = 'id',
|
||||||
|
EVENT_NAME = 'event_name',
|
||||||
|
SUBJECT = 'subject',
|
||||||
|
CREATED_AT = 'created_at',
|
||||||
|
TEMPLATE = 'template',
|
||||||
|
DESIGN = 'design',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebhookInputHeaderFields {
|
||||||
|
KEY = 'key',
|
||||||
|
VALUE = 'value',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UpdateModalViews {
|
||||||
|
ADD = 'add',
|
||||||
|
Edit = 'edit',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pageLimits: number[] = [5, 10, 15];
|
||||||
|
|
||||||
|
export const webhookEventNames = {
|
||||||
|
'User signup': 'user.signup',
|
||||||
|
'User created': 'user.created',
|
||||||
|
'User login': 'user.login',
|
||||||
|
'User deleted': 'user.deleted',
|
||||||
|
'User access enabled': 'user.access_enabled',
|
||||||
|
'User access revoked': 'user.access_revoked',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emailTemplateEventNames = {
|
||||||
|
Signup: 'basic_auth_signup',
|
||||||
|
'Magic Link Login': 'magic_link_login',
|
||||||
|
'Update Email': 'update_email',
|
||||||
|
'Forgot Password': 'forgot_password',
|
||||||
|
'Verify Otp': 'verify_otp',
|
||||||
|
'Invite member': 'invite_member',
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum webhookVerifiedStatus {
|
||||||
|
VERIFIED = 'verified',
|
||||||
|
NOT_VERIFIED = 'not_verified',
|
||||||
|
PENDING = 'verification_pending',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const emailTemplateVariables = {
|
||||||
|
'user.id': {
|
||||||
|
description: `User identifier`,
|
||||||
|
value: '{.user.id}}',
|
||||||
|
},
|
||||||
|
'user.email': {
|
||||||
|
description: 'User email address',
|
||||||
|
value: '{.user.email}}',
|
||||||
|
},
|
||||||
|
'user.given_name': {
|
||||||
|
description: `User first name`,
|
||||||
|
value: '{.user.given_name}}',
|
||||||
|
},
|
||||||
|
'user.family_name': {
|
||||||
|
description: `User last name`,
|
||||||
|
value: '{.user.family_name}}',
|
||||||
|
},
|
||||||
|
'user.middle_name': {
|
||||||
|
description: `Middle name of user`,
|
||||||
|
value: '{.user.middle_name}}',
|
||||||
|
},
|
||||||
|
'user.nickname': {
|
||||||
|
description: `Nick name of user`,
|
||||||
|
value: '{.user.nickname}}',
|
||||||
|
},
|
||||||
|
'user.preferred_username': {
|
||||||
|
description: `Username, by default it is email`,
|
||||||
|
value: '{.user.preferred_username}}',
|
||||||
|
},
|
||||||
|
'user.signup_methods': {
|
||||||
|
description: `Comma separated list of methods using which user has signed up`,
|
||||||
|
value: '{.user.signup_methods}}',
|
||||||
|
},
|
||||||
|
'user.email_verified': {
|
||||||
|
description: `Whether email is verified or not`,
|
||||||
|
value: '{.user.email_verified}}',
|
||||||
|
},
|
||||||
|
'user.picture': {
|
||||||
|
description: `URL of the user profile picture`,
|
||||||
|
value: '{.user.picture}}',
|
||||||
|
},
|
||||||
|
'user.roles': {
|
||||||
|
description: `Comma separated list of roles assigned to user`,
|
||||||
|
value: '{.user.roles}}',
|
||||||
|
},
|
||||||
|
'user.gender': {
|
||||||
|
description: `Gender of user`,
|
||||||
|
value: '{.user.gender}}',
|
||||||
|
},
|
||||||
|
'user.birthdate': {
|
||||||
|
description: `BirthDate of user`,
|
||||||
|
value: '{.user.birthdate}}',
|
||||||
|
},
|
||||||
|
'user.phone_number': {
|
||||||
|
description: `Phone number of user`,
|
||||||
|
value: '{.user.phone_number}}',
|
||||||
|
},
|
||||||
|
'user.phone_number_verified': {
|
||||||
|
description: `Whether phone number is verified or not`,
|
||||||
|
value: '{.user.phone_number_verified}}',
|
||||||
|
},
|
||||||
|
'user.created_at': {
|
||||||
|
description: `User created at time`,
|
||||||
|
value: '{.user.created_at}}',
|
||||||
|
},
|
||||||
|
'user.updated_at': {
|
||||||
|
description: `Last updated time at user`,
|
||||||
|
value: '{.user.updated_at}}',
|
||||||
|
},
|
||||||
|
'organization.name': {
|
||||||
|
description: `Organization name`,
|
||||||
|
value: '{.organization.name}}',
|
||||||
|
},
|
||||||
|
'organization.logo': {
|
||||||
|
description: `Organization logo`,
|
||||||
|
value: '{.organization.logo}}',
|
||||||
|
},
|
||||||
|
verification_url: {
|
||||||
|
description: `Verification URL in case of events other than verify otp`,
|
||||||
|
value: '{.verification_url}}',
|
||||||
|
},
|
||||||
|
otp: {
|
||||||
|
description: `OTP sent during login with Multi factor authentication`,
|
||||||
|
value: '{.otp}}',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const webhookPayloadExample: string = `{
|
||||||
|
"event_name":"user.login",
|
||||||
|
"user":{
|
||||||
|
"birthdate":null,
|
||||||
|
"created_at":1657524721,
|
||||||
|
"email":"lakhan.m.samani@gmail.com",
|
||||||
|
"email_verified":true,
|
||||||
|
"family_name":"Samani",
|
||||||
|
"gender":null,
|
||||||
|
"given_name":"Lakhan",
|
||||||
|
"id":"466d0b31-1b87-420e-bea5-09d05d79c586",
|
||||||
|
"middle_name":null,
|
||||||
|
"nickname":null,
|
||||||
|
"phone_number":null,
|
||||||
|
"phone_number_verified":false,
|
||||||
|
"picture":"https://lh3.googleusercontent.com/a-/AFdZucppvU6a2zIDkX0wvhhapVjT0ZMKDlYCkQDi3NxcUg=s96-c",
|
||||||
|
"preferred_username":"lakhan.m.samani@gmail.com",
|
||||||
|
"revoked_timestamp":null,
|
||||||
|
"roles":[
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"signup_methods":"google",
|
||||||
|
"updated_at":1657526492
|
||||||
|
},
|
||||||
|
"auth_recipe":"google"
|
||||||
|
}`;
|
||||||
|
@@ -79,3 +79,60 @@ export const GenerateKeys = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const AddWebhook = `
|
||||||
|
mutation addWebhook($params: AddWebhookRequest!) {
|
||||||
|
_add_webhook(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EditWebhook = `
|
||||||
|
mutation editWebhook($params: UpdateWebhookRequest!) {
|
||||||
|
_update_webhook(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DeleteWebhook = `
|
||||||
|
mutation deleteWebhook($params: WebhookRequest!) {
|
||||||
|
_delete_webhook(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TestEndpoint = `
|
||||||
|
mutation testEndpoint($params: TestEndpointRequest!) {
|
||||||
|
_test_endpoint(params: $params) {
|
||||||
|
http_status
|
||||||
|
response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AddEmailTemplate = `
|
||||||
|
mutation addEmailTemplate($params: AddEmailTemplateRequest!) {
|
||||||
|
_add_email_template(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EditEmailTemplate = `
|
||||||
|
mutation editEmailTemplate($params: UpdateEmailTemplateRequest!) {
|
||||||
|
_update_email_template(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DeleteEmailTemplate = `
|
||||||
|
mutation deleteEmailTemplate($params: DeleteEmailTemplateRequest!) {
|
||||||
|
_delete_email_template(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@@ -18,48 +18,52 @@ export const AdminSessionQuery = `
|
|||||||
export const EnvVariablesQuery = `
|
export const EnvVariablesQuery = `
|
||||||
query {
|
query {
|
||||||
_env{
|
_env{
|
||||||
CLIENT_ID,
|
CLIENT_ID
|
||||||
CLIENT_SECRET,
|
CLIENT_SECRET
|
||||||
GOOGLE_CLIENT_ID,
|
GOOGLE_CLIENT_ID
|
||||||
GOOGLE_CLIENT_SECRET,
|
GOOGLE_CLIENT_SECRET
|
||||||
GITHUB_CLIENT_ID,
|
GITHUB_CLIENT_ID
|
||||||
GITHUB_CLIENT_SECRET,
|
GITHUB_CLIENT_SECRET
|
||||||
FACEBOOK_CLIENT_ID,
|
FACEBOOK_CLIENT_ID
|
||||||
FACEBOOK_CLIENT_SECRET,
|
FACEBOOK_CLIENT_SECRET
|
||||||
LINKEDIN_CLIENT_ID,
|
LINKEDIN_CLIENT_ID
|
||||||
LINKEDIN_CLIENT_SECRET,
|
LINKEDIN_CLIENT_SECRET
|
||||||
APPLE_CLIENT_ID,
|
APPLE_CLIENT_ID
|
||||||
APPLE_CLIENT_SECRET,
|
APPLE_CLIENT_SECRET
|
||||||
DEFAULT_ROLES,
|
TWITTER_CLIENT_ID
|
||||||
PROTECTED_ROLES,
|
TWITTER_CLIENT_SECRET
|
||||||
ROLES,
|
DEFAULT_ROLES
|
||||||
JWT_TYPE,
|
PROTECTED_ROLES
|
||||||
JWT_SECRET,
|
ROLES
|
||||||
JWT_ROLE_CLAIM,
|
JWT_TYPE
|
||||||
JWT_PRIVATE_KEY,
|
JWT_SECRET
|
||||||
JWT_PUBLIC_KEY,
|
JWT_ROLE_CLAIM
|
||||||
REDIS_URL,
|
JWT_PRIVATE_KEY
|
||||||
SMTP_HOST,
|
JWT_PUBLIC_KEY
|
||||||
SMTP_PORT,
|
REDIS_URL
|
||||||
SMTP_USERNAME,
|
SMTP_HOST
|
||||||
SMTP_PASSWORD,
|
SMTP_PORT
|
||||||
SENDER_EMAIL,
|
SMTP_USERNAME
|
||||||
ALLOWED_ORIGINS,
|
SMTP_PASSWORD
|
||||||
ORGANIZATION_NAME,
|
SENDER_EMAIL
|
||||||
ORGANIZATION_LOGO,
|
ALLOWED_ORIGINS
|
||||||
ADMIN_SECRET,
|
ORGANIZATION_NAME
|
||||||
DISABLE_LOGIN_PAGE,
|
ORGANIZATION_LOGO
|
||||||
DISABLE_MAGIC_LINK_LOGIN,
|
ADMIN_SECRET
|
||||||
DISABLE_EMAIL_VERIFICATION,
|
DISABLE_LOGIN_PAGE
|
||||||
DISABLE_BASIC_AUTHENTICATION,
|
DISABLE_MAGIC_LINK_LOGIN
|
||||||
DISABLE_SIGN_UP,
|
DISABLE_EMAIL_VERIFICATION
|
||||||
DISABLE_STRONG_PASSWORD,
|
DISABLE_BASIC_AUTHENTICATION
|
||||||
DISABLE_REDIS_FOR_ENV,
|
DISABLE_SIGN_UP
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT,
|
DISABLE_STRONG_PASSWORD
|
||||||
DATABASE_NAME,
|
DISABLE_REDIS_FOR_ENV
|
||||||
DATABASE_TYPE,
|
CUSTOM_ACCESS_TOKEN_SCRIPT
|
||||||
DATABASE_URL,
|
DATABASE_NAME
|
||||||
ACCESS_TOKEN_EXPIRY_TIME,
|
DATABASE_TYPE
|
||||||
|
DATABASE_URL
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME
|
||||||
|
DISABLE_MULTI_FACTOR_AUTHENTICATION
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -89,6 +93,7 @@ export const UserDetailsQuery = `
|
|||||||
roles
|
roles
|
||||||
created_at
|
created_at
|
||||||
revoked_timestamp
|
revoked_timestamp
|
||||||
|
is_multi_factor_auth_enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,3 +106,64 @@ export const EmailVerificationQuery = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const WebhooksDataQuery = `
|
||||||
|
query getWebhooksData($params: PaginatedInput!) {
|
||||||
|
_webhooks(params: $params){
|
||||||
|
webhooks{
|
||||||
|
id
|
||||||
|
event_name
|
||||||
|
endpoint
|
||||||
|
enabled
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
pagination{
|
||||||
|
limit
|
||||||
|
page
|
||||||
|
offset
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EmailTemplatesQuery = `
|
||||||
|
query getEmailTemplates($params: PaginatedInput!) {
|
||||||
|
_email_templates(params: $params) {
|
||||||
|
email_templates {
|
||||||
|
id
|
||||||
|
event_name
|
||||||
|
subject
|
||||||
|
created_at
|
||||||
|
template
|
||||||
|
design
|
||||||
|
}
|
||||||
|
pagination {
|
||||||
|
limit
|
||||||
|
page
|
||||||
|
offset
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const WebhookLogsQuery = `
|
||||||
|
query getWebhookLogs($params: ListWebhookLogRequest!) {
|
||||||
|
_webhook_logs(params: $params) {
|
||||||
|
webhook_logs {
|
||||||
|
id
|
||||||
|
http_status
|
||||||
|
request
|
||||||
|
response
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
pagination {
|
||||||
|
limit
|
||||||
|
page
|
||||||
|
offset
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
348
dashboard/src/pages/EmailTemplates.tsx
Normal file
348
dashboard/src/pages/EmailTemplates.tsx
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
TableCaption,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Text,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tooltip,
|
||||||
|
Tr,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaAngleDoubleLeft,
|
||||||
|
FaAngleDoubleRight,
|
||||||
|
FaAngleDown,
|
||||||
|
FaAngleLeft,
|
||||||
|
FaAngleRight,
|
||||||
|
FaExclamationCircle,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import UpdateEmailTemplateModal from '../components/UpdateEmailTemplateModal';
|
||||||
|
import {
|
||||||
|
pageLimits,
|
||||||
|
UpdateModalViews,
|
||||||
|
EmailTemplateInputDataFields,
|
||||||
|
} from '../constants';
|
||||||
|
import { EmailTemplatesQuery } from '../graphql/queries';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import DeleteEmailTemplateModal from '../components/DeleteEmailTemplateModal';
|
||||||
|
|
||||||
|
interface paginationPropTypes {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
offset: number;
|
||||||
|
total: number;
|
||||||
|
maxPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmailTemplateDataType {
|
||||||
|
[EmailTemplateInputDataFields.ID]: string;
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]: string;
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: string;
|
||||||
|
[EmailTemplateInputDataFields.CREATED_AT]: number;
|
||||||
|
[EmailTemplateInputDataFields.TEMPLATE]: string;
|
||||||
|
[EmailTemplateInputDataFields.DESIGN]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmailTemplates = () => {
|
||||||
|
const client = useClient();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [emailTemplatesData, setEmailTemplatesData] = useState<
|
||||||
|
EmailTemplateDataType[]
|
||||||
|
>([]);
|
||||||
|
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
|
||||||
|
limit: 5,
|
||||||
|
page: 1,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
maxPages: 1,
|
||||||
|
});
|
||||||
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
const { limit, total } = pagination;
|
||||||
|
if (total > 1) {
|
||||||
|
return total % limit === 0
|
||||||
|
? total / limit
|
||||||
|
: parseInt(`${total / limit}`) + 1;
|
||||||
|
} else return 1;
|
||||||
|
};
|
||||||
|
const fetchEmailTemplatesData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await client
|
||||||
|
.query(EmailTemplatesQuery, {
|
||||||
|
params: {
|
||||||
|
pagination: {
|
||||||
|
limit: paginationProps.limit,
|
||||||
|
page: paginationProps.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.data?._email_templates) {
|
||||||
|
const { pagination, email_templates: emailTemplates } =
|
||||||
|
res.data?._email_templates;
|
||||||
|
const maxPages = getMaxPages(pagination);
|
||||||
|
if (emailTemplates?.length) {
|
||||||
|
setEmailTemplatesData(emailTemplates);
|
||||||
|
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||||
|
} else {
|
||||||
|
if (paginationProps.page !== 1) {
|
||||||
|
setPaginationProps({
|
||||||
|
...paginationProps,
|
||||||
|
...pagination,
|
||||||
|
maxPages,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const paginationHandler = (value: Record<string, number>) => {
|
||||||
|
setPaginationProps({ ...paginationProps, ...value });
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchEmailTemplatesData();
|
||||||
|
}, [paginationProps.page, paginationProps.limit]);
|
||||||
|
return (
|
||||||
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
|
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||||
|
<Text fontSize="md" fontWeight="bold">
|
||||||
|
Email Templates
|
||||||
|
</Text>
|
||||||
|
<UpdateEmailTemplateModal
|
||||||
|
view={UpdateModalViews.ADD}
|
||||||
|
fetchEmailTemplatesData={fetchEmailTemplatesData}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{!loading ? (
|
||||||
|
emailTemplatesData.length ? (
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Event Name</Th>
|
||||||
|
<Th>Subject</Th>
|
||||||
|
<Th>Created At</Th>
|
||||||
|
<Th>Actions</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{emailTemplatesData.map((templateData: EmailTemplateDataType) => (
|
||||||
|
<Tr
|
||||||
|
key={templateData[EmailTemplateInputDataFields.ID]}
|
||||||
|
style={{ fontSize: 14 }}
|
||||||
|
>
|
||||||
|
<Td maxW="300">
|
||||||
|
{templateData[EmailTemplateInputDataFields.EVENT_NAME]}
|
||||||
|
</Td>
|
||||||
|
<Td>{templateData[EmailTemplateInputDataFields.SUBJECT]}</Td>
|
||||||
|
<Td>
|
||||||
|
{dayjs(templateData.created_at * 1000).format(
|
||||||
|
'MMM DD, YYYY'
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm" fontWeight="light">
|
||||||
|
Menu
|
||||||
|
</Text>
|
||||||
|
<FaAngleDown style={{ marginLeft: 10 }} />
|
||||||
|
</Flex>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<UpdateEmailTemplateModal
|
||||||
|
view={UpdateModalViews.Edit}
|
||||||
|
selectedTemplate={templateData}
|
||||||
|
fetchEmailTemplatesData={fetchEmailTemplatesData}
|
||||||
|
/>
|
||||||
|
<DeleteEmailTemplateModal
|
||||||
|
emailTemplateId={
|
||||||
|
templateData[EmailTemplateInputDataFields.ID]
|
||||||
|
}
|
||||||
|
eventName={
|
||||||
|
templateData[
|
||||||
|
EmailTemplateInputDataFields.EVENT_NAME
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fetchEmailTemplatesData={fetchEmailTemplatesData}
|
||||||
|
/>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageLimits.map((pageSize) => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TableCaption>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
minH="25vh"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center w="50px" marginRight="1.5%">
|
||||||
|
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
|
||||||
|
</Center>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
paddingRight="1%"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#d9d9d9"
|
||||||
|
>
|
||||||
|
No Data
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmailTemplates;
|
@@ -50,6 +50,8 @@ const Environment = () => {
|
|||||||
LINKEDIN_CLIENT_SECRET: '',
|
LINKEDIN_CLIENT_SECRET: '',
|
||||||
APPLE_CLIENT_ID: '',
|
APPLE_CLIENT_ID: '',
|
||||||
APPLE_CLIENT_SECRET: '',
|
APPLE_CLIENT_SECRET: '',
|
||||||
|
TWITTER_CLIENT_ID: '',
|
||||||
|
TWITTER_CLIENT_SECRET: '',
|
||||||
ROLES: [],
|
ROLES: [],
|
||||||
DEFAULT_ROLES: [],
|
DEFAULT_ROLES: [],
|
||||||
PROTECTED_ROLES: [],
|
PROTECTED_ROLES: [],
|
||||||
@@ -80,6 +82,8 @@ const Environment = () => {
|
|||||||
DATABASE_TYPE: '',
|
DATABASE_TYPE: '',
|
||||||
DATABASE_URL: '',
|
DATABASE_URL: '',
|
||||||
ACCESS_TOKEN_EXPIRY_TIME: '',
|
ACCESS_TOKEN_EXPIRY_TIME: '',
|
||||||
|
DISABLE_MULTI_FACTOR_AUTHENTICATION: false,
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [fieldVisibility, setFieldVisibility] = React.useState<
|
const [fieldVisibility, setFieldVisibility] = React.useState<
|
||||||
@@ -90,6 +94,7 @@ const Environment = () => {
|
|||||||
FACEBOOK_CLIENT_SECRET: false,
|
FACEBOOK_CLIENT_SECRET: false,
|
||||||
LINKEDIN_CLIENT_SECRET: false,
|
LINKEDIN_CLIENT_SECRET: false,
|
||||||
APPLE_CLIENT_SECRET: false,
|
APPLE_CLIENT_SECRET: false,
|
||||||
|
TWITTER_CLIENT_SECRET: false,
|
||||||
JWT_SECRET: false,
|
JWT_SECRET: false,
|
||||||
SMTP_PASSWORD: false,
|
SMTP_PASSWORD: false,
|
||||||
ADMIN_SECRET: false,
|
ADMIN_SECRET: false,
|
||||||
|
@@ -29,6 +29,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
useToast,
|
useToast,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
TableContainer
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
FaAngleLeft,
|
FaAngleLeft,
|
||||||
@@ -68,6 +69,7 @@ interface userDataTypes {
|
|||||||
roles: [string];
|
roles: [string];
|
||||||
created_at: number;
|
created_at: number;
|
||||||
revoked_timestamp: number;
|
revoked_timestamp: number;
|
||||||
|
is_multi_factor_auth_enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enum updateAccessActions {
|
const enum updateAccessActions {
|
||||||
@@ -250,6 +252,33 @@ export default function Users() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const multiFactorAuthUpdateHandler = async (user: userDataTypes) => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(UpdateUser, {
|
||||||
|
params: {
|
||||||
|
id: user.id,
|
||||||
|
is_multi_factor_auth_enabled: !user.is_multi_factor_auth_enabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.data?._update_user?.id) {
|
||||||
|
toast({
|
||||||
|
title: `Multi factor authentication ${user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled'
|
||||||
|
} for user`,
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
updateUserList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: 'Multi factor authentication update failed for user',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
@@ -264,229 +293,262 @@ export default function Users() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
userList.length > 0 ? (
|
userList.length > 0 ? (
|
||||||
<Table variant="simple">
|
<TableContainer>
|
||||||
<Thead>
|
<Table variant="simple">
|
||||||
<Tr>
|
<Thead>
|
||||||
<Th>Email</Th>
|
<Tr>
|
||||||
<Th>Created At</Th>
|
<Th>Email</Th>
|
||||||
<Th>Signup Methods</Th>
|
<Th>Created At</Th>
|
||||||
<Th>Roles</Th>
|
<Th>Signup Methods</Th>
|
||||||
<Th>Verified</Th>
|
<Th>Roles</Th>
|
||||||
<Th>Access</Th>
|
<Th>Verified</Th>
|
||||||
<Th>Actions</Th>
|
<Th>Access</Th>
|
||||||
</Tr>
|
<Th>
|
||||||
</Thead>
|
<Tooltip label="MultiFactor Authentication Enabled / Disabled">
|
||||||
<Tbody>
|
MFA
|
||||||
{userList.map((user: userDataTypes) => {
|
</Tooltip>
|
||||||
const { email_verified, created_at, ...rest }: any = user;
|
</Th>
|
||||||
return (
|
<Th>Actions</Th>
|
||||||
<Tr key={user.id} style={{ fontSize: 14 }}>
|
</Tr>
|
||||||
<Td maxW="300">{user.email}</Td>
|
</Thead>
|
||||||
<Td>
|
<Tbody>
|
||||||
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
|
{userList.map((user: userDataTypes) => {
|
||||||
</Td>
|
const { email_verified, created_at, ...rest }: any = user;
|
||||||
<Td>{user.signup_methods}</Td>
|
return (
|
||||||
<Td>{user.roles.join(', ')}</Td>
|
<Tr key={user.id} style={{ fontSize: 14 }}>
|
||||||
<Td>
|
<Td maxW="300">{user.email}</Td>
|
||||||
<Tag
|
<Td>
|
||||||
size="sm"
|
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
|
||||||
variant="outline"
|
</Td>
|
||||||
colorScheme={user.email_verified ? 'green' : 'yellow'}
|
<Td>{user.signup_methods}</Td>
|
||||||
>
|
<Td>{user.roles.join(', ')}</Td>
|
||||||
{user.email_verified.toString()}
|
<Td>
|
||||||
</Tag>
|
<Tag
|
||||||
</Td>
|
size="sm"
|
||||||
<Td>
|
variant="outline"
|
||||||
<Tag
|
colorScheme={user.email_verified ? 'green' : 'yellow'}
|
||||||
size="sm"
|
>
|
||||||
variant="outline"
|
{user.email_verified.toString()}
|
||||||
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
|
</Tag>
|
||||||
>
|
</Td>
|
||||||
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
|
<Td>
|
||||||
</Tag>
|
<Tag
|
||||||
</Td>
|
size="sm"
|
||||||
<Td>
|
variant="outline"
|
||||||
<Menu>
|
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
|
||||||
<MenuButton as={Button} variant="unstyled" size="sm">
|
>
|
||||||
<Flex
|
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
|
||||||
justifyContent="space-between"
|
</Tag>
|
||||||
alignItems="center"
|
</Td>
|
||||||
>
|
<Td>
|
||||||
<Text fontSize="sm" fontWeight="light">
|
<Tag
|
||||||
Menu
|
size="sm"
|
||||||
</Text>
|
variant="outline"
|
||||||
<FaAngleDown style={{ marginLeft: 10 }} />
|
colorScheme={
|
||||||
</Flex>
|
user.is_multi_factor_auth_enabled ? 'green' : 'red'
|
||||||
</MenuButton>
|
}
|
||||||
<MenuList>
|
>
|
||||||
{!user.email_verified && (
|
{user.is_multi_factor_auth_enabled
|
||||||
<MenuItem
|
? 'Enabled'
|
||||||
onClick={() => userVerificationHandler(user)}
|
: 'Disabled'}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
>
|
>
|
||||||
Verify User
|
<Text fontSize="sm" fontWeight="light">
|
||||||
</MenuItem>
|
Menu
|
||||||
)}
|
</Text>
|
||||||
<EditUserModal
|
<FaAngleDown style={{ marginLeft: 10 }} />
|
||||||
user={rest}
|
</Flex>
|
||||||
updateUserList={updateUserList}
|
</MenuButton>
|
||||||
/>
|
<MenuList>
|
||||||
<DeleteUserModal
|
{!user.email_verified && (
|
||||||
user={rest}
|
<MenuItem
|
||||||
updateUserList={updateUserList}
|
onClick={() => userVerificationHandler(user)}
|
||||||
/>
|
>
|
||||||
{user.revoked_timestamp ? (
|
Verify User
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
onClick={() =>
|
)}
|
||||||
updateAccessHandler(
|
<EditUserModal
|
||||||
user.id,
|
user={rest}
|
||||||
updateAccessActions.ENABLE
|
updateUserList={updateUserList}
|
||||||
)
|
/>
|
||||||
}
|
<DeleteUserModal
|
||||||
>
|
user={rest}
|
||||||
Enable Access
|
updateUserList={updateUserList}
|
||||||
</MenuItem>
|
/>
|
||||||
) : (
|
{user.revoked_timestamp ? (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateAccessHandler(
|
updateAccessHandler(
|
||||||
user.id,
|
user.id,
|
||||||
updateAccessActions.REVOKE
|
updateAccessActions.ENABLE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Revoke Access
|
Enable Access
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
) : (
|
||||||
</MenuList>
|
<MenuItem
|
||||||
</Menu>
|
onClick={() =>
|
||||||
</Td>
|
updateAccessHandler(
|
||||||
</Tr>
|
user.id,
|
||||||
);
|
updateAccessActions.REVOKE
|
||||||
})}
|
)
|
||||||
</Tbody>
|
}
|
||||||
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
|
>
|
||||||
<TableCaption>
|
Revoke Access
|
||||||
<Flex
|
</MenuItem>
|
||||||
justifyContent="space-between"
|
)}
|
||||||
alignItems="center"
|
{user.is_multi_factor_auth_enabled ? (
|
||||||
m="2% 0"
|
<MenuItem
|
||||||
>
|
onClick={() => multiFactorAuthUpdateHandler(user)}
|
||||||
<Flex flex="1">
|
>
|
||||||
<Tooltip label="First Page">
|
Disable MultiFactor Authentication
|
||||||
<IconButton
|
</MenuItem>
|
||||||
aria-label="icon button"
|
) : (
|
||||||
onClick={() =>
|
<MenuItem
|
||||||
|
onClick={() => multiFactorAuthUpdateHandler(user)}
|
||||||
|
>
|
||||||
|
Enable MultiFactor Authentication
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
paginationHandler({
|
paginationHandler({
|
||||||
page: 1,
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
isDisabled={paginationProps.page <= 1}
|
|
||||||
mr={4}
|
|
||||||
icon={<FaAngleDoubleLeft />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label="Previous Page">
|
|
||||||
<IconButton
|
|
||||||
aria-label="icon button"
|
|
||||||
onClick={() =>
|
|
||||||
paginationHandler({
|
|
||||||
page: paginationProps.page - 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isDisabled={paginationProps.page <= 1}
|
|
||||||
icon={<FaAngleLeft />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Flex>
|
|
||||||
<Flex
|
|
||||||
flex="8"
|
|
||||||
justifyContent="space-evenly"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Text mr={8}>
|
|
||||||
Page{' '}
|
|
||||||
<Text fontWeight="bold" as="span">
|
|
||||||
{paginationProps.page}
|
|
||||||
</Text>{' '}
|
|
||||||
of{' '}
|
|
||||||
<Text fontWeight="bold" as="span">
|
|
||||||
{paginationProps.maxPages}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Flex alignItems="center">
|
|
||||||
<Text flexShrink="0">Go to page:</Text>{' '}
|
|
||||||
<NumberInput
|
|
||||||
ml={2}
|
|
||||||
mr={8}
|
|
||||||
w={28}
|
|
||||||
min={1}
|
|
||||||
max={paginationProps.maxPages}
|
|
||||||
onChange={(value) =>
|
|
||||||
paginationHandler({
|
|
||||||
page: parseInt(value),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
value={paginationProps.page}
|
|
||||||
>
|
>
|
||||||
<NumberInputField />
|
{getLimits(paginationProps).map((pageSize) => (
|
||||||
<NumberInputStepper>
|
<option key={pageSize} value={pageSize}>
|
||||||
<NumberIncrementStepper />
|
Show {pageSize}
|
||||||
<NumberDecrementStepper />
|
</option>
|
||||||
</NumberInputStepper>
|
))}
|
||||||
</NumberInput>
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Select
|
|
||||||
w={32}
|
|
||||||
value={paginationProps.limit}
|
|
||||||
onChange={(e) =>
|
|
||||||
paginationHandler({
|
|
||||||
page: 1,
|
|
||||||
limit: parseInt(e.target.value),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{getLimits(paginationProps).map((pageSize) => (
|
|
||||||
<option key={pageSize} value={pageSize}>
|
|
||||||
Show {pageSize}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flex="1">
|
</TableCaption>
|
||||||
<Tooltip label="Next Page">
|
)}
|
||||||
<IconButton
|
</Table>
|
||||||
aria-label="icon button"
|
</TableContainer>
|
||||||
onClick={() =>
|
|
||||||
paginationHandler({
|
|
||||||
page: paginationProps.page + 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isDisabled={
|
|
||||||
paginationProps.page >= paginationProps.maxPages
|
|
||||||
}
|
|
||||||
icon={<FaAngleRight />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label="Last Page">
|
|
||||||
<IconButton
|
|
||||||
aria-label="icon button"
|
|
||||||
onClick={() =>
|
|
||||||
paginationHandler({
|
|
||||||
page: paginationProps.maxPages,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isDisabled={
|
|
||||||
paginationProps.page >= paginationProps.maxPages
|
|
||||||
}
|
|
||||||
ml={4}
|
|
||||||
icon={<FaAngleDoubleRight />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</TableCaption>
|
|
||||||
)}
|
|
||||||
</Table>
|
|
||||||
) : (
|
) : (
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
|
369
dashboard/src/pages/Webhooks.tsx
Normal file
369
dashboard/src/pages/Webhooks.tsx
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
TableCaption,
|
||||||
|
Tag,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Text,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tooltip,
|
||||||
|
Tr,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaAngleDoubleLeft,
|
||||||
|
FaAngleDoubleRight,
|
||||||
|
FaAngleDown,
|
||||||
|
FaAngleLeft,
|
||||||
|
FaAngleRight,
|
||||||
|
FaExclamationCircle,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import UpdateWebhookModal from '../components/UpdateWebhookModal';
|
||||||
|
import {
|
||||||
|
pageLimits,
|
||||||
|
WebhookInputDataFields,
|
||||||
|
UpdateModalViews,
|
||||||
|
} from '../constants';
|
||||||
|
import { WebhooksDataQuery } from '../graphql/queries';
|
||||||
|
import DeleteWebhookModal from '../components/DeleteWebhookModal';
|
||||||
|
import ViewWebhookLogsModal from '../components/ViewWebhookLogsModal';
|
||||||
|
|
||||||
|
interface paginationPropTypes {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
offset: number;
|
||||||
|
total: number;
|
||||||
|
maxPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface webhookDataTypes {
|
||||||
|
[WebhookInputDataFields.ID]: string;
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: string;
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: string;
|
||||||
|
[WebhookInputDataFields.ENABLED]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Webhooks = () => {
|
||||||
|
const client = useClient();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [webhookData, setWebhookData] = useState<webhookDataTypes[]>([]);
|
||||||
|
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
|
||||||
|
limit: 5,
|
||||||
|
page: 1,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
maxPages: 1,
|
||||||
|
});
|
||||||
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
const { limit, total } = pagination;
|
||||||
|
if (total > 1) {
|
||||||
|
return total % limit === 0
|
||||||
|
? total / limit
|
||||||
|
: parseInt(`${total / limit}`) + 1;
|
||||||
|
} else return 1;
|
||||||
|
};
|
||||||
|
const fetchWebookData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await client
|
||||||
|
.query(WebhooksDataQuery, {
|
||||||
|
params: {
|
||||||
|
pagination: {
|
||||||
|
limit: paginationProps.limit,
|
||||||
|
page: paginationProps.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.data?._webhooks) {
|
||||||
|
const { pagination, webhooks } = res.data?._webhooks;
|
||||||
|
const maxPages = getMaxPages(pagination);
|
||||||
|
if (webhooks?.length) {
|
||||||
|
setWebhookData(webhooks);
|
||||||
|
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||||
|
} else {
|
||||||
|
if (paginationProps.page !== 1) {
|
||||||
|
setPaginationProps({
|
||||||
|
...paginationProps,
|
||||||
|
...pagination,
|
||||||
|
maxPages,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const paginationHandler = (value: Record<string, number>) => {
|
||||||
|
setPaginationProps({ ...paginationProps, ...value });
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchWebookData();
|
||||||
|
}, [paginationProps.page, paginationProps.limit]);
|
||||||
|
return (
|
||||||
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
|
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||||
|
<Text fontSize="md" fontWeight="bold">
|
||||||
|
Webhooks
|
||||||
|
</Text>
|
||||||
|
<UpdateWebhookModal
|
||||||
|
view={UpdateModalViews.ADD}
|
||||||
|
fetchWebookData={fetchWebookData}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{!loading ? (
|
||||||
|
webhookData.length ? (
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Event Name</Th>
|
||||||
|
<Th>Endpoint</Th>
|
||||||
|
<Th>Enabled</Th>
|
||||||
|
<Th>Headers</Th>
|
||||||
|
<Th>Actions</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{webhookData.map((webhook: webhookDataTypes) => (
|
||||||
|
<Tr
|
||||||
|
key={webhook[WebhookInputDataFields.ID]}
|
||||||
|
style={{ fontSize: 14 }}
|
||||||
|
>
|
||||||
|
<Td maxW="300">
|
||||||
|
{webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
</Td>
|
||||||
|
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
webhook[WebhookInputDataFields.ENABLED]
|
||||||
|
? 'green'
|
||||||
|
: 'yellow'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{webhook[WebhookInputDataFields.ENABLED].toString()}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tooltip
|
||||||
|
bg="gray.300"
|
||||||
|
color="black"
|
||||||
|
label={JSON.stringify(
|
||||||
|
webhook[WebhookInputDataFields.HEADERS],
|
||||||
|
null,
|
||||||
|
' '
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Tag size="sm" variant="outline" colorScheme="gray">
|
||||||
|
{Object.keys(
|
||||||
|
webhook[WebhookInputDataFields.HEADERS] || {}
|
||||||
|
)?.length.toString()}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm" fontWeight="light">
|
||||||
|
Menu
|
||||||
|
</Text>
|
||||||
|
<FaAngleDown style={{ marginLeft: 10 }} />
|
||||||
|
</Flex>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<UpdateWebhookModal
|
||||||
|
view={UpdateModalViews.Edit}
|
||||||
|
selectedWebhook={webhook}
|
||||||
|
fetchWebookData={fetchWebookData}
|
||||||
|
/>
|
||||||
|
<DeleteWebhookModal
|
||||||
|
webhookId={webhook[WebhookInputDataFields.ID]}
|
||||||
|
eventName={webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
fetchWebookData={fetchWebookData}
|
||||||
|
/>
|
||||||
|
<ViewWebhookLogsModal
|
||||||
|
webhookId={webhook[WebhookInputDataFields.ID]}
|
||||||
|
eventName={webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
/>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageLimits.map((pageSize) => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TableCaption>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
minH="25vh"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center w="50px" marginRight="1.5%">
|
||||||
|
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
|
||||||
|
</Center>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
paddingRight="1%"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#d9d9d9"
|
||||||
|
>
|
||||||
|
No Data
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Webhooks;
|
@@ -3,37 +3,41 @@ import { Outlet, Route, Routes } from 'react-router-dom';
|
|||||||
|
|
||||||
import { useAuthContext } from '../contexts/AuthContext';
|
import { useAuthContext } from '../contexts/AuthContext';
|
||||||
import { DashboardLayout } from '../layouts/DashboardLayout';
|
import { DashboardLayout } from '../layouts/DashboardLayout';
|
||||||
|
import EmailTemplates from '../pages/EmailTemplates';
|
||||||
|
|
||||||
const Auth = lazy(() => import('../pages/Auth'));
|
const Auth = lazy(() => import('../pages/Auth'));
|
||||||
const Environment = lazy(() => import('../pages/Environment'));
|
const Environment = lazy(() => import('../pages/Environment'));
|
||||||
const Home = lazy(() => import('../pages/Home'));
|
const Home = lazy(() => import('../pages/Home'));
|
||||||
const Users = lazy(() => import('../pages/Users'));
|
const Users = lazy(() => import('../pages/Users'));
|
||||||
|
const Webhooks = lazy(() => import('../pages/Webhooks'));
|
||||||
|
|
||||||
export const AppRoutes = () => {
|
export const AppRoutes = () => {
|
||||||
const { isLoggedIn } = useAuthContext();
|
const { isLoggedIn } = useAuthContext();
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Suspense fallback={<></>}>
|
<Suspense fallback={<></>}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route path="/" element={<Outlet />}>
|
<Route path="/" element={<Outlet />}>
|
||||||
<Route index element={<Environment />} />
|
<Route index element={<Environment />} />
|
||||||
<Route path="/:sec" element={<Environment />} />
|
<Route path="/:sec" element={<Environment />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="users" element={<Users />} />
|
<Route path="users" element={<Users />} />
|
||||||
<Route path="*" element={<Home />} />
|
<Route path="webhooks" element={<Webhooks />} />
|
||||||
</Route>
|
<Route path="email-templates" element={<EmailTemplates />} />
|
||||||
</Routes>
|
<Route path="*" element={<Home />} />
|
||||||
</Suspense>
|
</Route>
|
||||||
</div>
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@@ -29,19 +29,16 @@ const fallbackCopyTextToClipboard = (text: string) => {
|
|||||||
document.body.removeChild(textArea);
|
document.body.removeChild(textArea);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const copyTextToClipboard = (text: string) => {
|
export const copyTextToClipboard = async (text: string) => {
|
||||||
if (!navigator.clipboard) {
|
if (!navigator.clipboard) {
|
||||||
fallbackCopyTextToClipboard(text);
|
fallbackCopyTextToClipboard(text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigator.clipboard.writeText(text).then(
|
try {
|
||||||
() => {
|
navigator.clipboard.writeText(text);
|
||||||
console.log('Async: Copying to clipboard was successful!');
|
} catch (err) {
|
||||||
},
|
throw err;
|
||||||
(err) => {
|
}
|
||||||
console.error('Async: Could not copy text: ', err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getObjectDiff = (obj1: any, obj2: any) => {
|
export const getObjectDiff = (obj1: any, obj2: any) => {
|
||||||
|
@@ -15,4 +15,6 @@ const (
|
|||||||
AuthRecipeMethodLinkedIn = "linkedin"
|
AuthRecipeMethodLinkedIn = "linkedin"
|
||||||
// AuthRecipeMethodApple is the apple auth method
|
// AuthRecipeMethodApple is the apple auth method
|
||||||
AuthRecipeMethodApple = "apple"
|
AuthRecipeMethodApple = "apple"
|
||||||
|
// AuthRecipeMethodTwitter is the twitter auth method
|
||||||
|
AuthRecipeMethodTwitter = "twitter"
|
||||||
)
|
)
|
||||||
|
@@ -47,6 +47,8 @@ const (
|
|||||||
EnvKeySmtpPassword = "SMTP_PASSWORD"
|
EnvKeySmtpPassword = "SMTP_PASSWORD"
|
||||||
// EnvKeySenderEmail key for env variable SENDER_EMAIL
|
// EnvKeySenderEmail key for env variable SENDER_EMAIL
|
||||||
EnvKeySenderEmail = "SENDER_EMAIL"
|
EnvKeySenderEmail = "SENDER_EMAIL"
|
||||||
|
// EnvKeyIsEmailServiceEnabled key for env variable IS_EMAIL_SERVICE_ENABLED
|
||||||
|
EnvKeyIsEmailServiceEnabled = "IS_EMAIL_SERVICE_ENABLED"
|
||||||
// EnvKeyJwtType key for env variable JWT_TYPE
|
// EnvKeyJwtType key for env variable JWT_TYPE
|
||||||
EnvKeyJwtType = "JWT_TYPE"
|
EnvKeyJwtType = "JWT_TYPE"
|
||||||
// EnvKeyJwtSecret key for env variable JWT_SECRET
|
// EnvKeyJwtSecret key for env variable JWT_SECRET
|
||||||
@@ -83,6 +85,10 @@ const (
|
|||||||
EnvKeyAppleClientID = "APPLE_CLIENT_ID"
|
EnvKeyAppleClientID = "APPLE_CLIENT_ID"
|
||||||
// EnvKeyAppleClientSecret key for env variable APPLE_CLIENT_SECRET
|
// EnvKeyAppleClientSecret key for env variable APPLE_CLIENT_SECRET
|
||||||
EnvKeyAppleClientSecret = "APPLE_CLIENT_SECRET"
|
EnvKeyAppleClientSecret = "APPLE_CLIENT_SECRET"
|
||||||
|
// EnvKeyTwitterClientID key for env variable TWITTER_CLIENT_ID
|
||||||
|
EnvKeyTwitterClientID = "TWITTER_CLIENT_ID"
|
||||||
|
// EnvKeyTwitterClientSecret key for env variable TWITTER_CLIENT_SECRET
|
||||||
|
EnvKeyTwitterClientSecret = "TWITTER_CLIENT_SECRET"
|
||||||
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
|
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
|
||||||
EnvKeyOrganizationName = "ORGANIZATION_NAME"
|
EnvKeyOrganizationName = "ORGANIZATION_NAME"
|
||||||
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
|
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
|
||||||
@@ -117,6 +123,12 @@ const (
|
|||||||
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
|
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
|
||||||
// EnvKeyDisableStrongPassword key for env variable DISABLE_STRONG_PASSWORD
|
// EnvKeyDisableStrongPassword key for env variable DISABLE_STRONG_PASSWORD
|
||||||
EnvKeyDisableStrongPassword = "DISABLE_STRONG_PASSWORD"
|
EnvKeyDisableStrongPassword = "DISABLE_STRONG_PASSWORD"
|
||||||
|
// EnvKeyEnforceMultiFactorAuthentication is key for env variable ENFORCE_MULTI_FACTOR_AUTHENTICATION
|
||||||
|
// If enforced and changed later on, existing user will have MFA but new user will not have MFA
|
||||||
|
EnvKeyEnforceMultiFactorAuthentication = "ENFORCE_MULTI_FACTOR_AUTHENTICATION"
|
||||||
|
// EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION
|
||||||
|
// this variable is used to completely disable multi factor authentication. It will have no effect on profile preference
|
||||||
|
EnvKeyDisableMultiFactorAuthentication = "DISABLE_MULTI_FACTOR_AUTHENTICATION"
|
||||||
|
|
||||||
// Slice variables
|
// Slice variables
|
||||||
// EnvKeyRoles key for env variable ROLES
|
// EnvKeyRoles key for env variable ROLES
|
||||||
|
@@ -9,9 +9,11 @@ const (
|
|||||||
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
|
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
|
||||||
GithubUserInfoURL = "https://api.github.com/user"
|
GithubUserInfoURL = "https://api.github.com/user"
|
||||||
// Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123
|
// Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123
|
||||||
GithubUserEmails = "https://api/github.com/user/emails"
|
GithubUserEmails = "https://api.github.com/user/emails"
|
||||||
|
|
||||||
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
|
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
|
||||||
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
|
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
|
||||||
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
|
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"
|
||||||
)
|
)
|
||||||
|
@@ -9,4 +9,8 @@ const (
|
|||||||
VerificationTypeUpdateEmail = "update_email"
|
VerificationTypeUpdateEmail = "update_email"
|
||||||
// VerificationTypeForgotPassword is the forgot_password verification type
|
// VerificationTypeForgotPassword is the forgot_password verification type
|
||||||
VerificationTypeForgotPassword = "forgot_password"
|
VerificationTypeForgotPassword = "forgot_password"
|
||||||
|
// VerificationTypeInviteMember is the invite_member verification type
|
||||||
|
VerificationTypeInviteMember = "invite_member"
|
||||||
|
// VerificationTypeOTP is the otp verification type
|
||||||
|
VerificationTypeOTP = "verify_otp"
|
||||||
)
|
)
|
||||||
|
@@ -12,7 +12,9 @@ type EmailTemplate struct {
|
|||||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
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"`
|
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"`
|
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"`
|
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"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
}
|
}
|
||||||
@@ -26,7 +28,9 @@ func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
|
|||||||
return &model.EmailTemplate{
|
return &model.EmailTemplate{
|
||||||
ID: id,
|
ID: id,
|
||||||
EventName: e.EventName,
|
EventName: e.EventName,
|
||||||
|
Subject: e.Subject,
|
||||||
Template: e.Template,
|
Template: e.Template,
|
||||||
|
Design: e.Design,
|
||||||
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
|
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
|
||||||
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
|
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ type CollectionList struct {
|
|||||||
Webhook string
|
Webhook string
|
||||||
WebhookLog string
|
WebhookLog string
|
||||||
EmailTemplate string
|
EmailTemplate string
|
||||||
|
OTP string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -20,8 +21,9 @@ var (
|
|||||||
VerificationRequest: Prefix + "verification_requests",
|
VerificationRequest: Prefix + "verification_requests",
|
||||||
Session: Prefix + "sessions",
|
Session: Prefix + "sessions",
|
||||||
Env: Prefix + "env",
|
Env: Prefix + "env",
|
||||||
Webhook: Prefix + "webhook",
|
Webhook: Prefix + "webhooks",
|
||||||
WebhookLog: Prefix + "webhook_log",
|
WebhookLog: Prefix + "webhook_logs",
|
||||||
EmailTemplate: Prefix + "email_template",
|
EmailTemplate: Prefix + "email_templates",
|
||||||
|
OTP: Prefix + "otps",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
12
server/db/models/otp.go
Normal file
12
server/db/models/otp.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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"`
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
@@ -14,51 +15,60 @@ type User struct {
|
|||||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
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"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
|
|
||||||
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
|
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
|
||||||
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at"`
|
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"`
|
Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password"`
|
||||||
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"`
|
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"`
|
||||||
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"`
|
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"`
|
||||||
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"`
|
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"`
|
||||||
MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name"`
|
MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name"`
|
||||||
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname"`
|
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname"`
|
||||||
Gender *string `json:"gender" bson:"gender" cql:"gender"`
|
Gender *string `json:"gender" bson:"gender" cql:"gender"`
|
||||||
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"`
|
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"`
|
||||||
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number"`
|
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"`
|
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"`
|
Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture"`
|
||||||
Roles string `json:"roles" bson:"roles" cql:"roles"`
|
Roles string `json:"roles" bson:"roles" cql:"roles"`
|
||||||
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
|
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) AsAPIUser() *model.User {
|
func (user *User) AsAPIUser() *model.User {
|
||||||
isEmailVerified := user.EmailVerifiedAt != nil
|
isEmailVerified := user.EmailVerifiedAt != nil
|
||||||
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
|
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
|
||||||
|
|
||||||
id := user.ID
|
// id := user.ID
|
||||||
if strings.Contains(id, Collections.WebhookLog+"/") {
|
// if strings.Contains(id, Collections.User+"/") {
|
||||||
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
|
// id = strings.TrimPrefix(id, Collections.User+"/")
|
||||||
}
|
// }
|
||||||
return &model.User{
|
return &model.User{
|
||||||
ID: id,
|
ID: user.ID,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
EmailVerified: isEmailVerified,
|
EmailVerified: isEmailVerified,
|
||||||
SignupMethods: user.SignupMethods,
|
SignupMethods: user.SignupMethods,
|
||||||
GivenName: user.GivenName,
|
GivenName: user.GivenName,
|
||||||
FamilyName: user.FamilyName,
|
FamilyName: user.FamilyName,
|
||||||
MiddleName: user.MiddleName,
|
MiddleName: user.MiddleName,
|
||||||
Nickname: user.Nickname,
|
Nickname: user.Nickname,
|
||||||
PreferredUsername: refs.NewStringRef(user.Email),
|
PreferredUsername: refs.NewStringRef(user.Email),
|
||||||
Gender: user.Gender,
|
Gender: user.Gender,
|
||||||
Birthdate: user.Birthdate,
|
Birthdate: user.Birthdate,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
PhoneNumberVerified: &isPhoneVerified,
|
PhoneNumberVerified: &isPhoneVerified,
|
||||||
Picture: user.Picture,
|
Picture: user.Picture,
|
||||||
Roles: strings.Split(user.Roles, ","),
|
Roles: strings.Split(user.Roles, ","),
|
||||||
RevokedTimestamp: user.RevokedTimestamp,
|
RevokedTimestamp: user.RevokedTimestamp,
|
||||||
CreatedAt: refs.NewInt64Ref(user.CreatedAt),
|
IsMultiFactorAuthEnabled: user.IsMultiFactorAuthEnabled,
|
||||||
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
|
CreatedAt: refs.NewInt64Ref(user.CreatedAt),
|
||||||
|
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) ToMap() map[string]interface{} {
|
||||||
|
res := map[string]interface{}{}
|
||||||
|
data, _ := json.Marshal(user) // Convert to a json string
|
||||||
|
json.Unmarshal(data, &res) // Convert to a map
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
@@ -25,8 +25,8 @@ type VerificationRequest struct {
|
|||||||
|
|
||||||
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
||||||
id := v.ID
|
id := v.ID
|
||||||
if strings.Contains(id, Collections.WebhookLog+"/") {
|
if strings.Contains(id, Collections.VerificationRequest+"/") {
|
||||||
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
|
id = strings.TrimPrefix(id, Collections.VerificationRequest+"/")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &model.VerificationRequest{
|
return &model.VerificationRequest{
|
||||||
|
@@ -16,6 +16,7 @@ import (
|
|||||||
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
if emailTemplate.ID == "" {
|
if emailTemplate.ID == "" {
|
||||||
emailTemplate.ID = uuid.New().String()
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
emailTemplate.Key = emailTemplate.ID
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||||
if env.ID == "" {
|
if env.ID == "" {
|
||||||
env.ID = uuid.New().String()
|
env.ID = uuid.New().String()
|
||||||
|
env.Key = env.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
env.CreatedAt = time.Now().Unix()
|
env.CreatedAt = time.Now().Unix()
|
||||||
|
92
server/db/providers/arangodb/otp.go
Normal file
92
server/db/providers/arangodb/otp.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package arangodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arangodb/go-driver"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
otp.UpdatedAt = time.Now().Unix()
|
||||||
|
otpCollection, _ := p.db.Collection(ctx, models.Collections.OTP)
|
||||||
|
|
||||||
|
var meta driver.DocumentMeta
|
||||||
|
var err error
|
||||||
|
if shouldCreate {
|
||||||
|
meta, err = otpCollection.CreateDocument(ctx, otp)
|
||||||
|
} else {
|
||||||
|
meta, err = otpCollection.UpdateDocument(ctx, otp.Key, otp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
otp.Key = meta.Key
|
||||||
|
otp.ID = meta.ID.String()
|
||||||
|
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 otp models.OTP
|
||||||
|
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.OTP)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"email": emailAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if otp.Key == "" {
|
||||||
|
return nil, fmt.Errorf("email template not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(ctx, &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 {
|
||||||
|
otpCollection, _ := p.db.Collection(ctx, models.Collections.OTP)
|
||||||
|
_, err := otpCollection.RemoveDocument(ctx, otp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -148,6 +148,20 @@ func NewProvider() (*provider, error) {
|
|||||||
Sparse: true,
|
Sparse: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
otpCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.OTP)
|
||||||
|
if !otpCollectionExists {
|
||||||
|
_, err = arangodb.CreateCollection(ctx, models.Collections.OTP, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
otpCollection, _ := arangodb.Collection(nil, models.Collections.OTP)
|
||||||
|
otpCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
|
||||||
|
Unique: true,
|
||||||
|
Sparse: true,
|
||||||
|
})
|
||||||
|
|
||||||
return &provider{
|
return &provider{
|
||||||
db: arangodb,
|
db: arangodb,
|
||||||
}, err
|
}, err
|
||||||
|
@@ -12,6 +12,7 @@ import (
|
|||||||
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
|
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
|
||||||
if session.ID == "" {
|
if session.ID == "" {
|
||||||
session.ID = uuid.New().String()
|
session.ID = uuid.New().String()
|
||||||
|
session.Key = session.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
session.CreatedAt = time.Now().Unix()
|
session.CreatedAt = time.Now().Unix()
|
||||||
|
@@ -2,22 +2,26 @@ package arangodb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/arangodb/go-driver"
|
"github.com/arangodb/go-driver"
|
||||||
arangoDriver "github.com/arangodb/go-driver"
|
arangoDriver "github.com/arangodb/go-driver"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddUser to save user information in database
|
// AddUser to save user information in database
|
||||||
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
|
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
|
||||||
if user.ID == "" {
|
if user.ID == "" {
|
||||||
user.ID = uuid.New().String()
|
user.ID = uuid.New().String()
|
||||||
|
user.Key = user.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Roles == "" {
|
if user.Roles == "" {
|
||||||
@@ -65,7 +69,7 @@ func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
|||||||
|
|
||||||
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @user_id REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
|
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @user_id REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
|
||||||
bindVars := map[string]interface{}{
|
bindVars := map[string]interface{}{
|
||||||
"user_id": user.ID,
|
"user_id": user.Key,
|
||||||
}
|
}
|
||||||
cursor, err := p.db.Query(ctx, query, bindVars)
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -174,3 +178,36 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, err
|
|||||||
|
|
||||||
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
|
||||||
|
data["updated_at"] = time.Now().Unix()
|
||||||
|
|
||||||
|
userInfoBytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := ""
|
||||||
|
if ids != nil && len(ids) > 0 {
|
||||||
|
keysArray := ""
|
||||||
|
for _, id := range ids {
|
||||||
|
keysArray += fmt.Sprintf("'%s', ", id)
|
||||||
|
}
|
||||||
|
keysArray = strings.Trim(keysArray, " ")
|
||||||
|
keysArray = strings.TrimSuffix(keysArray, ",")
|
||||||
|
query = fmt.Sprintf("FOR u IN %s FILTER u._id IN [%s] UPDATE u._key with %s IN %s", models.Collections.User, keysArray, string(userInfoBytes), models.Collections.User)
|
||||||
|
} else {
|
||||||
|
query = fmt.Sprintf("FOR u IN %s UPDATE u._key with %s IN %s", models.Collections.User, string(userInfoBytes), models.Collections.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.db.Query(ctx, query, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
|
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
|
||||||
if verificationRequest.ID == "" {
|
if verificationRequest.ID == "" {
|
||||||
verificationRequest.ID = uuid.New().String()
|
verificationRequest.ID = uuid.New().String()
|
||||||
|
verificationRequest.Key = verificationRequest.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
verificationRequest.CreatedAt = time.Now().Unix()
|
verificationRequest.CreatedAt = time.Now().Unix()
|
||||||
|
@@ -16,6 +16,7 @@ import (
|
|||||||
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
if webhook.ID == "" {
|
if webhook.ID == "" {
|
||||||
webhook.ID = uuid.New().String()
|
webhook.ID = uuid.New().String()
|
||||||
|
webhook.Key = webhook.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
webhook.Key = webhook.ID
|
webhook.Key = webhook.ID
|
||||||
|
@@ -16,6 +16,7 @@ import (
|
|||||||
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
||||||
if webhookLog.ID == "" {
|
if webhookLog.ID == "" {
|
||||||
webhookLog.ID = uuid.New().String()
|
webhookLog.ID = uuid.New().String()
|
||||||
|
webhookLog.Key = webhookLog.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
webhookLog.Key = webhookLog.ID
|
webhookLog.Key = webhookLog.ID
|
||||||
|
@@ -29,7 +29,7 @@ func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.Em
|
|||||||
return nil, fmt.Errorf("Email template with %s event_name already exists", emailTemplate.EventName)
|
return nil, fmt.Errorf("Email template with %s event_name already exists", emailTemplate.EventName)
|
||||||
}
|
}
|
||||||
|
|
||||||
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, template, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Template, emailTemplate.CreatedAt, emailTemplate.UpdatedAt)
|
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, subject, design, template, created_at, updated_at) VALUES ('%s', '%s', '%s','%s','%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Subject, emailTemplate.Design, emailTemplate.Template, emailTemplate.CreatedAt, emailTemplate.UpdatedAt)
|
||||||
err := p.db.Query(insertQuery).Exec()
|
err := p.db.Query(insertQuery).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -103,14 +103,14 @@ func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagin
|
|||||||
// there is no offset in cassandra
|
// there is no offset in cassandra
|
||||||
// so we fetch till limit + offset
|
// so we fetch till limit + offset
|
||||||
// and return the results from offset to limit
|
// and return the results from offset to limit
|
||||||
query := fmt.Sprintf("SELECT id, event_name, template, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.EmailTemplate, pagination.Limit+pagination.Offset)
|
query := fmt.Sprintf("SELECT id, event_name, subject, design, template, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.EmailTemplate, pagination.Limit+pagination.Offset)
|
||||||
|
|
||||||
scanner := p.db.Query(query).Iter().Scanner()
|
scanner := p.db.Query(query).Iter().Scanner()
|
||||||
counter := int64(0)
|
counter := int64(0)
|
||||||
for scanner.Next() {
|
for scanner.Next() {
|
||||||
if counter >= pagination.Offset {
|
if counter >= pagination.Offset {
|
||||||
var emailTemplate models.EmailTemplate
|
var emailTemplate models.EmailTemplate
|
||||||
err := scanner.Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
err := scanner.Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -128,8 +128,8 @@ func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagin
|
|||||||
// GetEmailTemplateByID to get EmailTemplate by id
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
var emailTemplate models.EmailTemplate
|
var emailTemplate models.EmailTemplate
|
||||||
query := fmt.Sprintf(`SELECT id, event_name, template, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.EmailTemplate, emailTemplateID)
|
query := fmt.Sprintf(`SELECT id, event_name, subject, design, template, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.EmailTemplate, emailTemplateID)
|
||||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -139,8 +139,8 @@ func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID str
|
|||||||
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
var emailTemplate models.EmailTemplate
|
var emailTemplate models.EmailTemplate
|
||||||
query := fmt.Sprintf(`SELECT id, event_name, template, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.EmailTemplate, eventName)
|
query := fmt.Sprintf(`SELECT id, event_name, subject, design, template, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.EmailTemplate, eventName)
|
||||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
67
server/db/providers/cassandradb/otp.go
Normal file
67
server/db/providers/cassandradb/otp.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package cassandradb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"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()
|
||||||
|
query := ""
|
||||||
|
if shouldCreate {
|
||||||
|
query = fmt.Sprintf(`INSERT INTO %s (id, email, otp, expires_at, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d, %d)`, KeySpace+"."+models.Collections.OTP, otp.ID, otp.Email, otp.Otp, otp.ExpiresAt, otp.CreatedAt, otp.UpdatedAt)
|
||||||
|
} else {
|
||||||
|
query = fmt.Sprintf(`UPDATE %s SET otp = '%s', expires_at = %d, updated_at = %d WHERE id = '%s'`, KeySpace+"."+models.Collections.OTP, otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
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 otp models.OTP
|
||||||
|
query := fmt.Sprintf(`SELECT id, email, otp, expires_at, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, emailAddress)
|
||||||
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &otp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOTP to delete otp
|
||||||
|
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
|
||||||
|
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.OTP, otp.ID)
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/gocql/gocql"
|
"github.com/gocql/gocql"
|
||||||
cansandraDriver "github.com/gocql/gocql"
|
cansandraDriver "github.com/gocql/gocql"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type provider struct {
|
type provider struct {
|
||||||
@@ -99,6 +100,7 @@ func NewProvider() (*provider, error) {
|
|||||||
cassandraClient.Consistency = gocql.LocalQuorum
|
cassandraClient.Consistency = gocql.LocalQuorum
|
||||||
cassandraClient.ConnectTimeout = 10 * time.Second
|
cassandraClient.ConnectTimeout = 10 * time.Second
|
||||||
cassandraClient.ProtoVersion = 4
|
cassandraClient.ProtoVersion = 4
|
||||||
|
cassandraClient.Timeout = 30 * time.Minute // for large data
|
||||||
|
|
||||||
session, err := cassandraClient.CreateSession()
|
session, err := cassandraClient.CreateSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -159,6 +161,13 @@ func NewProvider() (*provider, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to alter table as column exists: ", err)
|
||||||
|
// return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// token is reserved keyword in cassandra, hence we need to use jwt_token
|
// token is reserved keyword in cassandra, hence we need to use jwt_token
|
||||||
verificationRequestCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, jwt_token text, identifier text, expires_at bigint, email text, nonce text, redirect_uri text, created_at bigint, updated_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.VerificationRequest)
|
verificationRequestCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, jwt_token text, identifier text, expires_at bigint, email text, nonce text, redirect_uri text, created_at bigint, updated_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.VerificationRequest)
|
||||||
@@ -214,6 +223,24 @@ func NewProvider() (*provider, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// add subject on email_templates table
|
||||||
|
emailTemplateAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (subject text, design text);`, KeySpace, models.Collections.EmailTemplate)
|
||||||
|
err = session.Query(emailTemplateAlterQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to alter table as column exists: ", err)
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
|
||||||
|
otpCollection := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, otp text, expires_at bigint, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.OTP)
|
||||||
|
err = session.Query(otpCollection).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
otpIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_otp_email ON %s.%s (email)", KeySpace, models.Collections.OTP)
|
||||||
|
err = session.Query(otpIndexQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &provider{
|
return &provider{
|
||||||
db: session,
|
db: session,
|
||||||
|
@@ -107,7 +107,7 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
|
|||||||
}
|
}
|
||||||
|
|
||||||
if value == nil {
|
if value == nil {
|
||||||
updateFields += fmt.Sprintf("%s = null,", key)
|
updateFields += fmt.Sprintf("%s = null, ", key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,6 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
|
|||||||
updateFields = strings.TrimSuffix(updateFields, ",")
|
updateFields = strings.TrimSuffix(updateFields, ",")
|
||||||
|
|
||||||
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, updateFields, user.ID)
|
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, updateFields, user.ID)
|
||||||
|
|
||||||
err = p.db.Query(query).Exec()
|
err = p.db.Query(query).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
@@ -173,14 +172,14 @@ func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (
|
|||||||
// there is no offset in cassandra
|
// there is no offset in cassandra
|
||||||
// so we fetch till limit + offset
|
// so we fetch till limit + offset
|
||||||
// and return the results from offset to limit
|
// and return the results from offset to limit
|
||||||
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, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.User, pagination.Limit+pagination.Offset)
|
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 LIMIT %d", KeySpace+"."+models.Collections.User, pagination.Limit+pagination.Offset)
|
||||||
|
|
||||||
scanner := p.db.Query(query).Iter().Scanner()
|
scanner := p.db.Query(query).Iter().Scanner()
|
||||||
counter := int64(0)
|
counter := int64(0)
|
||||||
for scanner.Next() {
|
for scanner.Next() {
|
||||||
if counter >= pagination.Offset {
|
if counter >= pagination.Offset {
|
||||||
var user models.User
|
var user models.User
|
||||||
err := scanner.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.CreatedAt, &user.UpdatedAt)
|
err := scanner.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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -197,8 +196,8 @@ func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (
|
|||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
|
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
|
||||||
var user models.User
|
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, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
|
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 email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
|
||||||
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.CreatedAt, &user.UpdatedAt)
|
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 {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -208,10 +207,95 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.Use
|
|||||||
// GetUserByID to get user information from database using user ID
|
// GetUserByID to get user information from database using user ID
|
||||||
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
|
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
|
||||||
var user models.User
|
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, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
|
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 id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
|
||||||
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.CreatedAt, &user.UpdatedAt)
|
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 {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
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
|
||||||
|
data["updated_at"] = time.Now().Unix()
|
||||||
|
|
||||||
|
updateFields := ""
|
||||||
|
for key, value := range data {
|
||||||
|
if key == "_id" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "_key" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if value == nil {
|
||||||
|
updateFields += fmt.Sprintf("%s = null,", key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
valueType := reflect.TypeOf(value)
|
||||||
|
if valueType.Name() == "string" {
|
||||||
|
updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string))
|
||||||
|
} else {
|
||||||
|
updateFields += fmt.Sprintf("%s = %v, ", key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateFields = strings.Trim(updateFields, " ")
|
||||||
|
updateFields = strings.TrimSuffix(updateFields, ",")
|
||||||
|
|
||||||
|
query := ""
|
||||||
|
if ids != nil && len(ids) > 0 {
|
||||||
|
idsString := ""
|
||||||
|
for _, id := range ids {
|
||||||
|
idsString += fmt.Sprintf("'%s', ", id)
|
||||||
|
}
|
||||||
|
idsString = strings.Trim(idsString, " ")
|
||||||
|
idsString = strings.TrimSuffix(idsString, ",")
|
||||||
|
query = fmt.Sprintf("UPDATE %s SET %s WHERE id IN (%s)", KeySpace+"."+models.Collections.User, updateFields, idsString)
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// get all ids
|
||||||
|
getUserIDsQuery := fmt.Sprintf(`SELECT id FROM %s`, KeySpace+"."+models.Collections.User)
|
||||||
|
scanner := p.db.Query(getUserIDsQuery).Iter().Scanner()
|
||||||
|
// only 100 ids are allowed in 1 query
|
||||||
|
// hence we need create multiple update queries
|
||||||
|
idsString := ""
|
||||||
|
idsStringArray := []string{idsString}
|
||||||
|
counter := 1
|
||||||
|
for scanner.Next() {
|
||||||
|
var id string
|
||||||
|
err := scanner.Scan(&id)
|
||||||
|
if err == nil {
|
||||||
|
idsString += fmt.Sprintf("'%s', ", id)
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
if counter > 100 {
|
||||||
|
idsStringArray = append(idsStringArray, idsString)
|
||||||
|
counter = 1
|
||||||
|
idsString = ""
|
||||||
|
} else {
|
||||||
|
// update the last index of array when count is less than 100
|
||||||
|
idsStringArray[len(idsStringArray)-1] = idsString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, idStr := range idsStringArray {
|
||||||
|
idStr = strings.Trim(idStr, " ")
|
||||||
|
idStr = strings.TrimSuffix(idStr, ",")
|
||||||
|
query = fmt.Sprintf("UPDATE %s SET %s WHERE id IN (%s)", KeySpace+"."+models.Collections.User, updateFields, idStr)
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
70
server/db/providers/mongodb/otp.go
Normal file
70
server/db/providers/mongodb/otp.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
otp.UpdatedAt = time.Now().Unix()
|
||||||
|
otpCollection := p.db.Collection(models.Collections.OTP, options.Collection())
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if shouldCreate {
|
||||||
|
_, err = otpCollection.InsertOne(ctx, otp)
|
||||||
|
} else {
|
||||||
|
_, err = otpCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": otp.ID}}, bson.M{"$set": otp}, options.MergeUpdateOptions())
|
||||||
|
}
|
||||||
|
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 otp models.OTP
|
||||||
|
|
||||||
|
otpCollection := p.db.Collection(models.Collections.OTP, options.Collection())
|
||||||
|
err := otpCollection.FindOne(ctx, bson.M{"email": emailAddress}).Decode(&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 {
|
||||||
|
otpCollection := p.db.Collection(models.Collections.OTP, options.Collection())
|
||||||
|
_, err := otpCollection.DeleteOne(nil, bson.M{"_id": otp.ID}, options.Delete())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -110,6 +110,15 @@ func NewProvider() (*provider, error) {
|
|||||||
},
|
},
|
||||||
}, options.CreateIndexes())
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.OTP, options.CreateCollection())
|
||||||
|
otpCollection := mongodb.Collection(models.Collections.OTP, options.Collection())
|
||||||
|
otpCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
{
|
||||||
|
Keys: bson.M{"email": 1},
|
||||||
|
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
return &provider{
|
return &provider{
|
||||||
db: mongodb,
|
db: mongodb,
|
||||||
}, nil
|
}, nil
|
||||||
|
@@ -9,7 +9,9 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -129,3 +131,27 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, err
|
|||||||
|
|
||||||
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
|
||||||
|
data["updated_at"] = time.Now().Unix()
|
||||||
|
|
||||||
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
|
|
||||||
|
var res *mongo.UpdateResult
|
||||||
|
var err error
|
||||||
|
if ids != nil && len(ids) > 0 {
|
||||||
|
res, err = userCollection.UpdateMany(ctx, bson.M{"_id": bson.M{"$in": ids}}, bson.M{"$set": data})
|
||||||
|
} else {
|
||||||
|
res, err = userCollection.UpdateMany(ctx, bson.M{}, bson.M{"$set": data})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
log.Info("Updated users: ", res.ModifiedCount)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
22
server/db/providers/provider_template/otp.go
Normal file
22
server/db/providers/provider_template/otp.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package provider_template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpsertOTP to add or update otp
|
||||||
|
func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOTPByEmail to get otp for a given email address
|
||||||
|
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOTP to delete otp
|
||||||
|
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
|
||||||
|
return nil
|
||||||
|
}
|
@@ -60,3 +60,12 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, err
|
|||||||
|
|
||||||
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
|
||||||
|
data["updated_at"] = time.Now().Unix()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -20,6 +20,9 @@ type Provider interface {
|
|||||||
GetUserByEmail(ctx context.Context, email string) (models.User, error)
|
GetUserByEmail(ctx context.Context, email string) (models.User, error)
|
||||||
// GetUserByID to get user information from database using user ID
|
// GetUserByID to get user information from database using user ID
|
||||||
GetUserByID(ctx context.Context, id string) (models.User, error)
|
GetUserByID(ctx context.Context, id string) (models.User, error)
|
||||||
|
// UpdateUsers to update multiple users, with parameters of user IDs slice
|
||||||
|
// If ids set to nil / empty all the users will be updated
|
||||||
|
UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error
|
||||||
|
|
||||||
// AddVerification to save verification request in database
|
// AddVerification to save verification request in database
|
||||||
AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error)
|
AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error)
|
||||||
@@ -72,4 +75,11 @@ type Provider interface {
|
|||||||
GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error)
|
GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error)
|
||||||
// DeleteEmailTemplate to delete EmailTemplate
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error
|
DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error
|
||||||
|
|
||||||
|
// UpsertOTP to add or update otp
|
||||||
|
UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, error)
|
||||||
|
// GetOTPByEmail to get otp for a given email address
|
||||||
|
GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error)
|
||||||
|
// DeleteOTP to delete otp
|
||||||
|
DeleteOTP(ctx context.Context, otp *models.OTP) error
|
||||||
}
|
}
|
||||||
|
53
server/db/providers/sql/otp.go
Normal file
53
server/db/providers/sql/otp.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpsertOTP to add or update otp
|
||||||
|
func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, error) {
|
||||||
|
if otp.ID == "" {
|
||||||
|
otp.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
otp.Key = otp.ID
|
||||||
|
otp.CreatedAt = time.Now().Unix()
|
||||||
|
otp.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
res := p.db.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "email"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{"otp", "expires_at", "updated_at"}),
|
||||||
|
}).Create(&otp)
|
||||||
|
if res.Error != nil {
|
||||||
|
return nil, res.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
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 otp models.OTP
|
||||||
|
|
||||||
|
result := p.db.Where("email = ?", emailAddress).First(&otp)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return &otp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOTP to delete otp
|
||||||
|
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
|
||||||
|
result := p.db.Delete(&models.OTP{
|
||||||
|
ID: otp.ID,
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -40,6 +40,7 @@ func NewProvider() (*provider, error) {
|
|||||||
NamingStrategy: schema.NamingStrategy{
|
NamingStrategy: schema.NamingStrategy{
|
||||||
TablePrefix: models.Prefix,
|
TablePrefix: models.Prefix,
|
||||||
},
|
},
|
||||||
|
AllowGlobalUpdate: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
dbType := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseType
|
dbType := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseType
|
||||||
@@ -60,7 +61,7 @@ func NewProvider() (*provider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{})
|
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}, &models.OTP{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,3 +122,22 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, err
|
|||||||
|
|
||||||
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
|
||||||
|
data["updated_at"] = time.Now().Unix()
|
||||||
|
|
||||||
|
var res *gorm.DB
|
||||||
|
if ids != nil && len(ids) > 0 {
|
||||||
|
res = p.db.Model(&models.User{}).Where("id in ?", ids).Updates(data)
|
||||||
|
} else {
|
||||||
|
res = p.db.Model(&models.User{}).Updates(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Error != nil {
|
||||||
|
return res.Error
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -2,8 +2,8 @@ package email
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -11,27 +11,75 @@ import (
|
|||||||
gomail "gopkg.in/mail.v2"
|
gomail "gopkg.in/mail.v2"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addEmailTemplate is used to add html template in email body
|
func getDefaultTemplate(event string) *model.EmailTemplate {
|
||||||
func addEmailTemplate(a string, b map[string]interface{}, templateName string) string {
|
switch event {
|
||||||
tmpl, err := template.New(templateName).Parse(a)
|
case constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeMagicLinkLogin, constants.VerificationTypeUpdateEmail:
|
||||||
if err != nil {
|
return &model.EmailTemplate{
|
||||||
output, _ := json.Marshal(b)
|
Subject: emailVerificationSubject,
|
||||||
return string(output)
|
Template: emailVerificationTemplate,
|
||||||
|
}
|
||||||
|
case constants.VerificationTypeForgotPassword:
|
||||||
|
return &model.EmailTemplate{
|
||||||
|
Subject: forgotPasswordSubject,
|
||||||
|
Template: forgotPasswordTemplate,
|
||||||
|
}
|
||||||
|
case constants.VerificationTypeInviteMember:
|
||||||
|
return &model.EmailTemplate{
|
||||||
|
Subject: inviteEmailSubject,
|
||||||
|
Template: inviteEmailTemplate,
|
||||||
|
}
|
||||||
|
case constants.VerificationTypeOTP:
|
||||||
|
return &model.EmailTemplate{
|
||||||
|
Subject: otpEmailSubject,
|
||||||
|
Template: otpEmailTemplate,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
err = tmpl.Execute(buf, b)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
s := buf.String()
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMail function to send mail
|
func getEmailTemplate(event string, data map[string]interface{}) (*model.EmailTemplate, error) {
|
||||||
func SendMail(to []string, Subject, bodyMessage string) error {
|
ctx := context.Background()
|
||||||
|
tmp, err := db.Provider.GetEmailTemplateByEventName(ctx, event)
|
||||||
|
if err != nil || tmp == nil {
|
||||||
|
tmp = getDefaultTemplate(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
templ, err := template.New(event + "_template.tmpl").Parse(tmp.Template)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err = templ.Execute(buf, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
templateString := buf.String()
|
||||||
|
|
||||||
|
subject, err := template.New(event + "_subject.tmpl").Parse(tmp.Subject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = &bytes.Buffer{}
|
||||||
|
err = subject.Execute(buf, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
subjectString := buf.String()
|
||||||
|
|
||||||
|
return &model.EmailTemplate{
|
||||||
|
Template: templateString,
|
||||||
|
Subject: subjectString,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEmail function to send mail
|
||||||
|
func SendEmail(to []string, event string, data map[string]interface{}) error {
|
||||||
// dont trigger email sending in case of test
|
// dont trigger email sending in case of test
|
||||||
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
|
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,6 +88,13 @@ func SendMail(to []string, Subject, bodyMessage string) error {
|
|||||||
if envKey == constants.TestEnv {
|
if envKey == constants.TestEnv {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmp, err := getEmailTemplate(event, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get event template: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
m := gomail.NewMessage()
|
m := gomail.NewMessage()
|
||||||
senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)
|
senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -79,8 +134,8 @@ func SendMail(to []string, Subject, bodyMessage string) error {
|
|||||||
|
|
||||||
m.SetHeader("From", senderEmail)
|
m.SetHeader("From", senderEmail)
|
||||||
m.SetHeader("To", to...)
|
m.SetHeader("To", to...)
|
||||||
m.SetHeader("Subject", Subject)
|
m.SetHeader("Subject", tmp.Subject)
|
||||||
m.SetBody("text/html", bodyMessage)
|
m.SetBody("text/html", tmp.Template)
|
||||||
port, _ := strconv.Atoi(smtpPort)
|
port, _ := strconv.Atoi(smtpPort)
|
||||||
d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword)
|
d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword)
|
||||||
if !isProd {
|
if !isProd {
|
||||||
|
@@ -1,19 +1,8 @@
|
|||||||
package email
|
package email
|
||||||
|
|
||||||
import (
|
const (
|
||||||
log "github.com/sirupsen/logrus"
|
emailVerificationSubject = "Please verify your email"
|
||||||
|
emailVerificationTemplate = `
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendVerificationMail to send verification email
|
|
||||||
func SendVerificationMail(toEmail, token, hostname string) error {
|
|
||||||
// The receiver needs to be in slice as the receive supports multiple receiver
|
|
||||||
Receiver := []string{toEmail}
|
|
||||||
|
|
||||||
Subject := "Please verify your email"
|
|
||||||
message := `
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
<head>
|
<head>
|
||||||
@@ -98,23 +87,4 @@ func SendVerificationMail(toEmail, token, hostname string) error {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
data := make(map[string]interface{}, 3)
|
)
|
||||||
var err error
|
|
||||||
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data["verification_url"] = hostname + "/verify_email?token=" + token
|
|
||||||
message = addEmailTemplate(message, data, "verify_email.tmpl")
|
|
||||||
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
|
||||||
|
|
||||||
err = SendMail(Receiver, Subject, message)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("error sending email: ", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
@@ -1,28 +1,8 @@
|
|||||||
package email
|
package email
|
||||||
|
|
||||||
import (
|
const (
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
forgotPasswordSubject = "Reset Password"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
forgotPasswordTemplate = `
|
||||||
)
|
|
||||||
|
|
||||||
// SendForgotPasswordMail to send forgot password email
|
|
||||||
func SendForgotPasswordMail(toEmail, token, hostname string) error {
|
|
||||||
resetPasswordUrl, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resetPasswordUrl == "" {
|
|
||||||
if err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The receiver needs to be in slice as the receive supports multiple receiver
|
|
||||||
Receiver := []string{toEmail}
|
|
||||||
|
|
||||||
Subject := "Reset Password"
|
|
||||||
|
|
||||||
message := `
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
<head>
|
<head>
|
||||||
@@ -73,13 +53,13 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
|
|||||||
<table width="100%%" cellspacing="0" cellpadding="0">
|
<table width="100%%" cellspacing="0" cellpadding="0">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
|
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.organization.logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
||||||
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
||||||
<p>Hey there 👋</p>
|
<p>Hey there 👋</p>
|
||||||
<p>We have received a request to reset password for email: <b>{{.org_name}}</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
|
<p>We have received a request to reset password for email: <b>{{.organization.name}}</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
|
||||||
<a clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a>
|
<a clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -106,18 +86,4 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
)
|
||||||
data := make(map[string]interface{}, 3)
|
|
||||||
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data["verification_url"] = resetPasswordUrl + "?token=" + token
|
|
||||||
message = addEmailTemplate(message, data, "reset_password_email.tmpl")
|
|
||||||
|
|
||||||
return SendMail(Receiver, Subject, message)
|
|
||||||
}
|
|
||||||
|
@@ -1,19 +1,8 @@
|
|||||||
package email
|
package email
|
||||||
|
|
||||||
import (
|
const (
|
||||||
log "github.com/sirupsen/logrus"
|
inviteEmailSubject = "Please accept the invitation"
|
||||||
|
inviteEmailTemplate = `
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InviteEmail to send invite email
|
|
||||||
func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
|
|
||||||
// The receiver needs to be in slice as the receive supports multiple receiver
|
|
||||||
Receiver := []string{toEmail}
|
|
||||||
|
|
||||||
Subject := "Please accept the invitation"
|
|
||||||
message := `
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
<head>
|
<head>
|
||||||
@@ -64,13 +53,13 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
|
|||||||
<table width="100%%" cellspacing="0" cellpadding="0">
|
<table width="100%%" cellspacing="0" cellpadding="0">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
|
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.organization.logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
||||||
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
||||||
<p>Hi there 👋</p>
|
<p>Hi there 👋</p>
|
||||||
<p>Join us! You are invited to sign-up for <b>{{.org_name}}</b>. Please accept the invitation by clicking the button below.</p> <br/>
|
<p>Join us! You are invited to sign-up for <b>{{.organization.name}}</b>. Please accept the invitation by clicking the button below.</p> <br/>
|
||||||
<a
|
<a
|
||||||
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a>
|
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -98,23 +87,4 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
data := make(map[string]interface{}, 3)
|
)
|
||||||
var err error
|
|
||||||
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data["verification_url"] = verificationURL + "?token=" + token + "&redirect_uri=" + redirectURI
|
|
||||||
message = addEmailTemplate(message, data, "invite_email.tmpl")
|
|
||||||
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
|
||||||
|
|
||||||
err = SendMail(Receiver, Subject, message)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("error sending email: ", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
88
server/email/otp.go
Normal file
88
server/email/otp.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
const (
|
||||||
|
otpEmailSubject = "OTP for your multi factor authentication"
|
||||||
|
otpEmailTemplate = `
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||||
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta content="telephone=no" name="format-detection">
|
||||||
|
<title></title>
|
||||||
|
<!--[if (mso 16)]>
|
||||||
|
<style type="text/css">
|
||||||
|
a {}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if gte mso 9]><style>sup { font-size: 100%% !important; }</style><![endif]-->
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG></o:AllowPNG>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body style="font-family: sans-serif;">
|
||||||
|
<div class="es-wrapper-color">
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
|
||||||
|
<v:fill type="tile" color="#ffffff"></v:fill>
|
||||||
|
</v:background>
|
||||||
|
<![endif]-->
|
||||||
|
<table class="es-wrapper" width="100%%" cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-email-paddings" valign="top">
|
||||||
|
<table class="es-content esd-footer-popover" cellspacing="0" cellpadding="0" align="center">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-stripe" align="center">
|
||||||
|
<table class="es-content-body" style="border-left:1px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-bottom:1px solid transparent;padding:20px 0px;" width="600" cellspacing="0" cellpadding="0" bgcolor="#ffffff" align="center">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-structure es-p20t es-p40b es-p40r es-p40l" esd-custom-block-id="8537" align="left">
|
||||||
|
<table width="100%%" cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-container-frame" width="518" align="left">
|
||||||
|
<table width="100%%" cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.organization.logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
||||||
|
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
||||||
|
<p>Hey there 👋</p>
|
||||||
|
<b>{{.otp}}</b> is your one time password (OTP) for accessing {{.organization.name}}. Please keep your OTP confidential and it will expire in 1 minute.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
)
|
65
server/env/env.go
vendored
65
server/env/env.go
vendored
@@ -72,6 +72,8 @@ func InitAllEnv() error {
|
|||||||
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
|
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
|
||||||
osAppleClientID := os.Getenv(constants.EnvKeyAppleClientID)
|
osAppleClientID := os.Getenv(constants.EnvKeyAppleClientID)
|
||||||
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
|
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
|
||||||
|
osTwitterClientID := os.Getenv(constants.EnvKeyTwitterClientID)
|
||||||
|
osTwitterClientSecret := os.Getenv(constants.EnvKeyTwitterClientSecret)
|
||||||
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
|
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
|
||||||
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
|
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
|
||||||
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
|
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
|
||||||
@@ -84,6 +86,8 @@ func InitAllEnv() error {
|
|||||||
osDisableSignUp := os.Getenv(constants.EnvKeyDisableSignUp)
|
osDisableSignUp := os.Getenv(constants.EnvKeyDisableSignUp)
|
||||||
osDisableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv)
|
osDisableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv)
|
||||||
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
|
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
|
||||||
|
osEnforceMultiFactorAuthentication := os.Getenv(constants.EnvKeyEnforceMultiFactorAuthentication)
|
||||||
|
osDisableMultiFactorAuthentication := os.Getenv(constants.EnvKeyDisableMultiFactorAuthentication)
|
||||||
|
|
||||||
// os slice vars
|
// os slice vars
|
||||||
osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins)
|
osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins)
|
||||||
@@ -353,31 +357,45 @@ func InitAllEnv() error {
|
|||||||
if val, ok := envData[constants.EnvKeyLinkedInClientID]; !ok || val == "" {
|
if val, ok := envData[constants.EnvKeyLinkedInClientID]; !ok || val == "" {
|
||||||
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
|
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
|
||||||
}
|
}
|
||||||
if osFacebookClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osFacebookClientID {
|
if osLinkedInClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osLinkedInClientID {
|
||||||
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
|
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := envData[constants.EnvKeyLinkedInClientSecret]; !ok || val == "" {
|
if val, ok := envData[constants.EnvKeyLinkedInClientSecret]; !ok || val == "" {
|
||||||
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
|
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
|
||||||
}
|
}
|
||||||
if osFacebookClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osFacebookClientSecret {
|
if osLinkedInClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osLinkedInClientSecret {
|
||||||
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
|
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := envData[constants.EnvKeyAppleClientID]; !ok || val == "" {
|
if val, ok := envData[constants.EnvKeyAppleClientID]; !ok || val == "" {
|
||||||
envData[constants.EnvKeyAppleClientID] = osAppleClientID
|
envData[constants.EnvKeyAppleClientID] = osAppleClientID
|
||||||
}
|
}
|
||||||
if osFacebookClientID != "" && envData[constants.EnvKeyAppleClientID] != osFacebookClientID {
|
if osAppleClientID != "" && envData[constants.EnvKeyAppleClientID] != osAppleClientID {
|
||||||
envData[constants.EnvKeyAppleClientID] = osAppleClientID
|
envData[constants.EnvKeyAppleClientID] = osAppleClientID
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := envData[constants.EnvKeyAppleClientSecret]; !ok || val == "" {
|
if val, ok := envData[constants.EnvKeyAppleClientSecret]; !ok || val == "" {
|
||||||
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
|
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
|
||||||
}
|
}
|
||||||
if osFacebookClientSecret != "" && envData[constants.EnvKeyAppleClientSecret] != osFacebookClientSecret {
|
if osAppleClientSecret != "" && envData[constants.EnvKeyAppleClientSecret] != osAppleClientSecret {
|
||||||
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
|
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, ok := envData[constants.EnvKeyTwitterClientID]; !ok || val == "" {
|
||||||
|
envData[constants.EnvKeyTwitterClientID] = osTwitterClientID
|
||||||
|
}
|
||||||
|
if osTwitterClientID != "" && envData[constants.EnvKeyTwitterClientID] != osTwitterClientID {
|
||||||
|
envData[constants.EnvKeyTwitterClientID] = osTwitterClientID
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := envData[constants.EnvKeyTwitterClientSecret]; !ok || val == "" {
|
||||||
|
envData[constants.EnvKeyTwitterClientSecret] = osTwitterClientSecret
|
||||||
|
}
|
||||||
|
if osTwitterClientSecret != "" && envData[constants.EnvKeyTwitterClientSecret] != osTwitterClientSecret {
|
||||||
|
envData[constants.EnvKeyTwitterClientSecret] = osTwitterClientSecret
|
||||||
|
}
|
||||||
|
|
||||||
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
|
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
|
||||||
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
|
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
|
||||||
}
|
}
|
||||||
@@ -490,10 +508,49 @@ func InitAllEnv() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := envData[constants.EnvKeyEnforceMultiFactorAuthentication]; !ok {
|
||||||
|
envData[constants.EnvKeyEnforceMultiFactorAuthentication] = osEnforceMultiFactorAuthentication == "true"
|
||||||
|
}
|
||||||
|
if osEnforceMultiFactorAuthentication != "" {
|
||||||
|
boolValue, err := strconv.ParseBool(osEnforceMultiFactorAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if boolValue != envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) {
|
||||||
|
envData[constants.EnvKeyEnforceMultiFactorAuthentication] = boolValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := envData[constants.EnvKeyDisableMultiFactorAuthentication]; !ok {
|
||||||
|
envData[constants.EnvKeyDisableMultiFactorAuthentication] = osDisableMultiFactorAuthentication == "true"
|
||||||
|
}
|
||||||
|
if osDisableMultiFactorAuthentication != "" {
|
||||||
|
boolValue, err := strconv.ParseBool(osDisableMultiFactorAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if boolValue != envData[constants.EnvKeyDisableMultiFactorAuthentication].(bool) {
|
||||||
|
envData[constants.EnvKeyDisableMultiFactorAuthentication] = boolValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// no need to add nil check as its already done above
|
// no need to add nil check as its already done above
|
||||||
if envData[constants.EnvKeySmtpHost] == "" || envData[constants.EnvKeySmtpUsername] == "" || envData[constants.EnvKeySmtpPassword] == "" || envData[constants.EnvKeySenderEmail] == "" && envData[constants.EnvKeySmtpPort] == "" {
|
if envData[constants.EnvKeySmtpHost] == "" || envData[constants.EnvKeySmtpUsername] == "" || envData[constants.EnvKeySmtpPassword] == "" || envData[constants.EnvKeySenderEmail] == "" && envData[constants.EnvKeySmtpPort] == "" {
|
||||||
envData[constants.EnvKeyDisableEmailVerification] = true
|
envData[constants.EnvKeyDisableEmailVerification] = true
|
||||||
envData[constants.EnvKeyDisableMagicLinkLogin] = true
|
envData[constants.EnvKeyDisableMagicLinkLogin] = true
|
||||||
|
envData[constants.EnvKeyIsEmailServiceEnabled] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData[constants.EnvKeySmtpHost] != "" || envData[constants.EnvKeySmtpUsername] != "" || envData[constants.EnvKeySmtpPassword] != "" || envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" {
|
||||||
|
envData[constants.EnvKeyIsEmailServiceEnabled] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) {
|
||||||
|
return errors.New("to enable multi factor authentication, please enable email service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) {
|
||||||
|
envData[constants.EnvKeyDisableMultiFactorAuthentication] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData[constants.EnvKeyDisableEmailVerification].(bool) {
|
if envData[constants.EnvKeyDisableEmailVerification].(bool) {
|
||||||
|
4
server/env/persist_env.go
vendored
4
server/env/persist_env.go
vendored
@@ -201,7 +201,7 @@ func PersistEnv() error {
|
|||||||
envValue := strings.TrimSpace(os.Getenv(key))
|
envValue := strings.TrimSpace(os.Getenv(key))
|
||||||
if envValue != "" {
|
if envValue != "" {
|
||||||
switch key {
|
switch key {
|
||||||
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword:
|
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication:
|
||||||
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
|
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
|
||||||
if value.(bool) != envValueBool {
|
if value.(bool) != envValueBool {
|
||||||
storeData[key] = envValueBool
|
storeData[key] = envValueBool
|
||||||
@@ -221,6 +221,8 @@ func PersistEnv() error {
|
|||||||
// handle derivative cases like disabling email verification & magic login
|
// handle derivative cases like disabling email verification & magic login
|
||||||
// in case SMTP is off but env is set to true
|
// in case SMTP is off but env is set to true
|
||||||
if storeData[constants.EnvKeySmtpHost] == "" || storeData[constants.EnvKeySmtpUsername] == "" || storeData[constants.EnvKeySmtpPassword] == "" || storeData[constants.EnvKeySenderEmail] == "" && storeData[constants.EnvKeySmtpPort] == "" {
|
if storeData[constants.EnvKeySmtpHost] == "" || storeData[constants.EnvKeySmtpUsername] == "" || storeData[constants.EnvKeySmtpPassword] == "" || storeData[constants.EnvKeySenderEmail] == "" && storeData[constants.EnvKeySmtpPort] == "" {
|
||||||
|
storeData[constants.EnvKeyIsEmailServiceEnabled] = false
|
||||||
|
|
||||||
if !storeData[constants.EnvKeyDisableEmailVerification].(bool) {
|
if !storeData[constants.EnvKeyDisableEmailVerification].(bool) {
|
||||||
storeData[constants.EnvKeyDisableEmailVerification] = true
|
storeData[constants.EnvKeyDisableEmailVerification] = true
|
||||||
hasChanged = true
|
hasChanged = true
|
||||||
|
@@ -9,7 +9,7 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.7.2
|
github.com/gin-gonic/gin v1.7.2
|
||||||
github.com/go-playground/validator/v10 v10.8.0 // indirect
|
github.com/go-playground/validator/v10 v10.8.0 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.0
|
github.com/go-redis/redis/v8 v8.11.0
|
||||||
github.com/gocql/gocql v1.0.0
|
github.com/gocql/gocql v1.2.0
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
|
@@ -110,8 +110,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
|
|||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gocql/gocql v1.0.0 h1:UnbTERpP72VZ/viKE1Q1gPtmLvyTZTvuAstvSRydw/c=
|
github.com/gocql/gocql v1.2.0 h1:TZhsCd7fRuye4VyHr3WCvWwIQaZUmjsqnSIXK9FcVCE=
|
||||||
github.com/gocql/gocql v1.0.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
|
github.com/gocql/gocql v1.2.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,9 @@ package model
|
|||||||
|
|
||||||
type AddEmailTemplateRequest struct {
|
type AddEmailTemplateRequest struct {
|
||||||
EventName string `json:"event_name"`
|
EventName string `json:"event_name"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
Template string `json:"template"`
|
Template string `json:"template"`
|
||||||
|
Design string `json:"design"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddWebhookRequest struct {
|
type AddWebhookRequest struct {
|
||||||
@@ -23,12 +25,13 @@ type AdminSignupInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
AccessToken *string `json:"access_token"`
|
ShouldShowOtpScreen *bool `json:"should_show_otp_screen"`
|
||||||
IDToken *string `json:"id_token"`
|
AccessToken *string `json:"access_token"`
|
||||||
RefreshToken *string `json:"refresh_token"`
|
IDToken *string `json:"id_token"`
|
||||||
ExpiresIn *int64 `json:"expires_in"`
|
RefreshToken *string `json:"refresh_token"`
|
||||||
User *User `json:"user"`
|
ExpiresIn *int64 `json:"expires_in"`
|
||||||
|
User *User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteEmailTemplateRequest struct {
|
type DeleteEmailTemplateRequest struct {
|
||||||
@@ -43,64 +46,70 @@ type EmailTemplate struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
EventName string `json:"event_name"`
|
EventName string `json:"event_name"`
|
||||||
Template string `json:"template"`
|
Template string `json:"template"`
|
||||||
|
Design string `json:"design"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
CreatedAt *int64 `json:"created_at"`
|
CreatedAt *int64 `json:"created_at"`
|
||||||
UpdatedAt *int64 `json:"updated_at"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailTemplates struct {
|
type EmailTemplates struct {
|
||||||
Pagination *Pagination `json:"pagination"`
|
Pagination *Pagination `json:"pagination"`
|
||||||
EmailTemplates []*EmailTemplate `json:"EmailTemplates"`
|
EmailTemplates []*EmailTemplate `json:"email_templates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Env struct {
|
type Env struct {
|
||||||
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
DatabaseName *string `json:"DATABASE_NAME"`
|
DatabaseName *string `json:"DATABASE_NAME"`
|
||||||
DatabaseURL *string `json:"DATABASE_URL"`
|
DatabaseURL *string `json:"DATABASE_URL"`
|
||||||
DatabaseType *string `json:"DATABASE_TYPE"`
|
DatabaseType *string `json:"DATABASE_TYPE"`
|
||||||
DatabaseUsername *string `json:"DATABASE_USERNAME"`
|
DatabaseUsername *string `json:"DATABASE_USERNAME"`
|
||||||
DatabasePassword *string `json:"DATABASE_PASSWORD"`
|
DatabasePassword *string `json:"DATABASE_PASSWORD"`
|
||||||
DatabaseHost *string `json:"DATABASE_HOST"`
|
DatabaseHost *string `json:"DATABASE_HOST"`
|
||||||
DatabasePort *string `json:"DATABASE_PORT"`
|
DatabasePort *string `json:"DATABASE_PORT"`
|
||||||
ClientID string `json:"CLIENT_ID"`
|
ClientID string `json:"CLIENT_ID"`
|
||||||
ClientSecret string `json:"CLIENT_SECRET"`
|
ClientSecret string `json:"CLIENT_SECRET"`
|
||||||
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
||||||
SMTPHost *string `json:"SMTP_HOST"`
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
SMTPPort *string `json:"SMTP_PORT"`
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
SMTPUsername *string `json:"SMTP_USERNAME"`
|
SMTPUsername *string `json:"SMTP_USERNAME"`
|
||||||
SMTPPassword *string `json:"SMTP_PASSWORD"`
|
SMTPPassword *string `json:"SMTP_PASSWORD"`
|
||||||
SenderEmail *string `json:"SENDER_EMAIL"`
|
SenderEmail *string `json:"SENDER_EMAIL"`
|
||||||
JwtType *string `json:"JWT_TYPE"`
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
JwtSecret *string `json:"JWT_SECRET"`
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"`
|
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"`
|
||||||
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"`
|
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"`
|
||||||
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
AppURL *string `json:"APP_URL"`
|
AppURL *string `json:"APP_URL"`
|
||||||
RedisURL *string `json:"REDIS_URL"`
|
RedisURL *string `json:"REDIS_URL"`
|
||||||
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
||||||
DisableEmailVerification bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
DisableEmailVerification bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
||||||
DisableBasicAuthentication bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
DisableBasicAuthentication bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
DisableMagicLinkLogin bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
DisableMagicLinkLogin bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"`
|
DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
DisableSignUp bool `json:"DISABLE_SIGN_UP"`
|
DisableSignUp bool `json:"DISABLE_SIGN_UP"`
|
||||||
DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"`
|
DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"`
|
||||||
DisableStrongPassword bool `json:"DISABLE_STRONG_PASSWORD"`
|
DisableStrongPassword bool `json:"DISABLE_STRONG_PASSWORD"`
|
||||||
Roles []string `json:"ROLES"`
|
DisableMultiFactorAuthentication bool `json:"DISABLE_MULTI_FACTOR_AUTHENTICATION"`
|
||||||
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
EnforceMultiFactorAuthentication bool `json:"ENFORCE_MULTI_FACTOR_AUTHENTICATION"`
|
||||||
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
Roles []string `json:"ROLES"`
|
||||||
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
||||||
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
||||||
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
||||||
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
||||||
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
||||||
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
|
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
||||||
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
|
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
||||||
AppleClientID *string `json:"APPLE_CLIENT_ID"`
|
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
|
||||||
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
|
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
|
||||||
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
AppleClientID *string `json:"APPLE_CLIENT_ID"`
|
||||||
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
|
||||||
|
TwitterClientID *string `json:"TWITTER_CLIENT_ID"`
|
||||||
|
TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET"`
|
||||||
|
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
||||||
|
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
@@ -157,11 +166,13 @@ type Meta struct {
|
|||||||
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
|
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
|
||||||
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
|
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
|
||||||
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
|
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
|
||||||
|
IsTwitterLoginEnabled bool `json:"is_twitter_login_enabled"`
|
||||||
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
|
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
|
||||||
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
|
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
|
||||||
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
||||||
IsSignUpEnabled bool `json:"is_sign_up_enabled"`
|
IsSignUpEnabled bool `json:"is_sign_up_enabled"`
|
||||||
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"`
|
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"`
|
||||||
|
IsMultiFactorAuthEnabled bool `json:"is_multi_factor_auth_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OAuthRevokeInput struct {
|
type OAuthRevokeInput struct {
|
||||||
@@ -184,6 +195,10 @@ type PaginationInput struct {
|
|||||||
Page *int64 `json:"page"`
|
Page *int64 `json:"page"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResendOTPRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
type ResendVerifyEmailInput struct {
|
type ResendVerifyEmailInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Identifier string `json:"identifier"`
|
Identifier string `json:"identifier"`
|
||||||
@@ -205,20 +220,21 @@ type SessionQueryInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignUpInput struct {
|
type SignUpInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
GivenName *string `json:"given_name"`
|
GivenName *string `json:"given_name"`
|
||||||
FamilyName *string `json:"family_name"`
|
FamilyName *string `json:"family_name"`
|
||||||
MiddleName *string `json:"middle_name"`
|
MiddleName *string `json:"middle_name"`
|
||||||
Nickname *string `json:"nickname"`
|
Nickname *string `json:"nickname"`
|
||||||
Gender *string `json:"gender"`
|
Gender *string `json:"gender"`
|
||||||
Birthdate *string `json:"birthdate"`
|
Birthdate *string `json:"birthdate"`
|
||||||
PhoneNumber *string `json:"phone_number"`
|
PhoneNumber *string `json:"phone_number"`
|
||||||
Picture *string `json:"picture"`
|
Picture *string `json:"picture"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
ConfirmPassword string `json:"confirm_password"`
|
ConfirmPassword string `json:"confirm_password"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
Scope []string `json:"scope"`
|
Scope []string `json:"scope"`
|
||||||
RedirectURI *string `json:"redirect_uri"`
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
|
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestEndpointRequest struct {
|
type TestEndpointRequest struct {
|
||||||
@@ -228,8 +244,8 @@ type TestEndpointRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TestEndpointResponse struct {
|
type TestEndpointResponse struct {
|
||||||
HTTPStatus *int64 `json:"http_status"`
|
HTTPStatus *int64 `json:"http_status"`
|
||||||
Response map[string]interface{} `json:"response"`
|
Response *string `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateAccessInput struct {
|
type UpdateAccessInput struct {
|
||||||
@@ -240,78 +256,86 @@ type UpdateEmailTemplateRequest struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
EventName *string `json:"event_name"`
|
EventName *string `json:"event_name"`
|
||||||
Template *string `json:"template"`
|
Template *string `json:"template"`
|
||||||
|
Subject *string `json:"subject"`
|
||||||
|
Design *string `json:"design"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateEnvInput struct {
|
type UpdateEnvInput struct {
|
||||||
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
||||||
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
||||||
SMTPHost *string `json:"SMTP_HOST"`
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
SMTPPort *string `json:"SMTP_PORT"`
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
SMTPUsername *string `json:"SMTP_USERNAME"`
|
SMTPUsername *string `json:"SMTP_USERNAME"`
|
||||||
SMTPPassword *string `json:"SMTP_PASSWORD"`
|
SMTPPassword *string `json:"SMTP_PASSWORD"`
|
||||||
SenderEmail *string `json:"SENDER_EMAIL"`
|
SenderEmail *string `json:"SENDER_EMAIL"`
|
||||||
JwtType *string `json:"JWT_TYPE"`
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
JwtSecret *string `json:"JWT_SECRET"`
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"`
|
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"`
|
||||||
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"`
|
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"`
|
||||||
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
AppURL *string `json:"APP_URL"`
|
AppURL *string `json:"APP_URL"`
|
||||||
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
||||||
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
||||||
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
DisableSignUp *bool `json:"DISABLE_SIGN_UP"`
|
DisableSignUp *bool `json:"DISABLE_SIGN_UP"`
|
||||||
DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV"`
|
DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV"`
|
||||||
DisableStrongPassword *bool `json:"DISABLE_STRONG_PASSWORD"`
|
DisableStrongPassword *bool `json:"DISABLE_STRONG_PASSWORD"`
|
||||||
Roles []string `json:"ROLES"`
|
DisableMultiFactorAuthentication *bool `json:"DISABLE_MULTI_FACTOR_AUTHENTICATION"`
|
||||||
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
EnforceMultiFactorAuthentication *bool `json:"ENFORCE_MULTI_FACTOR_AUTHENTICATION"`
|
||||||
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
Roles []string `json:"ROLES"`
|
||||||
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
||||||
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
||||||
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
||||||
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
||||||
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
||||||
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
|
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
||||||
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
|
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
||||||
AppleClientID *string `json:"APPLE_CLIENT_ID"`
|
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
|
||||||
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
|
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
|
||||||
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
AppleClientID *string `json:"APPLE_CLIENT_ID"`
|
||||||
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
|
||||||
|
TwitterClientID *string `json:"TWITTER_CLIENT_ID"`
|
||||||
|
TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET"`
|
||||||
|
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
||||||
|
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProfileInput struct {
|
type UpdateProfileInput struct {
|
||||||
OldPassword *string `json:"old_password"`
|
OldPassword *string `json:"old_password"`
|
||||||
NewPassword *string `json:"new_password"`
|
NewPassword *string `json:"new_password"`
|
||||||
ConfirmNewPassword *string `json:"confirm_new_password"`
|
ConfirmNewPassword *string `json:"confirm_new_password"`
|
||||||
Email *string `json:"email"`
|
Email *string `json:"email"`
|
||||||
GivenName *string `json:"given_name"`
|
GivenName *string `json:"given_name"`
|
||||||
FamilyName *string `json:"family_name"`
|
FamilyName *string `json:"family_name"`
|
||||||
MiddleName *string `json:"middle_name"`
|
MiddleName *string `json:"middle_name"`
|
||||||
Nickname *string `json:"nickname"`
|
Nickname *string `json:"nickname"`
|
||||||
Gender *string `json:"gender"`
|
Gender *string `json:"gender"`
|
||||||
Birthdate *string `json:"birthdate"`
|
Birthdate *string `json:"birthdate"`
|
||||||
PhoneNumber *string `json:"phone_number"`
|
PhoneNumber *string `json:"phone_number"`
|
||||||
Picture *string `json:"picture"`
|
Picture *string `json:"picture"`
|
||||||
|
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserInput struct {
|
type UpdateUserInput struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email *string `json:"email"`
|
Email *string `json:"email"`
|
||||||
EmailVerified *bool `json:"email_verified"`
|
EmailVerified *bool `json:"email_verified"`
|
||||||
GivenName *string `json:"given_name"`
|
GivenName *string `json:"given_name"`
|
||||||
FamilyName *string `json:"family_name"`
|
FamilyName *string `json:"family_name"`
|
||||||
MiddleName *string `json:"middle_name"`
|
MiddleName *string `json:"middle_name"`
|
||||||
Nickname *string `json:"nickname"`
|
Nickname *string `json:"nickname"`
|
||||||
Gender *string `json:"gender"`
|
Gender *string `json:"gender"`
|
||||||
Birthdate *string `json:"birthdate"`
|
Birthdate *string `json:"birthdate"`
|
||||||
PhoneNumber *string `json:"phone_number"`
|
PhoneNumber *string `json:"phone_number"`
|
||||||
Picture *string `json:"picture"`
|
Picture *string `json:"picture"`
|
||||||
Roles []*string `json:"roles"`
|
Roles []*string `json:"roles"`
|
||||||
|
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateWebhookRequest struct {
|
type UpdateWebhookRequest struct {
|
||||||
@@ -323,24 +347,25 @@ type UpdateWebhookRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
SignupMethods string `json:"signup_methods"`
|
SignupMethods string `json:"signup_methods"`
|
||||||
GivenName *string `json:"given_name"`
|
GivenName *string `json:"given_name"`
|
||||||
FamilyName *string `json:"family_name"`
|
FamilyName *string `json:"family_name"`
|
||||||
MiddleName *string `json:"middle_name"`
|
MiddleName *string `json:"middle_name"`
|
||||||
Nickname *string `json:"nickname"`
|
Nickname *string `json:"nickname"`
|
||||||
PreferredUsername *string `json:"preferred_username"`
|
PreferredUsername *string `json:"preferred_username"`
|
||||||
Gender *string `json:"gender"`
|
Gender *string `json:"gender"`
|
||||||
Birthdate *string `json:"birthdate"`
|
Birthdate *string `json:"birthdate"`
|
||||||
PhoneNumber *string `json:"phone_number"`
|
PhoneNumber *string `json:"phone_number"`
|
||||||
PhoneNumberVerified *bool `json:"phone_number_verified"`
|
PhoneNumberVerified *bool `json:"phone_number_verified"`
|
||||||
Picture *string `json:"picture"`
|
Picture *string `json:"picture"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
CreatedAt *int64 `json:"created_at"`
|
CreatedAt *int64 `json:"created_at"`
|
||||||
UpdatedAt *int64 `json:"updated_at"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
RevokedTimestamp *int64 `json:"revoked_timestamp"`
|
RevokedTimestamp *int64 `json:"revoked_timestamp"`
|
||||||
|
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users struct {
|
type Users struct {
|
||||||
@@ -379,6 +404,11 @@ type VerifyEmailInput struct {
|
|||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VerifyOTPRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Otp string `json:"otp"`
|
||||||
|
}
|
||||||
|
|
||||||
type Webhook struct {
|
type Webhook struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
EventName *string `json:"event_name"`
|
EventName *string `json:"event_name"`
|
||||||
|
@@ -20,11 +20,13 @@ type Meta {
|
|||||||
is_github_login_enabled: Boolean!
|
is_github_login_enabled: Boolean!
|
||||||
is_linkedin_login_enabled: Boolean!
|
is_linkedin_login_enabled: Boolean!
|
||||||
is_apple_login_enabled: Boolean!
|
is_apple_login_enabled: Boolean!
|
||||||
|
is_twitter_login_enabled: Boolean!
|
||||||
is_email_verification_enabled: Boolean!
|
is_email_verification_enabled: Boolean!
|
||||||
is_basic_authentication_enabled: Boolean!
|
is_basic_authentication_enabled: Boolean!
|
||||||
is_magic_link_login_enabled: Boolean!
|
is_magic_link_login_enabled: Boolean!
|
||||||
is_sign_up_enabled: Boolean!
|
is_sign_up_enabled: Boolean!
|
||||||
is_strong_password_enabled: Boolean!
|
is_strong_password_enabled: Boolean!
|
||||||
|
is_multi_factor_auth_enabled: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
@@ -47,6 +49,7 @@ type User {
|
|||||||
created_at: Int64
|
created_at: Int64
|
||||||
updated_at: Int64
|
updated_at: Int64
|
||||||
revoked_timestamp: Int64
|
revoked_timestamp: Int64
|
||||||
|
is_multi_factor_auth_enabled: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users {
|
type Users {
|
||||||
@@ -78,6 +81,7 @@ type Error {
|
|||||||
|
|
||||||
type AuthResponse {
|
type AuthResponse {
|
||||||
message: String!
|
message: String!
|
||||||
|
should_show_otp_screen: Boolean
|
||||||
access_token: String
|
access_token: String
|
||||||
id_token: String
|
id_token: String
|
||||||
refresh_token: String
|
refresh_token: String
|
||||||
@@ -122,6 +126,8 @@ type Env {
|
|||||||
DISABLE_SIGN_UP: Boolean!
|
DISABLE_SIGN_UP: Boolean!
|
||||||
DISABLE_REDIS_FOR_ENV: Boolean!
|
DISABLE_REDIS_FOR_ENV: Boolean!
|
||||||
DISABLE_STRONG_PASSWORD: Boolean!
|
DISABLE_STRONG_PASSWORD: Boolean!
|
||||||
|
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean!
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean!
|
||||||
ROLES: [String!]
|
ROLES: [String!]
|
||||||
PROTECTED_ROLES: [String!]
|
PROTECTED_ROLES: [String!]
|
||||||
DEFAULT_ROLES: [String!]
|
DEFAULT_ROLES: [String!]
|
||||||
@@ -136,6 +142,8 @@ type Env {
|
|||||||
LINKEDIN_CLIENT_SECRET: String
|
LINKEDIN_CLIENT_SECRET: String
|
||||||
APPLE_CLIENT_ID: String
|
APPLE_CLIENT_ID: String
|
||||||
APPLE_CLIENT_SECRET: String
|
APPLE_CLIENT_SECRET: String
|
||||||
|
TWITTER_CLIENT_ID: String
|
||||||
|
TWITTER_CLIENT_SECRET: String
|
||||||
ORGANIZATION_NAME: String
|
ORGANIZATION_NAME: String
|
||||||
ORGANIZATION_LOGO: String
|
ORGANIZATION_LOGO: String
|
||||||
}
|
}
|
||||||
@@ -177,7 +185,7 @@ type WebhookLog {
|
|||||||
|
|
||||||
type TestEndpointResponse {
|
type TestEndpointResponse {
|
||||||
http_status: Int64
|
http_status: Int64
|
||||||
response: Map
|
response: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebhookLogs {
|
type WebhookLogs {
|
||||||
@@ -189,13 +197,15 @@ type EmailTemplate {
|
|||||||
id: ID!
|
id: ID!
|
||||||
event_name: String!
|
event_name: String!
|
||||||
template: String!
|
template: String!
|
||||||
|
design: String!
|
||||||
|
subject: String!
|
||||||
created_at: Int64
|
created_at: Int64
|
||||||
updated_at: Int64
|
updated_at: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailTemplates {
|
type EmailTemplates {
|
||||||
pagination: Pagination!
|
pagination: Pagination!
|
||||||
EmailTemplates: [EmailTemplate!]!
|
email_templates: [EmailTemplate!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateEnvInput {
|
input UpdateEnvInput {
|
||||||
@@ -222,6 +232,8 @@ input UpdateEnvInput {
|
|||||||
DISABLE_SIGN_UP: Boolean
|
DISABLE_SIGN_UP: Boolean
|
||||||
DISABLE_REDIS_FOR_ENV: Boolean
|
DISABLE_REDIS_FOR_ENV: Boolean
|
||||||
DISABLE_STRONG_PASSWORD: Boolean
|
DISABLE_STRONG_PASSWORD: Boolean
|
||||||
|
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean
|
||||||
ROLES: [String!]
|
ROLES: [String!]
|
||||||
PROTECTED_ROLES: [String!]
|
PROTECTED_ROLES: [String!]
|
||||||
DEFAULT_ROLES: [String!]
|
DEFAULT_ROLES: [String!]
|
||||||
@@ -236,6 +248,8 @@ input UpdateEnvInput {
|
|||||||
LINKEDIN_CLIENT_SECRET: String
|
LINKEDIN_CLIENT_SECRET: String
|
||||||
APPLE_CLIENT_ID: String
|
APPLE_CLIENT_ID: String
|
||||||
APPLE_CLIENT_SECRET: String
|
APPLE_CLIENT_SECRET: String
|
||||||
|
TWITTER_CLIENT_ID: String
|
||||||
|
TWITTER_CLIENT_SECRET: String
|
||||||
ORGANIZATION_NAME: String
|
ORGANIZATION_NAME: String
|
||||||
ORGANIZATION_LOGO: String
|
ORGANIZATION_LOGO: String
|
||||||
}
|
}
|
||||||
@@ -263,6 +277,7 @@ input SignUpInput {
|
|||||||
roles: [String!]
|
roles: [String!]
|
||||||
scope: [String!]
|
scope: [String!]
|
||||||
redirect_uri: String
|
redirect_uri: String
|
||||||
|
is_multi_factor_auth_enabled: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input LoginInput {
|
input LoginInput {
|
||||||
@@ -294,6 +309,7 @@ input UpdateProfileInput {
|
|||||||
birthdate: String
|
birthdate: String
|
||||||
phone_number: String
|
phone_number: String
|
||||||
picture: String
|
picture: String
|
||||||
|
is_multi_factor_auth_enabled: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateUserInput {
|
input UpdateUserInput {
|
||||||
@@ -309,6 +325,7 @@ input UpdateUserInput {
|
|||||||
phone_number: String
|
phone_number: String
|
||||||
picture: String
|
picture: String
|
||||||
roles: [String]
|
roles: [String]
|
||||||
|
is_multi_factor_auth_enabled: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input ForgotPasswordInput {
|
input ForgotPasswordInput {
|
||||||
@@ -404,19 +421,32 @@ input TestEndpointRequest {
|
|||||||
|
|
||||||
input AddEmailTemplateRequest {
|
input AddEmailTemplateRequest {
|
||||||
event_name: String!
|
event_name: String!
|
||||||
|
subject: String!
|
||||||
template: String!
|
template: String!
|
||||||
|
design: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateEmailTemplateRequest {
|
input UpdateEmailTemplateRequest {
|
||||||
id: ID!
|
id: ID!
|
||||||
event_name: String
|
event_name: String
|
||||||
template: String
|
template: String
|
||||||
|
subject: String
|
||||||
|
design: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input DeleteEmailTemplateRequest {
|
input DeleteEmailTemplateRequest {
|
||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input VerifyOTPRequest {
|
||||||
|
email: String!
|
||||||
|
otp: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ResendOTPRequest {
|
||||||
|
email: String!
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
signup(params: SignUpInput!): AuthResponse!
|
signup(params: SignUpInput!): AuthResponse!
|
||||||
login(params: LoginInput!): AuthResponse!
|
login(params: LoginInput!): AuthResponse!
|
||||||
@@ -428,6 +458,8 @@ type Mutation {
|
|||||||
forgot_password(params: ForgotPasswordInput!): Response!
|
forgot_password(params: ForgotPasswordInput!): Response!
|
||||||
reset_password(params: ResetPasswordInput!): Response!
|
reset_password(params: ResetPasswordInput!): Response!
|
||||||
revoke(params: OAuthRevokeInput!): Response!
|
revoke(params: OAuthRevokeInput!): Response!
|
||||||
|
verify_otp(params: VerifyOTPRequest!): AuthResponse!
|
||||||
|
resend_otp(params: ResendOTPRequest!): Response!
|
||||||
# admin only apis
|
# admin only apis
|
||||||
_delete_user(params: DeleteUserInput!): Response!
|
_delete_user(params: DeleteUserInput!): Response!
|
||||||
_update_user(params: UpdateUserInput!): User!
|
_update_user(params: UpdateUserInput!): User!
|
||||||
|
@@ -51,6 +51,14 @@ func (r *mutationResolver) Revoke(ctx context.Context, params model.OAuthRevokeI
|
|||||||
return resolvers.RevokeResolver(ctx, params)
|
return resolvers.RevokeResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) {
|
||||||
|
return resolvers.VerifyOtpResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) {
|
||||||
|
return resolvers.ResendOTPResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
|
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
|
||||||
return resolvers.DeleteUserResolver(ctx, params)
|
return resolvers.DeleteUserResolver(ctx, params)
|
||||||
}
|
}
|
||||||
@@ -173,7 +181,5 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
|
|||||||
// Query returns generated.QueryResolver implementation.
|
// Query returns generated.QueryResolver implementation.
|
||||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
type (
|
type mutationResolver struct{ *Resolver }
|
||||||
mutationResolver struct{ *Resolver }
|
type queryResolver struct{ *Resolver }
|
||||||
queryResolver struct{ *Resolver }
|
|
||||||
)
|
|
||||||
|
@@ -248,7 +248,7 @@ func AuthorizeHandler() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
|
||||||
cookie.SetSession(gc, newSessionToken)
|
cookie.SetSession(gc, newSessionToken)
|
||||||
code := uuid.New().String()
|
code := uuid.New().String()
|
||||||
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
|
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
|
||||||
|
@@ -67,6 +67,8 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
user, err = processLinkedInUserInfo(code)
|
user, err = processLinkedInUserInfo(code)
|
||||||
case constants.AuthRecipeMethodApple:
|
case constants.AuthRecipeMethodApple:
|
||||||
user, err = processAppleUserInfo(code)
|
user, err = processAppleUserInfo(code)
|
||||||
|
case constants.AuthRecipeMethodTwitter:
|
||||||
|
user, err = processTwitterUserInfo(code, sessionState)
|
||||||
default:
|
default:
|
||||||
log.Info("Invalid oauth provider")
|
log.Info("Invalid oauth provider")
|
||||||
err = fmt.Errorf(`invalid oauth provider`)
|
err = fmt.Errorf(`invalid oauth provider`)
|
||||||
@@ -285,9 +287,9 @@ func processGithubUserInfo(code string) (models.User, error) {
|
|||||||
log.Debug("Failed to create github user info request: ", err)
|
log.Debug("Failed to create github user info request: ", err)
|
||||||
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
|
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
|
||||||
}
|
}
|
||||||
req.Header = http.Header{
|
req.Header.Set(
|
||||||
"Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)},
|
"Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken),
|
||||||
}
|
)
|
||||||
|
|
||||||
response, err := client.Do(req)
|
response, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -329,14 +331,14 @@ func processGithubUserInfo(code string) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetch using /users/email endpoint
|
// fetch using /users/email endpoint
|
||||||
req, err := http.NewRequest("GET", constants.GithubUserEmails, nil)
|
req, err := http.NewRequest(http.MethodGet, constants.GithubUserEmails, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create github emails request: ", err)
|
log.Debug("Failed to create github emails request: ", err)
|
||||||
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
|
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
|
||||||
}
|
}
|
||||||
req.Header = http.Header{
|
req.Header.Set(
|
||||||
"Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)},
|
"Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken),
|
||||||
}
|
)
|
||||||
|
|
||||||
response, err := client.Do(req)
|
response, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -564,3 +566,70 @@ func processAppleUserInfo(code string) (models.User, error) {
|
|||||||
|
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processTwitterUserInfo(code, verifier string) (models.User, error) {
|
||||||
|
user := models.User{}
|
||||||
|
oauth2Token, err := oauth.OAuthProviders.TwitterConfig.Exchange(oauth2.NoContext, code, oauth2.SetAuthURLParam("code_verifier", verifier))
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to exchange code for token: ", err)
|
||||||
|
return user, fmt.Errorf("invalid twitter exchange code: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", constants.TwitterUserInfoURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to create Twitter user info request: ", err)
|
||||||
|
return user, fmt.Errorf("error creating Twitter user info request: %s", err.Error())
|
||||||
|
}
|
||||||
|
req.Header = http.Header{
|
||||||
|
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to request Twitter user info: ", err)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to read Twitter user info response body: ", err)
|
||||||
|
return user, fmt.Errorf("failed to read Twitter response body: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode >= 400 {
|
||||||
|
log.Debug("Failed to request Twitter user info: ", string(body))
|
||||||
|
return user, fmt.Errorf("failed to request Twitter user info: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
responseRawData := make(map[string]interface{})
|
||||||
|
json.Unmarshal(body, &responseRawData)
|
||||||
|
|
||||||
|
userRawData := responseRawData["data"].(map[string]interface{})
|
||||||
|
|
||||||
|
// log.Info(userRawData)
|
||||||
|
// Twitter API does not return E-Mail adresses by default. For that case special privileges have
|
||||||
|
// to be granted on a per-App basis. See https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials
|
||||||
|
|
||||||
|
// Currently Twitter API only provides the full name of a user. To fill givenName and familyName
|
||||||
|
// the full name will be split at the first whitespace. This approach will not be valid for all name combinations
|
||||||
|
nameArr := strings.SplitAfterN(userRawData["name"].(string), " ", 2)
|
||||||
|
|
||||||
|
firstName := nameArr[0]
|
||||||
|
lastName := ""
|
||||||
|
if len(nameArr) == 2 {
|
||||||
|
lastName = nameArr[1]
|
||||||
|
}
|
||||||
|
nickname := userRawData["username"].(string)
|
||||||
|
profilePicture := userRawData["profile_image_url"].(string)
|
||||||
|
|
||||||
|
user = models.User{
|
||||||
|
GivenName: &firstName,
|
||||||
|
FamilyName: &lastName,
|
||||||
|
Picture: &profilePicture,
|
||||||
|
Nickname: &nickname,
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/oauth"
|
"github.com/authorizerdev/authorizer/server/oauth"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthStateString := state + "___" + redirectURI + "___" + roles + "___" + strings.Join(scope, ",")
|
oauthStateString := state + "___" + redirectURI + "___" + roles + "___" + strings.Join(scope, " ")
|
||||||
|
|
||||||
provider := c.Param("oauth_provider")
|
provider := c.Param("oauth_provider")
|
||||||
isProviderConfigured := true
|
isProviderConfigured := true
|
||||||
@@ -169,6 +170,26 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodLinkedIn
|
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodLinkedIn
|
||||||
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
|
case constants.AuthRecipeMethodTwitter:
|
||||||
|
if oauth.OAuthProviders.TwitterConfig == nil {
|
||||||
|
log.Debug("Twitter OAuth provider is not configured")
|
||||||
|
isProviderConfigured = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier, challenge := utils.GenerateCodeChallenge()
|
||||||
|
|
||||||
|
err := memorystore.Provider.SetState(oauthStateString, verifier)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Error setting state: ", err)
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": "internal server error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oauth.OAuthProviders.TwitterConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodTwitter
|
||||||
|
url := oauth.OAuthProviders.TwitterConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("code_challenge", challenge), oauth2.SetAuthURLParam("code_challenge_method", "S256"))
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
case constants.AuthRecipeMethodApple:
|
case constants.AuthRecipeMethodApple:
|
||||||
if oauth.OAuthProviders.AppleConfig == nil {
|
if oauth.OAuthProviders.AppleConfig == nil {
|
||||||
log.Debug("Apple OAuth provider is not configured")
|
log.Debug("Apple OAuth provider is not configured")
|
||||||
|
@@ -76,7 +76,6 @@ func TokenHandler() gin.HandlerFunc {
|
|||||||
sessionKey := ""
|
sessionKey := ""
|
||||||
|
|
||||||
if isAuthorizationCodeGrant {
|
if isAuthorizationCodeGrant {
|
||||||
|
|
||||||
if codeVerifier == "" {
|
if codeVerifier == "" {
|
||||||
log.Debug("Code verifier is empty")
|
log.Debug("Code verifier is empty")
|
||||||
gc.JSON(http.StatusBadRequest, gin.H{
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
@@ -134,15 +133,18 @@ func TokenHandler() gin.HandlerFunc {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userID = claims.Subject
|
userID = claims.Subject
|
||||||
roles = claims.Roles
|
roles = claims.Roles
|
||||||
scope = claims.Scope
|
scope = claims.Scope
|
||||||
loginMethod = claims.LoginMethod
|
loginMethod = claims.LoginMethod
|
||||||
|
|
||||||
// rollover the session for security
|
// rollover the session for security
|
||||||
sessionKey = userID
|
sessionKey = userID
|
||||||
if loginMethod != "" {
|
if loginMethod != "" {
|
||||||
sessionKey = loginMethod + ":" + userID
|
sessionKey = loginMethod + ":" + userID
|
||||||
}
|
}
|
||||||
|
|
||||||
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
|
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
|
||||||
} else {
|
} else {
|
||||||
// validate refresh token
|
// validate refresh token
|
||||||
|
@@ -109,6 +109,7 @@ func main() {
|
|||||||
router := routes.InitRouter(log)
|
router := routes.InitRouter(log)
|
||||||
log.Info("Starting Authorizer: ", VERSION)
|
log.Info("Starting Authorizer: ", VERSION)
|
||||||
port, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyPort)
|
port, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyPort)
|
||||||
|
log.Info("Authorizer running at PORT: ", port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error while getting port from env using default port 8080: ", err)
|
log.Info("Error while getting port from env using default port 8080: ", err)
|
||||||
port = "8080"
|
port = "8080"
|
||||||
|
@@ -25,12 +25,15 @@ func InitMemStore() error {
|
|||||||
constants.EnvKeyOrganizationLogo: "https://www.authorizer.dev/images/logo.png",
|
constants.EnvKeyOrganizationLogo: "https://www.authorizer.dev/images/logo.png",
|
||||||
|
|
||||||
// boolean envs
|
// boolean envs
|
||||||
constants.EnvKeyDisableBasicAuthentication: false,
|
constants.EnvKeyDisableBasicAuthentication: false,
|
||||||
constants.EnvKeyDisableMagicLinkLogin: false,
|
constants.EnvKeyDisableMagicLinkLogin: false,
|
||||||
constants.EnvKeyDisableEmailVerification: false,
|
constants.EnvKeyDisableEmailVerification: false,
|
||||||
constants.EnvKeyDisableLoginPage: false,
|
constants.EnvKeyDisableLoginPage: false,
|
||||||
constants.EnvKeyDisableSignUp: false,
|
constants.EnvKeyDisableSignUp: false,
|
||||||
constants.EnvKeyDisableStrongPassword: false,
|
constants.EnvKeyDisableStrongPassword: false,
|
||||||
|
constants.EnvKeyIsEmailServiceEnabled: false,
|
||||||
|
constants.EnvKeyEnforceMultiFactorAuthentication: false,
|
||||||
|
constants.EnvKeyDisableMultiFactorAuthentication: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
requiredEnvs := RequiredEnvStoreObj.GetRequiredEnv()
|
requiredEnvs := RequiredEnvStoreObj.GetRequiredEnv()
|
||||||
|
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetUserSession sets the user session
|
// SetUserSession sets the user session for given user identifier in form recipe:user_id
|
||||||
func (c *provider) SetUserSession(userId, key, token string) error {
|
func (c *provider) SetUserSession(userId, key, token string) error {
|
||||||
c.sessionStore.Set(userId, key, token)
|
c.sessionStore.Set(userId, key, token)
|
||||||
return nil
|
return nil
|
||||||
@@ -34,6 +34,7 @@ func (c *provider) DeleteAllUserSessions(userId string) error {
|
|||||||
constants.AuthRecipeMethodGithub,
|
constants.AuthRecipeMethodGithub,
|
||||||
constants.AuthRecipeMethodGoogle,
|
constants.AuthRecipeMethodGoogle,
|
||||||
constants.AuthRecipeMethodLinkedIn,
|
constants.AuthRecipeMethodLinkedIn,
|
||||||
|
constants.AuthRecipeMethodTwitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
|
@@ -39,6 +39,7 @@ func (s *SessionStore) Set(key string, subKey, value string) {
|
|||||||
func (s *SessionStore) RemoveAll(key string) {
|
func (s *SessionStore) RemoveAll(key string) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
delete(s.store, key)
|
delete(s.store, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +54,9 @@ func (s *SessionStore) Remove(key, subKey string) {
|
|||||||
|
|
||||||
// Get all the values for given key
|
// Get all the values for given key
|
||||||
func (s *SessionStore) GetAll(key string) map[string]string {
|
func (s *SessionStore) GetAll(key string) map[string]string {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
if _, ok := s.store[key]; !ok {
|
if _, ok := s.store[key]; !ok {
|
||||||
s.store[key] = make(map[string]string)
|
s.store[key] = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -63,6 +67,7 @@ func (s *SessionStore) GetAll(key string) map[string]string {
|
|||||||
func (s *SessionStore) RemoveByNamespace(namespace string) error {
|
func (s *SessionStore) RemoveByNamespace(namespace string) error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
for key := range s.store {
|
for key := range s.store {
|
||||||
if strings.Contains(key, namespace+":") {
|
if strings.Contains(key, namespace+":") {
|
||||||
delete(s.store, key)
|
delete(s.store, key)
|
||||||
|
@@ -2,7 +2,7 @@ package providers
|
|||||||
|
|
||||||
// Provider defines current memory store provider
|
// Provider defines current memory store provider
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// SetUserSession sets the user session
|
// SetUserSession sets the user session for given user identifier in form recipe:user_id
|
||||||
SetUserSession(userId, key, token string) error
|
SetUserSession(userId, key, token string) error
|
||||||
// GetAllUserSessions returns all the user sessions from the session store
|
// GetAllUserSessions returns all the user sessions from the session store
|
||||||
GetAllUserSessions(userId string) (map[string]string, error)
|
GetAllUserSessions(userId string) (map[string]string, error)
|
||||||
|
@@ -14,7 +14,7 @@ var (
|
|||||||
envStorePrefix = "authorizer_env"
|
envStorePrefix = "authorizer_env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetUserSession sets the user session in redis store.
|
// SetUserSession sets the user session for given user identifier in form recipe:user_id
|
||||||
func (c *provider) SetUserSession(userId, key, token string) error {
|
func (c *provider) SetUserSession(userId, key, token string) error {
|
||||||
err := c.store.HSet(c.ctx, userId, key, token).Err()
|
err := c.store.HSet(c.ctx, userId, key, token).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,6 +71,7 @@ func (c *provider) DeleteAllUserSessions(userID string) error {
|
|||||||
constants.AuthRecipeMethodGithub,
|
constants.AuthRecipeMethodGithub,
|
||||||
constants.AuthRecipeMethodGoogle,
|
constants.AuthRecipeMethodGoogle,
|
||||||
constants.AuthRecipeMethodLinkedIn,
|
constants.AuthRecipeMethodLinkedIn,
|
||||||
|
constants.AuthRecipeMethodTwitter,
|
||||||
}
|
}
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
err := c.store.Del(c.ctx, namespace+":"+userID).Err()
|
err := c.store.Del(c.ctx, namespace+":"+userID).Err()
|
||||||
@@ -160,7 +161,7 @@ func (c *provider) GetEnvStore() (map[string]interface{}, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword {
|
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication {
|
||||||
boolValue, err := strconv.ParseBool(value)
|
boolValue, err := strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
|
@@ -20,6 +20,7 @@ type OAuthProvider struct {
|
|||||||
FacebookConfig *oauth2.Config
|
FacebookConfig *oauth2.Config
|
||||||
LinkedInConfig *oauth2.Config
|
LinkedInConfig *oauth2.Config
|
||||||
AppleConfig *oauth2.Config
|
AppleConfig *oauth2.Config
|
||||||
|
TwitterConfig *oauth2.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviders is a struct that contains reference all the OpenID providers
|
// OIDCProviders is a struct that contains reference all the OpenID providers
|
||||||
@@ -74,6 +75,7 @@ func InitOAuth() error {
|
|||||||
ClientSecret: githubClientSecret,
|
ClientSecret: githubClientSecret,
|
||||||
RedirectURL: "/oauth_callback/github",
|
RedirectURL: "/oauth_callback/github",
|
||||||
Endpoint: githubOAuth2.Endpoint,
|
Endpoint: githubOAuth2.Endpoint,
|
||||||
|
Scopes: []string{"read:user", "user:email"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,5 +135,28 @@ func InitOAuth() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
twitterClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientID)
|
||||||
|
if err != nil {
|
||||||
|
twitterClientID = ""
|
||||||
|
}
|
||||||
|
twitterClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
twitterClientSecret = ""
|
||||||
|
}
|
||||||
|
if twitterClientID != "" && twitterClientSecret != "" {
|
||||||
|
OAuthProviders.TwitterConfig = &oauth2.Config{
|
||||||
|
ClientID: twitterClientID,
|
||||||
|
ClientSecret: twitterClientSecret,
|
||||||
|
RedirectURL: "/oauth_callback/twitter",
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
// Endpoint is currently not yet part of oauth2-package. See https://go-review.googlesource.com/c/oauth2/+/350889 for status
|
||||||
|
AuthURL: "https://twitter.com/i/oauth2/authorize",
|
||||||
|
TokenURL: "https://api.twitter.com/2/oauth2/token",
|
||||||
|
AuthStyle: oauth2.AuthStyleInHeader,
|
||||||
|
},
|
||||||
|
Scopes: []string{"tweet.read", "users.read"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -14,8 +14,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO add template validator
|
|
||||||
|
|
||||||
// AddEmailTemplateResolver resolver for add email template mutation
|
// AddEmailTemplateResolver resolver for add email template mutation
|
||||||
func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) {
|
func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) {
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
@@ -34,13 +32,23 @@ func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplate
|
|||||||
return nil, fmt.Errorf("invalid event name %s", params.EventName)
|
return nil, fmt.Errorf("invalid event name %s", params.EventName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(params.Subject) == "" {
|
||||||
|
return nil, fmt.Errorf("empty subject not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(params.Template) == "" {
|
if strings.TrimSpace(params.Template) == "" {
|
||||||
return nil, fmt.Errorf("empty template not allowed")
|
return nil, fmt.Errorf("empty template not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(params.Design) == "" {
|
||||||
|
return nil, fmt.Errorf("empty design not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
_, err = db.Provider.AddEmailTemplate(ctx, models.EmailTemplate{
|
_, err = db.Provider.AddEmailTemplate(ctx, models.EmailTemplate{
|
||||||
EventName: params.EventName,
|
EventName: params.EventName,
|
||||||
Template: params.Template,
|
Template: params.Template,
|
||||||
|
Subject: params.Subject,
|
||||||
|
Design: params.Design,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to add email template: ", err)
|
log.Debug("Failed to add email template: ", err)
|
||||||
|
@@ -143,6 +143,13 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
if val, ok := store[constants.EnvKeyAppleClientSecret]; ok {
|
if val, ok := store[constants.EnvKeyAppleClientSecret]; ok {
|
||||||
res.AppleClientSecret = refs.NewStringRef(val.(string))
|
res.AppleClientSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
|
if val, ok := store[constants.EnvKeyTwitterClientID]; ok {
|
||||||
|
res.TwitterClientID = refs.NewStringRef(val.(string))
|
||||||
|
}
|
||||||
|
if val, ok := store[constants.EnvKeyTwitterClientSecret]; ok {
|
||||||
|
res.TwitterClientSecret = refs.NewStringRef(val.(string))
|
||||||
|
}
|
||||||
|
|
||||||
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
|
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
|
||||||
res.OrganizationName = refs.NewStringRef(val.(string))
|
res.OrganizationName = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
@@ -170,6 +177,8 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
res.DisableLoginPage = store[constants.EnvKeyDisableLoginPage].(bool)
|
res.DisableLoginPage = store[constants.EnvKeyDisableLoginPage].(bool)
|
||||||
res.DisableSignUp = store[constants.EnvKeyDisableSignUp].(bool)
|
res.DisableSignUp = store[constants.EnvKeyDisableSignUp].(bool)
|
||||||
res.DisableStrongPassword = store[constants.EnvKeyDisableStrongPassword].(bool)
|
res.DisableStrongPassword = store[constants.EnvKeyDisableStrongPassword].(bool)
|
||||||
|
res.EnforceMultiFactorAuthentication = store[constants.EnvKeyEnforceMultiFactorAuthentication].(bool)
|
||||||
|
res.DisableMultiFactorAuthentication = store[constants.EnvKeyDisableMultiFactorAuthentication].(bool)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -49,7 +50,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
log := log.WithFields(log.Fields{
|
log := log.WithFields(log.Fields{
|
||||||
"email": params.Email,
|
"email": params.Email,
|
||||||
})
|
})
|
||||||
_, err = db.Provider.GetUserByEmail(ctx, params.Email)
|
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("User not found: ", err)
|
log.Debug("User not found: ", err)
|
||||||
return res, fmt.Errorf(`user with this email not found`)
|
return res, fmt.Errorf(`user with this email not found`)
|
||||||
@@ -61,9 +62,9 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
log.Debug("Failed to generate nonce: ", err)
|
log.Debug("Failed to generate nonce: ", err)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
redirectURL := parsers.GetAppURL(gc) + "/reset-password"
|
redirectURL := parsers.GetAppURL(gc)
|
||||||
if params.RedirectURI != nil {
|
if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" {
|
||||||
redirectURL = *params.RedirectURI
|
redirectURL = refs.StringValue(params.RedirectURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
|
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
|
||||||
@@ -84,8 +85,12 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routine so that we can reduce the api latency
|
||||||
go email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
|
go email.SendEmail([]string{params.Email}, constants.VerificationTypeForgotPassword, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"verification_url": utils.GetForgotPasswordURL(verificationToken, hostname),
|
||||||
|
})
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: `Please check your inbox! We have sent a password reset link.`,
|
Message: `Please check your inbox! We have sent a password reset link.`,
|
||||||
|
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -35,13 +36,13 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this feature is only allowed if email server is configured
|
// this feature is only allowed if email server is configured
|
||||||
isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)
|
EnvKeyIsEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Error getting email verification disabled: ", err)
|
log.Debug("Error getting email verification disabled: ", err)
|
||||||
isEmailVerificationDisabled = true
|
EnvKeyIsEmailServiceEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if isEmailVerificationDisabled {
|
if !EnvKeyIsEmailServiceEnabled {
|
||||||
log.Debug("Email server is not configured")
|
log.Debug("Email server is not configured")
|
||||||
return nil, errors.New("email sending is disabled")
|
return nil, errors.New("email sending is disabled")
|
||||||
}
|
}
|
||||||
@@ -114,7 +115,7 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
verificationToken, err := token.CreateVerificationToken(email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
|
verificationToken, err := token.CreateVerificationToken(email, constants.VerificationTypeInviteMember, hostname, nonceHash, redirectURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create verification token: ", err)
|
log.Debug("Failed to create verification token: ", err)
|
||||||
}
|
}
|
||||||
@@ -134,8 +135,17 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
|
|||||||
} else {
|
} else {
|
||||||
// use basic authentication if that option is on
|
// use basic authentication if that option is on
|
||||||
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
|
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
|
||||||
verificationRequest.Identifier = constants.VerificationTypeForgotPassword
|
verificationRequest.Identifier = constants.VerificationTypeInviteMember
|
||||||
|
|
||||||
|
isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("MFA service not enabled: ", err)
|
||||||
|
isMFAEnforced = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMFAEnforced {
|
||||||
|
user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true)
|
||||||
|
}
|
||||||
verifyEmailURL = appURL + "/setup-password"
|
verifyEmailURL = appURL + "/setup-password"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -152,7 +162,12 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go emailservice.InviteEmail(email, verificationToken, verifyEmailURL, redirectURL)
|
// exec it as go routine so that we can reduce the api latency
|
||||||
|
go emailservice.SendEmail([]string{user.Email}, constants.VerificationTypeInviteMember, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"verification_url": utils.GetInviteVerificationURL(verifyEmailURL, verificationToken, redirectURL),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &model.Response{
|
return &model.Response{
|
||||||
|
@@ -13,8 +13,10 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -48,7 +50,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
|
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to get user by email: ", err)
|
log.Debug("Failed to get user by email: ", err)
|
||||||
return res, fmt.Errorf(`user with this email not found`)
|
return res, fmt.Errorf(`bad user credentials`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.RevokedTimestamp != nil {
|
if user.RevokedTimestamp != nil {
|
||||||
@@ -70,7 +72,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to compare password: ", err)
|
log.Debug("Failed to compare password: ", err)
|
||||||
return res, fmt.Errorf(`invalid password`)
|
return res, fmt.Errorf(`bad user credentials`)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultRolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
defaultRolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||||
@@ -97,6 +99,47 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
scope = params.Scope
|
scope = params.Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
|
||||||
|
if err != nil || !isEmailServiceEnabled {
|
||||||
|
log.Debug("Email service not enabled: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
|
||||||
|
if err != nil || !isEmailServiceEnabled {
|
||||||
|
log.Debug("MFA service not enabled: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If email service is not enabled continue the process in any way
|
||||||
|
if refs.BoolValue(user.IsMultiFactorAuthEnabled) && isEmailServiceEnabled && !isMFADisabled {
|
||||||
|
otp := utils.GenerateOTP()
|
||||||
|
otpData, err := db.Provider.UpsertOTP(ctx, &models.OTP{
|
||||||
|
Email: user.Email,
|
||||||
|
Otp: otp,
|
||||||
|
ExpiresAt: time.Now().Add(1 * time.Minute).Unix(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to add otp: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// exec it as go routine so that we can reduce the api latency
|
||||||
|
go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"otp": otpData.Otp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to send otp email: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &model.AuthResponse{
|
||||||
|
Message: "Please check the OTP in your inbox",
|
||||||
|
ShouldShowOtpScreen: refs.NewBoolRef(true),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create auth token", err)
|
log.Debug("Failed to create auth token", err)
|
||||||
|
@@ -219,8 +219,12 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec it as go routing so that we can reduce the api latency
|
// exec it as go routine so that we can reduce the api latency
|
||||||
go email.SendVerificationMail(params.Email, verificationToken, hostname)
|
go email.SendEmail([]string{params.Email}, constants.VerificationTypeMagicLinkLogin, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
|
@@ -77,6 +77,18 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
|
|||||||
githubClientSecret = ""
|
githubClientSecret = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
twitterClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientID)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get Twitter Client ID from environment variable", err)
|
||||||
|
twitterClientID = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
twitterClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get Twitter Client Secret from environment variable", err)
|
||||||
|
twitterClientSecret = ""
|
||||||
|
}
|
||||||
|
|
||||||
isBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication)
|
isBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to get Disable Basic Authentication from environment variable", err)
|
log.Debug("Failed to get Disable Basic Authentication from environment variable", err)
|
||||||
@@ -107,6 +119,12 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
|
|||||||
isSignUpDisabled = true
|
isSignUpDisabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMultiFactorAuthenticationEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get Disable Multi Factor Authentication from environment variable", err)
|
||||||
|
isSignUpDisabled = true
|
||||||
|
}
|
||||||
|
|
||||||
metaInfo := model.Meta{
|
metaInfo := model.Meta{
|
||||||
Version: constants.VERSION,
|
Version: constants.VERSION,
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
@@ -115,11 +133,13 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
|
|||||||
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
|
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
|
||||||
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
|
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
|
||||||
IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "",
|
IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "",
|
||||||
|
IsTwitterLoginEnabled: twitterClientID != "" && twitterClientSecret != "",
|
||||||
IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
|
IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
|
||||||
IsEmailVerificationEnabled: !isEmailVerificationDisabled,
|
IsEmailVerificationEnabled: !isEmailVerificationDisabled,
|
||||||
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,
|
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,
|
||||||
IsSignUpEnabled: !isSignUpDisabled,
|
IsSignUpEnabled: !isSignUpDisabled,
|
||||||
IsStrongPasswordEnabled: !isStrongPasswordDisabled,
|
IsStrongPasswordEnabled: !isStrongPasswordDisabled,
|
||||||
|
IsMultiFactorAuthEnabled: !isMultiFactorAuthenticationEnabled,
|
||||||
}
|
}
|
||||||
return &metaInfo, nil
|
return &metaInfo, nil
|
||||||
}
|
}
|
||||||
|
101
server/resolvers/resend_otp.go
Normal file
101
server/resolvers/resend_otp.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResendOTPResolver is a resolver for resend otp mutation
|
||||||
|
func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) {
|
||||||
|
log := log.WithFields(log.Fields{
|
||||||
|
"email": params.Email,
|
||||||
|
})
|
||||||
|
params.Email = strings.ToLower(params.Email)
|
||||||
|
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get user by email: ", err)
|
||||||
|
return nil, fmt.Errorf(`user with this email not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.RevokedTimestamp != nil {
|
||||||
|
log.Debug("User access is revoked")
|
||||||
|
return nil, fmt.Errorf(`user access has been revoked`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.EmailVerifiedAt == nil {
|
||||||
|
log.Debug("User email is not verified")
|
||||||
|
return nil, fmt.Errorf(`email not verified`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !refs.BoolValue(user.IsMultiFactorAuthEnabled) {
|
||||||
|
log.Debug("User multi factor authentication is not enabled")
|
||||||
|
return nil, fmt.Errorf(`multi factor authentication not enabled`)
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
|
||||||
|
if err != nil || !isEmailServiceEnabled {
|
||||||
|
log.Debug("Email service not enabled: ", err)
|
||||||
|
return nil, errors.New("email service not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
|
||||||
|
if err != nil || isMFADisabled {
|
||||||
|
log.Debug("MFA service not enabled: ", err)
|
||||||
|
return nil, errors.New("multi factor authentication is disabled for this instance")
|
||||||
|
}
|
||||||
|
|
||||||
|
// get otp by email
|
||||||
|
otpData, err := db.Provider.GetOTPByEmail(ctx, params.Email)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get otp for given email: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if otpData == nil {
|
||||||
|
log.Debug("No otp found for given email: ", params.Email)
|
||||||
|
return &model.Response{
|
||||||
|
Message: "Failed to get for given email",
|
||||||
|
}, errors.New("failed to get otp for given email")
|
||||||
|
}
|
||||||
|
|
||||||
|
otp := utils.GenerateOTP()
|
||||||
|
otpData, err = db.Provider.UpsertOTP(ctx, &models.OTP{
|
||||||
|
Email: user.Email,
|
||||||
|
Otp: otp,
|
||||||
|
ExpiresAt: time.Now().Add(1 * time.Minute).Unix(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Error generating new otp: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// exec it as go routine so that we can reduce the api latency
|
||||||
|
go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"otp": otp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Error sending otp email: ", otp)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &model.Response{
|
||||||
|
Message: `OTP has been sent. Please check your inbox`,
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -39,6 +39,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
|
|||||||
return res, fmt.Errorf("invalid identifier")
|
return res, fmt.Errorf("invalid identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("invalid user")
|
||||||
|
}
|
||||||
|
|
||||||
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, params.Email, params.Identifier)
|
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, params.Email, params.Identifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to get verification request: ", err)
|
log.Debug("Failed to get verification request: ", err)
|
||||||
@@ -74,8 +79,12 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
|
|||||||
log.Debug("Failed to add verification request: ", err)
|
log.Debug("Failed to add verification request: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routine so that we can reduce the api latency
|
||||||
go email.SendVerificationMail(params.Email, verificationToken, hostname)
|
go email.SendEmail([]string{params.Email}, params.Identifier, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
|
||||||
|
})
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: `Verification email has been sent. Please check your inbox`,
|
Message: `Verification email has been sent. Please check your inbox`,
|
||||||
|
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -84,6 +85,16 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
|
|||||||
signupMethod := user.SignupMethods
|
signupMethod := user.SignupMethods
|
||||||
if !strings.Contains(signupMethod, constants.AuthRecipeMethodBasicAuth) {
|
if !strings.Contains(signupMethod, constants.AuthRecipeMethodBasicAuth) {
|
||||||
signupMethod = signupMethod + "," + constants.AuthRecipeMethodBasicAuth
|
signupMethod = signupMethod + "," + constants.AuthRecipeMethodBasicAuth
|
||||||
|
|
||||||
|
isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("MFA service not enabled: ", err)
|
||||||
|
isMFAEnforced = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMFAEnforced {
|
||||||
|
user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
user.SignupMethods = signupMethod
|
user.SignupMethods = signupMethod
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -157,6 +158,20 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
user.Picture = params.Picture
|
user.Picture = params.Picture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.IsMultiFactorAuthEnabled != nil {
|
||||||
|
user.IsMultiFactorAuthEnabled = params.IsMultiFactorAuthEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("MFA service not enabled: ", err)
|
||||||
|
isMFAEnforced = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMFAEnforced {
|
||||||
|
user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true)
|
||||||
|
}
|
||||||
|
|
||||||
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
|
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
|
||||||
isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)
|
isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -206,9 +221,14 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routine so that we can reduce the api latency
|
||||||
go func() {
|
go func() {
|
||||||
email.SendVerificationMail(params.Email, verificationToken, hostname)
|
// exec it as go routine so that we can reduce the api latency
|
||||||
|
email.SendEmail([]string{params.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
|
||||||
|
})
|
||||||
utils.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodBasicAuth, user)
|
utils.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodBasicAuth, user)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@@ -96,15 +96,9 @@ func TestEndpointResolver(ctx context.Context, params model.TestEndpointRequest)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response := map[string]interface{}{}
|
|
||||||
if err := json.Unmarshal(body, &response); err != nil {
|
|
||||||
log.Debug("error un-marshalling response: ", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
statusCode := int64(resp.StatusCode)
|
statusCode := int64(resp.StatusCode)
|
||||||
return &model.TestEndpointResponse{
|
return &model.TestEndpointResponse{
|
||||||
HTTPStatus: &statusCode,
|
HTTPStatus: &statusCode,
|
||||||
Response: response,
|
Response: refs.NewStringRef(string(body)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -15,8 +15,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO add template validator
|
|
||||||
|
|
||||||
// UpdateEmailTemplateResolver resolver for update email template mutation
|
// UpdateEmailTemplateResolver resolver for update email template mutation
|
||||||
func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) {
|
func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) {
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
@@ -51,6 +49,14 @@ func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTe
|
|||||||
emailTemplateDetails.EventName = refs.StringValue(params.EventName)
|
emailTemplateDetails.EventName = refs.StringValue(params.EventName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.Subject != nil && emailTemplateDetails.Subject != refs.StringValue(params.Subject) {
|
||||||
|
if strings.TrimSpace(refs.StringValue(params.Subject)) == "" {
|
||||||
|
log.Debug("empty subject not allowed")
|
||||||
|
return nil, fmt.Errorf("empty subject not allowed")
|
||||||
|
}
|
||||||
|
emailTemplateDetails.Subject = refs.StringValue(params.Subject)
|
||||||
|
}
|
||||||
|
|
||||||
if params.Template != nil && emailTemplateDetails.Template != refs.StringValue(params.Template) {
|
if params.Template != nil && emailTemplateDetails.Template != refs.StringValue(params.Template) {
|
||||||
if strings.TrimSpace(refs.StringValue(params.Template)) == "" {
|
if strings.TrimSpace(refs.StringValue(params.Template)) == "" {
|
||||||
log.Debug("empty template not allowed")
|
log.Debug("empty template not allowed")
|
||||||
@@ -59,6 +65,14 @@ func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTe
|
|||||||
emailTemplateDetails.Template = refs.StringValue(params.Template)
|
emailTemplateDetails.Template = refs.StringValue(params.Template)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.Design != nil && emailTemplateDetails.Design != refs.StringValue(params.Design) {
|
||||||
|
if strings.TrimSpace(refs.StringValue(params.Design)) == "" {
|
||||||
|
log.Debug("empty design not allowed")
|
||||||
|
return nil, fmt.Errorf("empty design not allowed")
|
||||||
|
}
|
||||||
|
emailTemplateDetails.Design = refs.StringValue(params.Design)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = db.Provider.UpdateEmailTemplate(ctx, emailTemplateDetails)
|
_, err = db.Provider.UpdateEmailTemplate(ctx, emailTemplateDetails)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -31,6 +31,7 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
|
|||||||
isCurrentGoogleLoginEnabled := currentData[constants.EnvKeyGoogleClientID] != nil && currentData[constants.EnvKeyGoogleClientSecret] != nil && currentData[constants.EnvKeyGoogleClientID].(string) != "" && currentData[constants.EnvKeyGoogleClientSecret].(string) != ""
|
isCurrentGoogleLoginEnabled := currentData[constants.EnvKeyGoogleClientID] != nil && currentData[constants.EnvKeyGoogleClientSecret] != nil && currentData[constants.EnvKeyGoogleClientID].(string) != "" && currentData[constants.EnvKeyGoogleClientSecret].(string) != ""
|
||||||
isCurrentGithubLoginEnabled := currentData[constants.EnvKeyGithubClientID] != nil && currentData[constants.EnvKeyGithubClientSecret] != nil && currentData[constants.EnvKeyGithubClientID].(string) != "" && currentData[constants.EnvKeyGithubClientSecret].(string) != ""
|
isCurrentGithubLoginEnabled := currentData[constants.EnvKeyGithubClientID] != nil && currentData[constants.EnvKeyGithubClientSecret] != nil && currentData[constants.EnvKeyGithubClientID].(string) != "" && currentData[constants.EnvKeyGithubClientSecret].(string) != ""
|
||||||
isCurrentLinkedInLoginEnabled := currentData[constants.EnvKeyLinkedInClientID] != nil && currentData[constants.EnvKeyLinkedInClientSecret] != nil && currentData[constants.EnvKeyLinkedInClientID].(string) != "" && currentData[constants.EnvKeyLinkedInClientSecret].(string) != ""
|
isCurrentLinkedInLoginEnabled := currentData[constants.EnvKeyLinkedInClientID] != nil && currentData[constants.EnvKeyLinkedInClientSecret] != nil && currentData[constants.EnvKeyLinkedInClientID].(string) != "" && currentData[constants.EnvKeyLinkedInClientSecret].(string) != ""
|
||||||
|
isCurrentTwitterLoginEnabled := currentData[constants.EnvKeyTwitterClientID] != nil && currentData[constants.EnvKeyTwitterClientSecret] != nil && currentData[constants.EnvKeyTwitterClientID].(string) != "" && currentData[constants.EnvKeyTwitterClientSecret].(string) != ""
|
||||||
|
|
||||||
isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool)
|
isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool)
|
||||||
isUpdatedMagicLinkLoginEnabled := !updatedData[constants.EnvKeyDisableMagicLinkLogin].(bool)
|
isUpdatedMagicLinkLoginEnabled := !updatedData[constants.EnvKeyDisableMagicLinkLogin].(bool)
|
||||||
@@ -39,6 +40,7 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
|
|||||||
isUpdatedGoogleLoginEnabled := updatedData[constants.EnvKeyGoogleClientID] != nil && updatedData[constants.EnvKeyGoogleClientSecret] != nil && updatedData[constants.EnvKeyGoogleClientID].(string) != "" && updatedData[constants.EnvKeyGoogleClientSecret].(string) != ""
|
isUpdatedGoogleLoginEnabled := updatedData[constants.EnvKeyGoogleClientID] != nil && updatedData[constants.EnvKeyGoogleClientSecret] != nil && updatedData[constants.EnvKeyGoogleClientID].(string) != "" && updatedData[constants.EnvKeyGoogleClientSecret].(string) != ""
|
||||||
isUpdatedGithubLoginEnabled := updatedData[constants.EnvKeyGithubClientID] != nil && updatedData[constants.EnvKeyGithubClientSecret] != nil && updatedData[constants.EnvKeyGithubClientID].(string) != "" && updatedData[constants.EnvKeyGithubClientSecret].(string) != ""
|
isUpdatedGithubLoginEnabled := updatedData[constants.EnvKeyGithubClientID] != nil && updatedData[constants.EnvKeyGithubClientSecret] != nil && updatedData[constants.EnvKeyGithubClientID].(string) != "" && updatedData[constants.EnvKeyGithubClientSecret].(string) != ""
|
||||||
isUpdatedLinkedInLoginEnabled := updatedData[constants.EnvKeyLinkedInClientID] != nil && updatedData[constants.EnvKeyLinkedInClientSecret] != nil && updatedData[constants.EnvKeyLinkedInClientID].(string) != "" && updatedData[constants.EnvKeyLinkedInClientSecret].(string) != ""
|
isUpdatedLinkedInLoginEnabled := updatedData[constants.EnvKeyLinkedInClientID] != nil && updatedData[constants.EnvKeyLinkedInClientSecret] != nil && updatedData[constants.EnvKeyLinkedInClientID].(string) != "" && updatedData[constants.EnvKeyLinkedInClientSecret].(string) != ""
|
||||||
|
isUpdatedTwitterLoginEnabled := updatedData[constants.EnvKeyTwitterClientID] != nil && updatedData[constants.EnvKeyTwitterClientSecret] != nil && updatedData[constants.EnvKeyTwitterClientID].(string) != "" && updatedData[constants.EnvKeyTwitterClientSecret].(string) != ""
|
||||||
|
|
||||||
if isCurrentBasicAuthEnabled && !isUpdatedBasicAuthEnabled {
|
if isCurrentBasicAuthEnabled && !isUpdatedBasicAuthEnabled {
|
||||||
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth)
|
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth)
|
||||||
@@ -67,6 +69,10 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
|
|||||||
if isCurrentLinkedInLoginEnabled && !isUpdatedLinkedInLoginEnabled {
|
if isCurrentLinkedInLoginEnabled && !isUpdatedLinkedInLoginEnabled {
|
||||||
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodLinkedIn)
|
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodLinkedIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isCurrentTwitterLoginEnabled && !isUpdatedTwitterLoginEnabled {
|
||||||
|
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodTwitter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEnvResolver is a resolver for update config mutation
|
// UpdateEnvResolver is a resolver for update config mutation
|
||||||
@@ -234,6 +240,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
// handle derivative cases like disabling email verification & magic login
|
// handle derivative cases like disabling email verification & magic login
|
||||||
// in case SMTP is off but env is set to true
|
// in case SMTP is off but env is set to true
|
||||||
if updatedData[constants.EnvKeySmtpHost] == "" || updatedData[constants.EnvKeySmtpUsername] == "" || updatedData[constants.EnvKeySmtpPassword] == "" || updatedData[constants.EnvKeySenderEmail] == "" && updatedData[constants.EnvKeySmtpPort] == "" {
|
if updatedData[constants.EnvKeySmtpHost] == "" || updatedData[constants.EnvKeySmtpUsername] == "" || updatedData[constants.EnvKeySmtpPassword] == "" || updatedData[constants.EnvKeySenderEmail] == "" && updatedData[constants.EnvKeySmtpPort] == "" {
|
||||||
|
updatedData[constants.EnvKeyIsEmailServiceEnabled] = false
|
||||||
|
updatedData[constants.EnvKeyDisableMultiFactorAuthentication] = true
|
||||||
if !updatedData[constants.EnvKeyDisableEmailVerification].(bool) {
|
if !updatedData[constants.EnvKeyDisableEmailVerification].(bool) {
|
||||||
updatedData[constants.EnvKeyDisableEmailVerification] = true
|
updatedData[constants.EnvKeyDisableEmailVerification] = true
|
||||||
}
|
}
|
||||||
@@ -243,6 +251,16 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if updatedData[constants.EnvKeySmtpHost] != "" || updatedData[constants.EnvKeySmtpUsername] != "" || updatedData[constants.EnvKeySmtpPassword] != "" || updatedData[constants.EnvKeySenderEmail] != "" && updatedData[constants.EnvKeySmtpPort] != "" {
|
||||||
|
updatedData[constants.EnvKeyIsEmailServiceEnabled] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !currentData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && updatedData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !updatedData[constants.EnvKeyDisableMultiFactorAuthentication].(bool) {
|
||||||
|
go db.Provider.UpdateUsers(ctx, map[string]interface{}{
|
||||||
|
"is_multi_factor_auth_enabled": true,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// check the roles change
|
// check the roles change
|
||||||
if len(params.Roles) > 0 {
|
if len(params.Roles) > 0 {
|
||||||
if len(params.DefaultRoles) > 0 {
|
if len(params.DefaultRoles) > 0 {
|
||||||
@@ -265,8 +283,6 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go clearSessionIfRequired(currentData, updatedData)
|
|
||||||
|
|
||||||
// Update local store
|
// Update local store
|
||||||
memorystore.Provider.UpdateEnvStore(updatedData)
|
memorystore.Provider.UpdateEnvStore(updatedData)
|
||||||
jwk, err := crypto.GenerateJWKBasedOnEnv()
|
jwk, err := crypto.GenerateJWKBasedOnEnv()
|
||||||
@@ -320,6 +336,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go clearSessionIfRequired(currentData, updatedData)
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: "configurations updated successfully",
|
Message: "configurations updated successfully",
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package resolvers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -46,7 +47,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validate if all params are not empty
|
// validate if all params are not empty
|
||||||
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil && params.NewPassword == nil && params.ConfirmNewPassword == nil {
|
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil && params.NewPassword == nil && params.ConfirmNewPassword == nil && params.IsMultiFactorAuthEnabled == nil {
|
||||||
log.Debug("All params are empty")
|
log.Debug("All params are empty")
|
||||||
return res, fmt.Errorf("please enter at least one param to update")
|
return res, fmt.Errorf("please enter at least one param to update")
|
||||||
}
|
}
|
||||||
@@ -94,6 +95,29 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
user.Picture = params.Picture
|
user.Picture = params.Picture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.IsMultiFactorAuthEnabled != nil && refs.BoolValue(user.IsMultiFactorAuthEnabled) != refs.BoolValue(params.IsMultiFactorAuthEnabled) {
|
||||||
|
if refs.BoolValue(params.IsMultiFactorAuthEnabled) {
|
||||||
|
isEnvServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
|
||||||
|
if err != nil || !isEnvServiceEnabled {
|
||||||
|
log.Debug("Email service not enabled:")
|
||||||
|
return nil, errors.New("email service not enabled, so cannot enable multi factor authentication")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("MFA service not enabled: ", err)
|
||||||
|
isMFAEnforced = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMFAEnforced && !refs.BoolValue(params.IsMultiFactorAuthEnabled) {
|
||||||
|
log.Debug("Cannot disable mfa service as it is enforced:")
|
||||||
|
return nil, errors.New("cannot disable multi factor authentication as it is enforced by organization")
|
||||||
|
}
|
||||||
|
|
||||||
|
user.IsMultiFactorAuthEnabled = params.IsMultiFactorAuthEnabled
|
||||||
|
}
|
||||||
|
|
||||||
isPasswordChanging := false
|
isPasswordChanging := false
|
||||||
if params.NewPassword != nil && params.ConfirmNewPassword == nil {
|
if params.NewPassword != nil && params.ConfirmNewPassword == nil {
|
||||||
isPasswordChanging = true
|
isPasswordChanging = true
|
||||||
@@ -220,8 +244,12 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routine so that we can reduce the api latency
|
||||||
go email.SendVerificationMail(newEmail, verificationToken, hostname)
|
go email.SendEmail([]string{user.Email}, verificationType, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package resolvers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -45,7 +47,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
"user_id": params.ID,
|
"user_id": params.ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil && params.Roles == nil {
|
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil && params.Roles == nil && params.IsMultiFactorAuthEnabled == nil {
|
||||||
log.Debug("No params to update")
|
log.Debug("No params to update")
|
||||||
return res, fmt.Errorf("please enter atleast one param to update")
|
return res, fmt.Errorf("please enter atleast one param to update")
|
||||||
}
|
}
|
||||||
@@ -56,38 +58,49 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
return res, fmt.Errorf(`User not found`)
|
return res, fmt.Errorf(`User not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.GivenName != nil && user.GivenName != params.GivenName {
|
if params.GivenName != nil && refs.StringValue(user.GivenName) != refs.StringValue(params.GivenName) {
|
||||||
user.GivenName = params.GivenName
|
user.GivenName = params.GivenName
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.FamilyName != nil && user.FamilyName != params.FamilyName {
|
if params.FamilyName != nil && refs.StringValue(user.FamilyName) != refs.StringValue(params.FamilyName) {
|
||||||
user.FamilyName = params.FamilyName
|
user.FamilyName = params.FamilyName
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.MiddleName != nil && user.MiddleName != params.MiddleName {
|
if params.MiddleName != nil && refs.StringValue(user.MiddleName) != refs.StringValue(params.MiddleName) {
|
||||||
user.MiddleName = params.MiddleName
|
user.MiddleName = params.MiddleName
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Nickname != nil && user.Nickname != params.Nickname {
|
if params.Nickname != nil && refs.StringValue(user.Nickname) != refs.StringValue(params.Nickname) {
|
||||||
user.Nickname = params.Nickname
|
user.Nickname = params.Nickname
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Birthdate != nil && user.Birthdate != params.Birthdate {
|
if params.Birthdate != nil && refs.StringValue(user.Birthdate) != refs.StringValue(params.Birthdate) {
|
||||||
user.Birthdate = params.Birthdate
|
user.Birthdate = params.Birthdate
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Gender != nil && user.Gender != params.Gender {
|
if params.Gender != nil && refs.StringValue(user.Gender) != refs.StringValue(params.Gender) {
|
||||||
user.Gender = params.Gender
|
user.Gender = params.Gender
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.PhoneNumber != nil && user.PhoneNumber != params.PhoneNumber {
|
if params.PhoneNumber != nil && refs.StringValue(user.PhoneNumber) != refs.StringValue(params.PhoneNumber) {
|
||||||
user.PhoneNumber = params.PhoneNumber
|
user.PhoneNumber = params.PhoneNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Picture != nil && user.Picture != params.Picture {
|
if params.Picture != nil && refs.StringValue(user.Picture) != refs.StringValue(params.Picture) {
|
||||||
user.Picture = params.Picture
|
user.Picture = params.Picture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.IsMultiFactorAuthEnabled != nil && refs.BoolValue(user.IsMultiFactorAuthEnabled) != refs.BoolValue(params.IsMultiFactorAuthEnabled) {
|
||||||
|
user.IsMultiFactorAuthEnabled = params.IsMultiFactorAuthEnabled
|
||||||
|
if refs.BoolValue(params.IsMultiFactorAuthEnabled) {
|
||||||
|
isEnvServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
|
||||||
|
if err != nil || !isEnvServiceEnabled {
|
||||||
|
log.Debug("Email service not enabled:")
|
||||||
|
return nil, errors.New("email service not enabled, so cannot enable multi factor authentication")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if params.EmailVerified != nil {
|
if params.EmailVerified != nil {
|
||||||
if *params.EmailVerified {
|
if *params.EmailVerified {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
@@ -143,8 +156,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routine so that we can reduce the api latency
|
||||||
go email.SendVerificationMail(newEmail, verificationToken, hostname)
|
go email.SendEmail([]string{user.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
|
||||||
|
"user": user.ToMap(),
|
||||||
|
"organization": utils.GetOrganization(),
|
||||||
|
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
104
server/resolvers/verify_otp.go
Normal file
104
server/resolvers/verify_otp.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"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/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VerifyOtpResolver resolver for verify otp mutation
|
||||||
|
func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) {
|
||||||
|
var res *model.AuthResponse
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get GinContext: ", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
otp, err := db.Provider.GetOTPByEmail(ctx, params.Email)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get otp request by email: ", err)
|
||||||
|
return res, fmt.Errorf(`invalid email: %s`, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Otp != otp.Otp {
|
||||||
|
log.Debug("Failed to verify otp request: Incorrect value")
|
||||||
|
return res, fmt.Errorf(`invalid otp`)
|
||||||
|
}
|
||||||
|
|
||||||
|
expiresIn := otp.ExpiresAt - time.Now().Unix()
|
||||||
|
|
||||||
|
if expiresIn < 0 {
|
||||||
|
log.Debug("Failed to verify otp request: Timeout")
|
||||||
|
return res, fmt.Errorf("otp expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get user by email: ", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isSignUp := user.EmailVerifiedAt == nil
|
||||||
|
|
||||||
|
// TODO - Add Login method in DB when we introduce OTP for social media login
|
||||||
|
loginMethod := constants.AuthRecipeMethodBasicAuth
|
||||||
|
|
||||||
|
roles := strings.Split(user.Roles, ",")
|
||||||
|
scope := []string{"openid", "email", "profile"}
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to create auth token: ", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
db.Provider.DeleteOTP(gc, otp)
|
||||||
|
if isSignUp {
|
||||||
|
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, loginMethod, user)
|
||||||
|
} else {
|
||||||
|
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, loginMethod, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Provider.AddSession(ctx, models.Session{
|
||||||
|
UserID: user.ID,
|
||||||
|
UserAgent: utils.GetUserAgent(gc.Request),
|
||||||
|
IP: utils.GetIP(gc.Request),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
authTokenExpiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if authTokenExpiresIn <= 0 {
|
||||||
|
authTokenExpiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
res = &model.AuthResponse{
|
||||||
|
Message: `OTP verified successfully.`,
|
||||||
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
|
IDToken: &authToken.IDToken.Token,
|
||||||
|
ExpiresIn: &authTokenExpiresIn,
|
||||||
|
User: user.AsAPIUser(),
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKey := loginMethod + ":" + user.ID
|
||||||
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
|
||||||
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
res.RefreshToken = &authToken.RefreshToken.Token
|
||||||
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user