Compare commits
226 Commits
fix/sessio
...
1.1.21.bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
eaa10ec5bc | ||
![]() |
253128ca0c | ||
![]() |
cddfe1e088 | ||
![]() |
8e655bcb5b | ||
![]() |
9a411e673c | ||
![]() |
346c8e5a47 | ||
![]() |
3cd99fe5f6 | ||
![]() |
2bd92d6028 | ||
![]() |
ff805e3ef2 | ||
![]() |
0115128ee7 | ||
![]() |
d8eceadd7f | ||
![]() |
e6c4fdff26 | ||
![]() |
e760a5598e | ||
![]() |
f62a22619b | ||
![]() |
c32a7fa1e4 | ||
![]() |
399b97079d | ||
![]() |
fe687cb0ca | ||
![]() |
9cb011e921 | ||
![]() |
4e1bba2ba8 | ||
![]() |
f1509f90f0 | ||
![]() |
bd4d48c7c5 | ||
![]() |
0e3242372b | ||
![]() |
89cea39c41 | ||
![]() |
570a0b9531 | ||
![]() |
686b3a4666 | ||
![]() |
b266a14108 | ||
![]() |
e5972a0dee | ||
![]() |
6f46f1e6ef | ||
![]() |
cfbce17ab8 | ||
![]() |
aa6601e62c | ||
![]() |
d8ea0c656f | ||
![]() |
f5323e0eec | ||
![]() |
b1bc7b5370 | ||
![]() |
536fd87c3c | ||
![]() |
f8c96a9fee | ||
![]() |
837fc781de | ||
![]() |
640bb8c9ed | ||
![]() |
d9bba0bbe7 | ||
![]() |
f91ec1880f | ||
![]() |
19e2153379 | ||
![]() |
221009bf0a | ||
![]() |
6085c2d535 | ||
![]() |
8e0c5e4380 | ||
![]() |
21b70e4b26 | ||
![]() |
993693884d | ||
![]() |
ed849fa6f6 | ||
![]() |
aec1f5df53 | ||
![]() |
195bd1bc6a | ||
![]() |
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 | ||
![]() |
a102924fd7 | ||
![]() |
390846c85f | ||
![]() |
a48b809a89 | ||
![]() |
cd46da60a0 | ||
![]() |
50f52a99b4 | ||
![]() |
150b1e5712 | ||
![]() |
1f7eee43e2 | ||
![]() |
7c441fff14 | ||
![]() |
647cc1d9bf | ||
![]() |
97b1d8d66f | ||
![]() |
2cce1c4e93 | ||
![]() |
8b1511a07b | ||
![]() |
a69dd95992 | ||
![]() |
d3260f4f32 | ||
![]() |
301bde4da2 | ||
![]() |
913c5c94fb | ||
![]() |
610896b6f5 | ||
![]() |
33f79872be | ||
![]() |
8fc52d76dc | ||
![]() |
aa12757155 | ||
![]() |
847c364ad1 | ||
![]() |
eabc943452 | ||
![]() |
41a0f15e16 | ||
![]() |
e2294c24d0 | ||
![]() |
a3c0a0422c | ||
![]() |
d837b1590a | ||
![]() |
283e570ebb | ||
![]() |
14c74f6566 | ||
![]() |
8e655daa71 | ||
![]() |
fed092bb65 | ||
![]() |
6d28290605 | ||
![]() |
2de0ea57d0 | ||
![]() |
f2886e6da8 | ||
![]() |
6b57bce6d9 | ||
![]() |
bfbeb6add2 | ||
![]() |
1fe0d65874 | ||
![]() |
bfaa0f9d89 | ||
![]() |
4f5a6c77f8 | ||
![]() |
018a13ab3c | ||
![]() |
334041d0e4 | ||
![]() |
6a8309a231 | ||
![]() |
6347b60753 | ||
![]() |
bbb064b939 | ||
![]() |
e91a819067 | ||
![]() |
09c3eafe6b | ||
![]() |
bb51775d34 | ||
![]() |
6d586b16e4 | ||
![]() |
e8eb62769e | ||
![]() |
0ffb3f67f1 | ||
![]() |
ec62686fbc | ||
![]() |
a8064e79a1 | ||
![]() |
265331801f | ||
![]() |
6a74a50493 | ||
![]() |
8c27f20534 | ||
![]() |
29c6003ea3 | ||
![]() |
ae34fc7c2b | ||
![]() |
2a5d5d43b0 | ||
![]() |
e6a4670ba9 | ||
![]() |
64d64b4099 | ||
![]() |
88f9a10f21 | ||
![]() |
4e08d4f8fd | ||
![]() |
1c4dda9299 | ||
![]() |
ab18fa5832 | ||
![]() |
484d0c0882 | ||
![]() |
be59c3615f | ||
![]() |
db351f7771 | ||
![]() |
91c29c4092 | ||
![]() |
415b97535e | ||
![]() |
7d1272d815 | ||
![]() |
c9ba0b13f8 | ||
![]() |
fadd9f6168 | ||
![]() |
395e2e2a85 | ||
![]() |
6335084835 | ||
![]() |
eab336cd3d | ||
![]() |
f4691fca1f | ||
![]() |
341d4fbae5 | ||
![]() |
e467b45ab1 | ||
![]() |
7edfad3486 | ||
![]() |
80578b88ac | ||
![]() |
5646e7a0e7 | ||
![]() |
53a592ef63 | ||
![]() |
3337dbd0a4 |
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
|
||||||
|
16
Dockerfile
16
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.17-alpine as go-builder
|
FROM golang:1.19.1-alpine as go-builder
|
||||||
WORKDIR /authorizer
|
WORKDIR /authorizer
|
||||||
COPY server server
|
COPY server server
|
||||||
COPY Makefile .
|
COPY Makefile .
|
||||||
@@ -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" ]
|
||||||
|
26
Makefile
26
Makefile
@@ -10,7 +10,27 @@ build-dashboard:
|
|||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf build
|
||||||
test:
|
test:
|
||||||
rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && 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:
|
||||||
|
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_mongodb_db -p 27017:27017 mongo:4.4.15
|
||||||
|
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
|
||||||
|
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test
|
||||||
|
docker rm -vf authorizer_scylla_db
|
||||||
|
docker rm -vf authorizer_mongodb_db
|
||||||
|
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 run github.com/99designs/gqlgen generate && go mod tidy
|
||||||
|
|
||||||
|
30
README.md
30
README.md
@@ -7,19 +7,17 @@
|
|||||||
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
|
For more information check:
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Contributing](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md)
|
|
||||||
- [Docs](http://docs.authorizer.dev/)
|
- [Docs](http://docs.authorizer.dev/)
|
||||||
- [Join Community](https://discord.gg/Zv2D5h6kkK)
|
- [Discord Community](https://discord.gg/Zv2D5h6kkK)
|
||||||
|
- [Contributing Guide](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
# 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,20 +27,22 @@
|
|||||||
- ✅ 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](https://github.com/authorizerdev/authorizer-vue)
|
||||||
- VueJS SDK
|
- [Svelte SDK](https://github.com/authorizerdev/authorizer-svelte)
|
||||||
- Svelte SDK
|
- [Golang SDK](https://github.com/authorizerdev/authorizer-go)
|
||||||
- React Native SDK
|
- React Native SDK
|
||||||
- Flutter SDK
|
- Flutter SDK
|
||||||
- Android Native SDK
|
- Android Native SDK
|
||||||
- iOS native SDK
|
- iOS native SDK
|
||||||
- Golang SDK
|
|
||||||
- Python SDK
|
- Python SDK
|
||||||
- PHP SDK
|
- PHP SDK
|
||||||
- WordPress plugin
|
- WordPress plugin
|
||||||
@@ -64,8 +64,8 @@
|
|||||||
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) |
|
||||||
|
|
||||||
@@ -89,7 +89,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
|
||||||
|
6
app/.prettierrc.json
Normal file
6
app/.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
98
app/package-lock.json
generated
98
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.23.0",
|
"@authorizerdev/authorizer-react": "^1.1.2",
|
||||||
"@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",
|
||||||
@@ -22,26 +22,27 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"@types/styled-components": "^5.1.11"
|
"@types/styled-components": "^5.1.11",
|
||||||
|
"prettier": "2.7.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-js": {
|
"node_modules/@authorizerdev/authorizer-js": {
|
||||||
"version": "0.12.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
||||||
"integrity": "sha512-XgRxAkpRobbp15DeHygfOebCxlPJAXbVaLDckYyuz/PUDTyeMIG65RV5rQHYcL4oeoPqNc42dewwM3ST8JSiNg==",
|
"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.23.0",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
|
||||||
"integrity": "sha512-vOwwrrAorxhVsqpf3BO2In8PMg8RAbGBFu8uLDOvUzkwG0ny5CPg6jLx9+dCkRRsqgB+agBoQoIuXEUP0ijsTA==",
|
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": "^0.12.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 +405,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",
|
||||||
@@ -461,9 +470,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/final-form": {
|
"node_modules/final-form": {
|
||||||
"version": "4.20.6",
|
"version": "4.20.4",
|
||||||
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.6.tgz",
|
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
|
||||||
"integrity": "sha512-fCdwIj49KOaFfDRlXB57Eo+GghIMZQWrA9TakQI3C9uQxHwaFHXqZSNRlUdfnQmNNeySwGOaGPZCvjy58hyv4w==",
|
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.10.0"
|
"@babel/runtime": "^7.10.0"
|
||||||
},
|
},
|
||||||
@@ -609,6 +618,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin-prettier.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.7.2",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
@@ -816,7 +840,7 @@
|
|||||||
"node_modules/tr46": {
|
"node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.3.5",
|
"version": "4.3.5",
|
||||||
@@ -838,12 +862,12 @@
|
|||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
},
|
},
|
||||||
"node_modules/whatwg-url": {
|
"node_modules/whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tr46": "~0.0.3",
|
"tr46": "~0.0.3",
|
||||||
"webidl-conversions": "^3.0.0"
|
"webidl-conversions": "^3.0.0"
|
||||||
@@ -852,19 +876,19 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": {
|
"@authorizerdev/authorizer-js": {
|
||||||
"version": "0.12.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
||||||
"integrity": "sha512-XgRxAkpRobbp15DeHygfOebCxlPJAXbVaLDckYyuz/PUDTyeMIG65RV5rQHYcL4oeoPqNc42dewwM3ST8JSiNg==",
|
"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.23.0",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
|
||||||
"integrity": "sha512-vOwwrrAorxhVsqpf3BO2In8PMg8RAbGBFu8uLDOvUzkwG0ny5CPg6jLx9+dCkRRsqgB+agBoQoIuXEUP0ijsTA==",
|
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@authorizerdev/authorizer-js": "^0.12.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 +1185,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",
|
||||||
@@ -1200,9 +1232,9 @@
|
|||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||||
},
|
},
|
||||||
"final-form": {
|
"final-form": {
|
||||||
"version": "4.20.6",
|
"version": "4.20.4",
|
||||||
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.6.tgz",
|
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
|
||||||
"integrity": "sha512-fCdwIj49KOaFfDRlXB57Eo+GghIMZQWrA9TakQI3C9uQxHwaFHXqZSNRlUdfnQmNNeySwGOaGPZCvjy58hyv4w==",
|
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.10.0"
|
"@babel/runtime": "^7.10.0"
|
||||||
}
|
}
|
||||||
@@ -1313,6 +1345,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.7.2",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
@@ -1482,7 +1520,7 @@
|
|||||||
"tr46": {
|
"tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.3.5",
|
"version": "4.3.5",
|
||||||
@@ -1497,12 +1535,12 @@
|
|||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
},
|
},
|
||||||
"whatwg-url": {
|
"whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tr46": "~0.0.3",
|
"tr46": "~0.0.3",
|
||||||
"webidl-conversions": "^3.0.0"
|
"webidl-conversions": "^3.0.0"
|
||||||
|
@@ -5,13 +5,14 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||||
"start": "NODE_ENV=development node ./esbuild.config.js"
|
"start": "NODE_ENV=development node ./esbuild.config.js",
|
||||||
|
"format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Lakhan Samani",
|
"author": "Lakhan Samani",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "^0.23.0",
|
"@authorizerdev/authorizer-react": "^1.1.2",
|
||||||
"@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",
|
||||||
@@ -19,11 +20,12 @@
|
|||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-is": "^17.0.2",
|
"react-is": "^17.0.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"typescript": "^4.3.5",
|
"styled-components": "^5.3.0",
|
||||||
"styled-components": "^5.3.0"
|
"typescript": "^4.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
"@types/styled-components": "^5.1.11"
|
"@types/styled-components": "^5.1.11",
|
||||||
|
"prettier": "2.7.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
};
|
};
|
||||||
|
@@ -32,7 +32,7 @@ export default function Root({
|
|||||||
const { token, loading, config } = useAuthorizer();
|
const { token, loading, config } = useAuthorizer();
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(
|
const searchParams = new URLSearchParams(
|
||||||
hasWindow() ? window.location.search : ``
|
hasWindow() ? window.location.search : ``,
|
||||||
);
|
);
|
||||||
const state = searchParams.get('state') || createRandomString();
|
const state = searchParams.get('state') || createRandomString();
|
||||||
const scope = searchParams.get('scope')
|
const scope = searchParams.get('scope')
|
||||||
|
@@ -8,7 +8,7 @@ export const createRandomString = () => {
|
|||||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
|
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
|
||||||
let random = '';
|
let random = '';
|
||||||
const randomValues = Array.from(
|
const randomValues = Array.from(
|
||||||
getCrypto().getRandomValues(new Uint8Array(43))
|
getCrypto().getRandomValues(new Uint8Array(43)),
|
||||||
);
|
);
|
||||||
randomValues.forEach((v) => (random += charset[v % charset.length]));
|
randomValues.forEach((v) => (random += charset[v % charset.length]));
|
||||||
return random;
|
return random;
|
||||||
|
6
dashboard/.prettierrc.json
Normal file
6
dashboard/.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
1334
dashboard/package-lock.json
generated
1334
dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,8 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||||
"start": "NODE_ENV=development node ./esbuild.config.js"
|
"start": "NODE_ENV=development node ./esbuild.config.js",
|
||||||
|
"format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Lakhan Samani",
|
"author": "Lakhan Samani",
|
||||||
@@ -26,10 +27,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",
|
||||||
|
"prettier": "2.7.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
@@ -82,7 +82,7 @@ const EditUserModal = ({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
[property]: userData[property],
|
[property]: userData[property],
|
||||||
}),
|
}),
|
||||||
{}
|
{},
|
||||||
);
|
);
|
||||||
const res = await client
|
const res = await client
|
||||||
.mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } })
|
.mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } })
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Flex, Stack, Text, useMediaQuery } from "@chakra-ui/react";
|
import { Flex, Stack, Text, useMediaQuery } from '@chakra-ui/react';
|
||||||
import InputField from "../../components/InputField";
|
import InputField from '../../components/InputField';
|
||||||
import { TextInputType, TextAreaInputType } from "../../constants";
|
import { TextInputType, TextAreaInputType } from '../../constants';
|
||||||
|
|
||||||
const AccessToken = ({ variables, setVariables }: any) => {
|
const AccessToken = ({ variables, setVariables }: any) => {
|
||||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
{' '}
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||||
Access Token
|
Access Token
|
||||||
</Text>
|
</Text>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "50%"}
|
w={isNotSmallerScreen ? '30%' : '50%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Text fontSize="sm">Access Token Expiry Time:</Text>
|
<Text fontSize="sm">Access Token Expiry Time:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
@@ -33,9 +33,9 @@ const AccessToken = ({ variables, setVariables }: any) => {
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "60%"}
|
w={isNotSmallerScreen ? '30%' : '60%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
direction="column"
|
direction="column"
|
||||||
>
|
>
|
||||||
@@ -45,8 +45,8 @@ const AccessToken = ({ variables, setVariables }: any) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
variables={variables}
|
variables={variables}
|
||||||
|
@@ -1,24 +1,24 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
|
||||||
import InputField from "../../components/InputField";
|
import InputField from '../../components/InputField';
|
||||||
import { ArrayInputType} from "../../constants";
|
import { ArrayInputType } from '../../constants';
|
||||||
|
|
||||||
const DomainWhiteListing = ({ variables, setVariables }: any) => {
|
const DomainWhiteListing = ({ variables, setVariables }: any) => {
|
||||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
{' '}
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||||
Domain White Listing
|
Domain White Listing
|
||||||
</Text>
|
</Text>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<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">Allowed Origins:</Text>
|
<Text fontSize="sm">Allowed Origins:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
variables={variables}
|
variables={variables}
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
|
||||||
import InputField from "../../components/InputField";
|
import InputField from '../../components/InputField';
|
||||||
import { TextInputType, HiddenInputType} from "../../constants";
|
import { TextInputType, HiddenInputType } from '../../constants';
|
||||||
const EmailConfigurations = ({
|
const EmailConfigurations = ({
|
||||||
variables,
|
variables,
|
||||||
setVariables,
|
setVariables,
|
||||||
fieldVisibility,
|
fieldVisibility,
|
||||||
setFieldVisibility,
|
setFieldVisibility,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
{' '}
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||||
Email Configurations
|
Email Configurations
|
||||||
</Text>
|
</Text>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<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">SMTP Host:</Text>
|
<Text fontSize="sm">SMTP Host:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
@@ -32,13 +32,13 @@ const EmailConfigurations = ({
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</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">SMTP Port:</Text>
|
<Text fontSize="sm">SMTP Port:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
@@ -48,17 +48,17 @@ const EmailConfigurations = ({
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Text fontSize="sm">SMTP Username:</Text>
|
<Text fontSize="sm">SMTP Username:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
@@ -68,17 +68,17 @@ const EmailConfigurations = ({
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Text fontSize="sm">SMTP Password:</Text>
|
<Text fontSize="sm">SMTP Password:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
@@ -90,13 +90,13 @@ const EmailConfigurations = ({
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</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">From Email:</Text>
|
<Text fontSize="sm">From Email:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
|
@@ -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>
|
||||||
@@ -71,6 +71,94 @@ const Features = ({ variables, setVariables }: any) => {
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="100%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Disable Strong Password:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" mb={3}>
|
||||||
|
<InputField
|
||||||
|
variables={variables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={SwitchInputType.DISABLE_STRONG_PASSWORD}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
<Divider paddingY={5} />
|
||||||
|
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
|
||||||
|
Cookie Security Features
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6}>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||||
|
<Text fontSize="sm">Use Secure App Cookie:</Text>
|
||||||
|
<Text fontSize="x-small">
|
||||||
|
Note: If you set this to insecure, it will set{' '}
|
||||||
|
<code>sameSite</code> property of cookie to <code>lax</code> mode
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start">
|
||||||
|
<InputField
|
||||||
|
variables={variables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={SwitchInputType.APP_COOKIE_SECURE}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||||
|
<Text fontSize="sm">Use Secure Admin Cookie:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start">
|
||||||
|
<InputField
|
||||||
|
variables={variables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={SwitchInputType.ADMIN_COOKIE_SECURE}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -1,12 +1,21 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
import {
|
||||||
|
Flex,
|
||||||
|
Stack,
|
||||||
|
Center,
|
||||||
|
Text,
|
||||||
|
useMediaQuery,
|
||||||
|
Button,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
HiddenInputType,
|
HiddenInputType,
|
||||||
TextInputType,
|
TextInputType,
|
||||||
TextAreaInputType,
|
TextAreaInputType,
|
||||||
} from "../../constants";
|
} from '../../constants';
|
||||||
import GenerateKeysModal from "../GenerateKeysModal";
|
import GenerateKeysModal from '../GenerateKeysModal';
|
||||||
import InputField from "../InputField";
|
import InputField from '../InputField';
|
||||||
|
import { copyTextToClipboard } from '../../utils';
|
||||||
|
|
||||||
const JSTConfigurations = ({
|
const JSTConfigurations = ({
|
||||||
variables,
|
variables,
|
||||||
@@ -19,11 +28,40 @@ const JSTConfigurations = ({
|
|||||||
RSAEncryptionType,
|
RSAEncryptionType,
|
||||||
ECDSAEncryptionType,
|
ECDSAEncryptionType,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const copyJSON = async () => {
|
||||||
|
try {
|
||||||
|
await copyTextToClipboard(
|
||||||
|
JSON.stringify({
|
||||||
|
type: variables.JWT_TYPE,
|
||||||
|
key: variables.JWT_PUBLIC_KEY || variables.JWT_SECRET,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
toast({
|
||||||
|
title: `JWT config copied successfully`,
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error({
|
||||||
|
message: `Failed to copy JWT config`,
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
toast({
|
||||||
|
title: `Failed to copy JWT config`,
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
{' '}
|
||||||
<Flex
|
<Flex
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -32,24 +70,33 @@ const JSTConfigurations = ({
|
|||||||
paddingTop="2%"
|
paddingTop="2%"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
fontSize={isNotSmallerScreen ? "md" : "sm"}
|
fontSize={isNotSmallerScreen ? 'md' : 'sm'}
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
mb={5}
|
mb={5}
|
||||||
>
|
>
|
||||||
JWT (JSON Web Tokens) Configurations
|
JWT (JSON Web Tokens) Configurations
|
||||||
</Text>
|
</Text>
|
||||||
<Flex mb={7}>
|
<Flex mb={7}>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={copyJSON}
|
||||||
|
>
|
||||||
|
Copy As JSON Config
|
||||||
|
</Button>
|
||||||
<GenerateKeysModal jwtType={variables.JWT_TYPE} getData={getData} />
|
<GenerateKeysModal jwtType={variables.JWT_TYPE} getData={getData} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<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 Type:</Text>
|
<Text fontSize="sm">JWT Type:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "2"}
|
mt={isNotSmallerScreen ? '0' : '2'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
@@ -66,13 +113,13 @@ const JSTConfigurations = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
|
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
|
||||||
<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">JWT Secret</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}
|
||||||
@@ -86,13 +133,13 @@ const JSTConfigurations = ({
|
|||||||
</Flex>
|
</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">Public Key</Text>
|
<Text fontSize="sm">Public 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}
|
||||||
@@ -104,13 +151,13 @@ const JSTConfigurations = ({
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</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">Private Key</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}
|
||||||
@@ -124,9 +171,9 @@ const JSTConfigurations = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
@@ -135,8 +182,8 @@ const JSTConfigurations = ({
|
|||||||
</Text>
|
</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}
|
||||||
|
@@ -9,7 +9,14 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { FaGoogle, FaGithub, FaFacebookF, FaLinkedin } from 'react-icons/fa';
|
import {
|
||||||
|
FaGoogle,
|
||||||
|
FaGithub,
|
||||||
|
FaFacebookF,
|
||||||
|
FaLinkedin,
|
||||||
|
FaApple,
|
||||||
|
FaTwitter,
|
||||||
|
} from 'react-icons/fa';
|
||||||
import { TextInputType, HiddenInputType } from '../../constants';
|
import { TextInputType, HiddenInputType } from '../../constants';
|
||||||
|
|
||||||
const OAuthConfig = ({
|
const OAuthConfig = ({
|
||||||
@@ -102,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>
|
||||||
@@ -140,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>
|
||||||
@@ -178,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>
|
||||||
@@ -216,7 +223,83 @@ const OAuthConfig = ({
|
|||||||
fieldVisibility={fieldVisibility}
|
fieldVisibility={fieldVisibility}
|
||||||
setFieldVisibility={setFieldVisibility}
|
setFieldVisibility={setFieldVisibility}
|
||||||
inputType={HiddenInputType.LINKEDIN_CLIENT_SECRET}
|
inputType={HiddenInputType.LINKEDIN_CLIENT_SECRET}
|
||||||
placeholder="LinkedIn Secret"
|
placeholder="LinkedIn 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"
|
||||||
|
>
|
||||||
|
<FaApple style={{ color: '#3b5998' }} />
|
||||||
|
</Center>
|
||||||
|
<Center
|
||||||
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
|
marginRight="1.5%"
|
||||||
|
>
|
||||||
|
<InputField
|
||||||
|
borderRadius={5}
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setVariables}
|
||||||
|
inputType={TextInputType.APPLE_CLIENT_ID}
|
||||||
|
placeholder="Apple 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.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>
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
|
||||||
import InputField from "../InputField";
|
import InputField from '../InputField';
|
||||||
import { TextInputType } from "../../constants";
|
import { TextInputType } from '../../constants';
|
||||||
|
|
||||||
const OrganizationInfo = ({ variables, setVariables }: any) => {
|
const OrganizationInfo = ({ variables, setVariables }: any) => {
|
||||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
{' '}
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||||
Organization Information
|
Organization Information
|
||||||
</Text>
|
</Text>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Text fontSize="sm">Organization Name:</Text>
|
<Text fontSize="sm">Organization Name:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
@@ -32,17 +32,17 @@ const OrganizationInfo = ({ variables, setVariables }: any) => {
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Text fontSize="sm">Organization Logo:</Text>
|
<Text fontSize="sm">Organization Logo:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
Stack,
|
Stack,
|
||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
InputRightElement,
|
InputRightElement,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
} from "@chakra-ui/react";
|
} from '@chakra-ui/react';
|
||||||
import { FaRegEyeSlash, FaRegEye } from "react-icons/fa";
|
import { FaRegEyeSlash, FaRegEye } from 'react-icons/fa';
|
||||||
import InputField from "../InputField";
|
import InputField from '../InputField';
|
||||||
import { HiddenInputType } from "../../constants";
|
import { HiddenInputType } from '../../constants';
|
||||||
const SecurityAdminSecret = ({
|
const SecurityAdminSecret = ({
|
||||||
variables,
|
variables,
|
||||||
setVariables,
|
setVariables,
|
||||||
@@ -20,10 +20,10 @@ const SecurityAdminSecret = ({
|
|||||||
validateAdminSecretHandler,
|
validateAdminSecretHandler,
|
||||||
adminSecret,
|
adminSecret,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
{' '}
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
Security (Admin Secret)
|
Security (Admin Secret)
|
||||||
</Text>
|
</Text>
|
||||||
@@ -35,20 +35,20 @@ const SecurityAdminSecret = ({
|
|||||||
borderRadius="5px"
|
borderRadius="5px"
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
marginTop={isNotSmallerScreen ? "3%" : "5%"}
|
marginTop={isNotSmallerScreen ? '3%' : '5%'}
|
||||||
direction={isNotSmallerScreen ? "row" : "column"}
|
direction={isNotSmallerScreen ? 'row' : 'column'}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
mt={3}
|
mt={3}
|
||||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Text fontSize="sm">Old Admin Secret:</Text>
|
<Text fontSize="sm">Old Admin Secret:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputGroup size="sm">
|
<InputGroup size="sm">
|
||||||
<Input
|
<Input
|
||||||
@@ -59,8 +59,8 @@ const SecurityAdminSecret = ({
|
|||||||
onChange={(event: any) => validateAdminSecretHandler(event)}
|
onChange={(event: any) => validateAdminSecretHandler(event)}
|
||||||
type={
|
type={
|
||||||
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
|
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
|
||||||
? "password"
|
? 'password'
|
||||||
: "text"
|
: 'text'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InputRightElement
|
<InputRightElement
|
||||||
@@ -104,18 +104,18 @@ const SecurityAdminSecret = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
<Flex
|
||||||
paddingBottom="3%"
|
paddingBottom="3%"
|
||||||
direction={isNotSmallerScreen ? "row" : "column"}
|
direction={isNotSmallerScreen ? 'row' : 'column'}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
w={isNotSmallerScreen ? "30%" : "50%"}
|
w={isNotSmallerScreen ? '30%' : '50%'}
|
||||||
justifyContent="start"
|
justifyContent="start"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Text fontSize="sm">New Admin Secret:</Text>
|
<Text fontSize="sm">New Admin Secret:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center
|
<Center
|
||||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||||
mt={isNotSmallerScreen ? "0" : "3"}
|
mt={isNotSmallerScreen ? '0' : '3'}
|
||||||
>
|
>
|
||||||
<InputField
|
<InputField
|
||||||
borderRadius={5}
|
borderRadius={5}
|
||||||
|
@@ -167,7 +167,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{Object.values(HMACEncryptionType).includes(
|
{Object.values(HMACEncryptionType).includes(
|
||||||
stateVariables.JWT_TYPE
|
stateVariables.JWT_TYPE,
|
||||||
) ? (
|
) ? (
|
||||||
<Flex marginTop="8">
|
<Flex marginTop="8">
|
||||||
<Flex w="23%" justifyContent="start" alignItems="center">
|
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||||
|
@@ -12,7 +12,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Textarea,
|
Textarea,
|
||||||
Switch,
|
Switch,
|
||||||
Code,
|
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
@@ -65,7 +64,7 @@ const InputField = ({
|
|||||||
const updateInputHandler = (
|
const updateInputHandler = (
|
||||||
type: string,
|
type: string,
|
||||||
operation: any,
|
operation: any,
|
||||||
role: string = ''
|
role: string = '',
|
||||||
) => {
|
) => {
|
||||||
if (operation === ArrayInputOperations.APPEND) {
|
if (operation === ArrayInputOperations.APPEND) {
|
||||||
if (inputData[type] !== '') {
|
if (inputData[type] !== '') {
|
||||||
@@ -79,7 +78,7 @@ const InputField = ({
|
|||||||
}
|
}
|
||||||
if (operation === ArrayInputOperations.REMOVE) {
|
if (operation === ArrayInputOperations.REMOVE) {
|
||||||
let updatedEnvVars = variables[type].filter(
|
let updatedEnvVars = variables[type].filter(
|
||||||
(item: string) => item !== role
|
(item: string) => item !== role,
|
||||||
);
|
);
|
||||||
setVariables({
|
setVariables({
|
||||||
...variables,
|
...variables,
|
||||||
@@ -96,7 +95,7 @@ const InputField = ({
|
|||||||
onChange={(
|
onChange={(
|
||||||
event: Event & {
|
event: Event & {
|
||||||
target: HTMLInputElement;
|
target: HTMLInputElement;
|
||||||
}
|
},
|
||||||
) =>
|
) =>
|
||||||
setVariables({
|
setVariables({
|
||||||
...variables,
|
...variables,
|
||||||
@@ -121,7 +120,7 @@ const InputField = ({
|
|||||||
onChange={(
|
onChange={(
|
||||||
event: Event & {
|
event: Event & {
|
||||||
target: HTMLInputElement;
|
target: HTMLInputElement;
|
||||||
}
|
},
|
||||||
) =>
|
) =>
|
||||||
setVariables({
|
setVariables({
|
||||||
...variables,
|
...variables,
|
||||||
@@ -208,7 +207,7 @@ const InputField = ({
|
|||||||
updateInputHandler(
|
updateInputHandler(
|
||||||
inputType,
|
inputType,
|
||||||
ArrayInputOperations.REMOVE,
|
ArrayInputOperations.REMOVE,
|
||||||
role
|
role,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -289,7 +288,7 @@ const InputField = ({
|
|||||||
onChange={(
|
onChange={(
|
||||||
event: Event & {
|
event: Event & {
|
||||||
target: HTMLInputElement;
|
target: HTMLInputElement;
|
||||||
}
|
},
|
||||||
) =>
|
) =>
|
||||||
setVariables({
|
setVariables({
|
||||||
...variables,
|
...variables,
|
||||||
|
@@ -304,7 +304,7 @@ const InviteMembersModal = ({
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateEmailListHandler(
|
updateEmailListHandler(
|
||||||
ArrayInputOperations.REMOVE,
|
ArrayInputOperations.REMOVE,
|
||||||
index
|
index,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@@ -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 {
|
||||||
@@ -214,7 +218,7 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
|||||||
</NavItem>{' '}
|
</NavItem>{' '}
|
||||||
</Text>
|
</Text>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
href="/playground"
|
href="/playground"
|
||||||
|
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;
|
@@ -8,6 +8,8 @@ export const TextInputType = {
|
|||||||
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
|
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
|
||||||
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',
|
||||||
|
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',
|
||||||
@@ -33,6 +35,8 @@ export const HiddenInputType = {
|
|||||||
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
|
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
|
||||||
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',
|
||||||
|
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',
|
||||||
@@ -59,12 +63,17 @@ export const TextAreaInputType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SwitchInputType = {
|
export const SwitchInputType = {
|
||||||
|
APP_COOKIE_SECURE: 'APP_COOKIE_SECURE',
|
||||||
|
ADMIN_COOKIE_SECURE: 'ADMIN_COOKIE_SECURE',
|
||||||
DISABLE_LOGIN_PAGE: 'DISABLE_LOGIN_PAGE',
|
DISABLE_LOGIN_PAGE: 'DISABLE_LOGIN_PAGE',
|
||||||
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
|
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
|
||||||
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
|
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
|
||||||
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
|
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
|
||||||
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_MULTI_FACTOR_AUTHENTICATION: 'DISABLE_MULTI_FACTOR_AUTHENTICATION',
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION: 'ENFORCE_MULTI_FACTOR_AUTHENTICATION',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DateInputType = {
|
export const DateInputType = {
|
||||||
@@ -103,6 +112,10 @@ export interface envVarTypes {
|
|||||||
FACEBOOK_CLIENT_SECRET: string;
|
FACEBOOK_CLIENT_SECRET: string;
|
||||||
LINKEDIN_CLIENT_ID: string;
|
LINKEDIN_CLIENT_ID: string;
|
||||||
LINKEDIN_CLIENT_SECRET: string;
|
LINKEDIN_CLIENT_SECRET: string;
|
||||||
|
APPLE_CLIENT_ID: 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] | [];
|
||||||
@@ -122,16 +135,21 @@ export interface envVarTypes {
|
|||||||
ORGANIZATION_LOGO: string;
|
ORGANIZATION_LOGO: string;
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
|
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
|
||||||
ADMIN_SECRET: string;
|
ADMIN_SECRET: string;
|
||||||
|
APP_COOKIE_SECURE: boolean;
|
||||||
|
ADMIN_COOKIE_SECURE: boolean;
|
||||||
DISABLE_LOGIN_PAGE: boolean;
|
DISABLE_LOGIN_PAGE: boolean;
|
||||||
DISABLE_MAGIC_LINK_LOGIN: boolean;
|
DISABLE_MAGIC_LINK_LOGIN: boolean;
|
||||||
DISABLE_EMAIL_VERIFICATION: boolean;
|
DISABLE_EMAIL_VERIFICATION: boolean;
|
||||||
DISABLE_BASIC_AUTHENTICATION: boolean;
|
DISABLE_BASIC_AUTHENTICATION: boolean;
|
||||||
DISABLE_SIGN_UP: boolean;
|
DISABLE_SIGN_UP: boolean;
|
||||||
|
DISABLE_STRONG_PASSWORD: boolean;
|
||||||
OLD_ADMIN_SECRET: string;
|
OLD_ADMIN_SECRET: string;
|
||||||
DATABASE_NAME: string;
|
DATABASE_NAME: string;
|
||||||
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 = {
|
||||||
@@ -147,3 +165,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,45 +18,54 @@ 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
|
||||||
DEFAULT_ROLES,
|
APPLE_CLIENT_ID
|
||||||
PROTECTED_ROLES,
|
APPLE_CLIENT_SECRET
|
||||||
ROLES,
|
TWITTER_CLIENT_ID
|
||||||
JWT_TYPE,
|
TWITTER_CLIENT_SECRET
|
||||||
JWT_SECRET,
|
DEFAULT_ROLES
|
||||||
JWT_ROLE_CLAIM,
|
PROTECTED_ROLES
|
||||||
JWT_PRIVATE_KEY,
|
ROLES
|
||||||
JWT_PUBLIC_KEY,
|
JWT_TYPE
|
||||||
REDIS_URL,
|
JWT_SECRET
|
||||||
SMTP_HOST,
|
JWT_ROLE_CLAIM
|
||||||
SMTP_PORT,
|
JWT_PRIVATE_KEY
|
||||||
SMTP_USERNAME,
|
JWT_PUBLIC_KEY
|
||||||
SMTP_PASSWORD,
|
REDIS_URL
|
||||||
SENDER_EMAIL,
|
SMTP_HOST
|
||||||
ALLOWED_ORIGINS,
|
SMTP_PORT
|
||||||
ORGANIZATION_NAME,
|
SMTP_USERNAME
|
||||||
ORGANIZATION_LOGO,
|
SMTP_PASSWORD
|
||||||
ADMIN_SECRET,
|
SENDER_EMAIL
|
||||||
DISABLE_LOGIN_PAGE,
|
ALLOWED_ORIGINS
|
||||||
DISABLE_MAGIC_LINK_LOGIN,
|
ORGANIZATION_NAME
|
||||||
DISABLE_EMAIL_VERIFICATION,
|
ORGANIZATION_LOGO
|
||||||
DISABLE_BASIC_AUTHENTICATION,
|
ADMIN_SECRET
|
||||||
DISABLE_SIGN_UP,
|
APP_COOKIE_SECURE
|
||||||
DISABLE_REDIS_FOR_ENV,
|
ADMIN_COOKIE_SECURE
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT,
|
DISABLE_LOGIN_PAGE
|
||||||
DATABASE_NAME,
|
DISABLE_MAGIC_LINK_LOGIN
|
||||||
DATABASE_TYPE,
|
DISABLE_EMAIL_VERIFICATION
|
||||||
DATABASE_URL,
|
DISABLE_BASIC_AUTHENTICATION
|
||||||
ACCESS_TOKEN_EXPIRY_TIME,
|
DISABLE_SIGN_UP
|
||||||
|
DISABLE_STRONG_PASSWORD
|
||||||
|
DISABLE_REDIS_FOR_ENV
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT
|
||||||
|
DATABASE_NAME
|
||||||
|
DATABASE_TYPE
|
||||||
|
DATABASE_URL
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME
|
||||||
|
DISABLE_MULTI_FACTOR_AUTHENTICATION
|
||||||
|
ENFORCE_MULTI_FACTOR_AUTHENTICATION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -86,6 +95,7 @@ export const UserDetailsQuery = `
|
|||||||
roles
|
roles
|
||||||
created_at
|
created_at
|
||||||
revoked_timestamp
|
revoked_timestamp
|
||||||
|
is_multi_factor_auth_enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,3 +108,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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@@ -6,5 +6,5 @@ ReactDOM.render(
|
|||||||
<div>
|
<div>
|
||||||
<App />
|
<App />
|
||||||
</div>,
|
</div>,
|
||||||
document.getElementById('root')
|
document.getElementById('root'),
|
||||||
);
|
);
|
||||||
|
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;
|
@@ -48,6 +48,10 @@ const Environment = () => {
|
|||||||
FACEBOOK_CLIENT_SECRET: '',
|
FACEBOOK_CLIENT_SECRET: '',
|
||||||
LINKEDIN_CLIENT_ID: '',
|
LINKEDIN_CLIENT_ID: '',
|
||||||
LINKEDIN_CLIENT_SECRET: '',
|
LINKEDIN_CLIENT_SECRET: '',
|
||||||
|
APPLE_CLIENT_ID: '',
|
||||||
|
APPLE_CLIENT_SECRET: '',
|
||||||
|
TWITTER_CLIENT_ID: '',
|
||||||
|
TWITTER_CLIENT_SECRET: '',
|
||||||
ROLES: [],
|
ROLES: [],
|
||||||
DEFAULT_ROLES: [],
|
DEFAULT_ROLES: [],
|
||||||
PROTECTED_ROLES: [],
|
PROTECTED_ROLES: [],
|
||||||
@@ -67,16 +71,21 @@ const Environment = () => {
|
|||||||
ORGANIZATION_LOGO: '',
|
ORGANIZATION_LOGO: '',
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT: '',
|
CUSTOM_ACCESS_TOKEN_SCRIPT: '',
|
||||||
ADMIN_SECRET: '',
|
ADMIN_SECRET: '',
|
||||||
|
APP_COOKIE_SECURE: false,
|
||||||
|
ADMIN_COOKIE_SECURE: false,
|
||||||
DISABLE_LOGIN_PAGE: false,
|
DISABLE_LOGIN_PAGE: false,
|
||||||
DISABLE_MAGIC_LINK_LOGIN: false,
|
DISABLE_MAGIC_LINK_LOGIN: false,
|
||||||
DISABLE_EMAIL_VERIFICATION: false,
|
DISABLE_EMAIL_VERIFICATION: false,
|
||||||
DISABLE_BASIC_AUTHENTICATION: false,
|
DISABLE_BASIC_AUTHENTICATION: false,
|
||||||
DISABLE_SIGN_UP: false,
|
DISABLE_SIGN_UP: false,
|
||||||
|
DISABLE_STRONG_PASSWORD: false,
|
||||||
OLD_ADMIN_SECRET: '',
|
OLD_ADMIN_SECRET: '',
|
||||||
DATABASE_NAME: '',
|
DATABASE_NAME: '',
|
||||||
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<
|
||||||
@@ -86,6 +95,8 @@ const Environment = () => {
|
|||||||
GITHUB_CLIENT_SECRET: false,
|
GITHUB_CLIENT_SECRET: false,
|
||||||
FACEBOOK_CLIENT_SECRET: false,
|
FACEBOOK_CLIENT_SECRET: false,
|
||||||
LINKEDIN_CLIENT_SECRET: false,
|
LINKEDIN_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,
|
||||||
@@ -146,7 +157,7 @@ const Environment = () => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
[property]: envVariables[property],
|
[property]: envVariables[property],
|
||||||
}),
|
}),
|
||||||
{}
|
{},
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
updatedEnvVariables[HiddenInputType.ADMIN_SECRET] === '' ||
|
updatedEnvVariables[HiddenInputType.ADMIN_SECRET] === '' ||
|
||||||
|
@@ -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 {
|
||||||
@@ -193,7 +195,7 @@ export default function Users() {
|
|||||||
|
|
||||||
const updateAccessHandler = async (
|
const updateAccessHandler = async (
|
||||||
id: string,
|
id: string,
|
||||||
action: updateAccessActions
|
action: updateAccessActions,
|
||||||
) => {
|
) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case updateAccessActions.ENABLE:
|
case updateAccessActions.ENABLE:
|
||||||
@@ -250,6 +252,34 @@ 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,6 +294,7 @@ export default function Users() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
userList.length > 0 ? (
|
userList.length > 0 ? (
|
||||||
|
<TableContainer>
|
||||||
<Table variant="simple">
|
<Table variant="simple">
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
@@ -273,6 +304,11 @@ export default function Users() {
|
|||||||
<Th>Roles</Th>
|
<Th>Roles</Th>
|
||||||
<Th>Verified</Th>
|
<Th>Verified</Th>
|
||||||
<Th>Access</Th>
|
<Th>Access</Th>
|
||||||
|
<Th>
|
||||||
|
<Tooltip label="MultiFactor Authentication Enabled / Disabled">
|
||||||
|
MFA
|
||||||
|
</Tooltip>
|
||||||
|
</Th>
|
||||||
<Th>Actions</Th>
|
<Th>Actions</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
@@ -305,6 +341,19 @@ export default function Users() {
|
|||||||
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
|
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
user.is_multi_factor_auth_enabled ? 'green' : 'red'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{user.is_multi_factor_auth_enabled
|
||||||
|
? 'Enabled'
|
||||||
|
: 'Disabled'}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton as={Button} variant="unstyled" size="sm">
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
@@ -339,7 +388,7 @@ export default function Users() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateAccessHandler(
|
updateAccessHandler(
|
||||||
user.id,
|
user.id,
|
||||||
updateAccessActions.ENABLE
|
updateAccessActions.ENABLE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -350,13 +399,30 @@ export default function Users() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateAccessHandler(
|
updateAccessHandler(
|
||||||
user.id,
|
user.id,
|
||||||
updateAccessActions.REVOKE
|
updateAccessActions.REVOKE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Revoke Access
|
Revoke Access
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{user.is_multi_factor_auth_enabled ? (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
multiFactorAuthUpdateHandler(user)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Disable MultiFactor Authentication
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
multiFactorAuthUpdateHandler(user)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Enable MultiFactor Authentication
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Td>
|
</Td>
|
||||||
@@ -487,6 +553,7 @@ export default function Users() {
|
|||||||
</TableCaption>
|
</TableCaption>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
) : (
|
) : (
|
||||||
<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,11 +3,13 @@ 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();
|
||||||
@@ -29,6 +31,8 @@ export const AppRoutes = () => {
|
|||||||
<Route path="/:sec" element={<Environment />} />
|
<Route path="/:sec" element={<Environment />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="users" element={<Users />} />
|
<Route path="users" element={<Users />} />
|
||||||
|
<Route path="webhooks" element={<Webhooks />} />
|
||||||
|
<Route path="email-templates" element={<EmailTemplates />} />
|
||||||
<Route path="*" element={<Home />} />
|
<Route path="*" element={<Home />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@@ -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) => {
|
||||||
@@ -70,7 +67,7 @@ export const validateEmail = (email: string) => {
|
|||||||
return email
|
return email
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.match(
|
.match(
|
||||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||||
)
|
)
|
||||||
? true
|
? true
|
||||||
: false;
|
: false;
|
||||||
@@ -81,7 +78,7 @@ export const validateURI = (uri: string) => {
|
|||||||
return uri
|
return uri
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.match(
|
.match(
|
||||||
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/
|
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/,
|
||||||
)
|
)
|
||||||
? true
|
? true
|
||||||
: false;
|
: false;
|
||||||
|
@@ -27,7 +27,7 @@ const parseCSV = (file: File, delimiter: string): Promise<dataTypes[]> => {
|
|||||||
value: email.trim(),
|
value: email.trim(),
|
||||||
isInvalid: !validateEmail(email.trim()),
|
isInvalid: !validateEmail(email.trim()),
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "authorizer",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
20
server/constants/auth_methods.go
Normal file
20
server/constants/auth_methods.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthRecipeMethodBasicAuth is the basic_auth auth method
|
||||||
|
AuthRecipeMethodBasicAuth = "basic_auth"
|
||||||
|
// AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
|
||||||
|
AuthRecipeMethodMagicLinkLogin = "magic_link_login"
|
||||||
|
// AuthRecipeMethodGoogle is the google auth method
|
||||||
|
AuthRecipeMethodGoogle = "google"
|
||||||
|
// AuthRecipeMethodGithub is the github auth method
|
||||||
|
AuthRecipeMethodGithub = "github"
|
||||||
|
// AuthRecipeMethodFacebook is the facebook auth method
|
||||||
|
AuthRecipeMethodFacebook = "facebook"
|
||||||
|
// AuthRecipeMethodLinkedin is the linkedin auth method
|
||||||
|
AuthRecipeMethodLinkedIn = "linkedin"
|
||||||
|
// AuthRecipeMethodApple is the apple auth method
|
||||||
|
AuthRecipeMethodApple = "apple"
|
||||||
|
// AuthRecipeMethodTwitter is the twitter auth method
|
||||||
|
AuthRecipeMethodTwitter = "twitter"
|
||||||
|
)
|
@@ -23,4 +23,6 @@ const (
|
|||||||
DbTypeScyllaDB = "scylladb"
|
DbTypeScyllaDB = "scylladb"
|
||||||
// DbTypeCockroachDB is the cockroach database type
|
// DbTypeCockroachDB is the cockroach database type
|
||||||
DbTypeCockroachDB = "cockroachdb"
|
DbTypeCockroachDB = "cockroachdb"
|
||||||
|
// DbTypePlanetScaleDB is the planetscale database type
|
||||||
|
DbTypePlanetScaleDB = "planetscale"
|
||||||
)
|
)
|
||||||
|
@@ -47,6 +47,12 @@ 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"
|
||||||
|
// EnvKeyAppCookieSecure key for env variable APP_COOKIE_SECURE
|
||||||
|
EnvKeyAppCookieSecure = "APP_COOKIE_SECURE"
|
||||||
|
// EnvKeyAdminCookieSecure key for env variable ADMIN_COOKIE_SECURE
|
||||||
|
EnvKeyAdminCookieSecure = "ADMIN_COOKIE_SECURE"
|
||||||
// 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
|
||||||
@@ -79,6 +85,14 @@ const (
|
|||||||
EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID"
|
EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID"
|
||||||
// EnvKeyLinkedinClientSecret key for env variable LINKEDIN_CLIENT_SECRET
|
// EnvKeyLinkedinClientSecret key for env variable LINKEDIN_CLIENT_SECRET
|
||||||
EnvKeyLinkedInClientSecret = "LINKEDIN_CLIENT_SECRET"
|
EnvKeyLinkedInClientSecret = "LINKEDIN_CLIENT_SECRET"
|
||||||
|
// EnvKeyAppleClientID key for env variable APPLE_CLIENT_ID
|
||||||
|
EnvKeyAppleClientID = "APPLE_CLIENT_ID"
|
||||||
|
// EnvKeyAppleClientSecret key for env variable 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
|
||||||
@@ -111,6 +125,14 @@ const (
|
|||||||
EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
|
EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
|
||||||
// EnvKeyDisableRedisForEnv key for env variable DISABLE_REDIS_FOR_ENV
|
// EnvKeyDisableRedisForEnv key for env variable DISABLE_REDIS_FOR_ENV
|
||||||
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
|
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
|
||||||
|
// EnvKeyDisableStrongPassword key for env variable 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
|
||||||
|
19
server/constants/oauth2.go
Normal file
19
server/constants/oauth2.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// - query: for Authorization Code grant. 302 Found triggers redirect.
|
||||||
|
ResponseModeQuery = "query"
|
||||||
|
// - fragment: for Implicit grant. 302 Found triggers redirect.
|
||||||
|
ResponseModeFragment = "fragment"
|
||||||
|
// - form_post: 200 OK with response parameters embedded in an HTML form as hidden parameters.
|
||||||
|
ResponseModeFormPost = "form_post"
|
||||||
|
// - web_message: For Silent Authentication. Uses HTML5 web messaging.
|
||||||
|
ResponseModeWebMessage = "web_message"
|
||||||
|
|
||||||
|
// For the Authorization Code grant, use response_type=code to include the authorization code.
|
||||||
|
ResponseTypeCode = "code"
|
||||||
|
// For the Implicit grant, use response_type=token to include an access token.
|
||||||
|
ResponseTypeToken = "token"
|
||||||
|
// For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
|
||||||
|
ResponseTypeIDToken = "id_token"
|
||||||
|
)
|
@@ -8,7 +8,12 @@ const (
|
|||||||
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
|
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&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
|
// 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
|
||||||
|
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"
|
||||||
)
|
)
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
package constants
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SignupMethodBasicAuth is the basic_auth signup method
|
|
||||||
SignupMethodBasicAuth = "basic_auth"
|
|
||||||
// SignupMethodMagicLinkLogin is the magic_link_login signup method
|
|
||||||
SignupMethodMagicLinkLogin = "magic_link_login"
|
|
||||||
// SignupMethodGoogle is the google signup method
|
|
||||||
SignupMethodGoogle = "google"
|
|
||||||
// SignupMethodGithub is the github signup method
|
|
||||||
SignupMethodGithub = "github"
|
|
||||||
// SignupMethodFacebook is the facebook signup method
|
|
||||||
SignupMethodFacebook = "facebook"
|
|
||||||
// SignupMethodLinkedin is the linkedin signup method
|
|
||||||
SignupMethodLinkedIn = "linkedin"
|
|
||||||
)
|
|
@@ -9,4 +9,19 @@ 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// VerificationTypes is slice of all verification types
|
||||||
|
VerificationTypes = []string{
|
||||||
|
VerificationTypeBasicAuthSignup,
|
||||||
|
VerificationTypeMagicLinkLogin,
|
||||||
|
VerificationTypeUpdateEmail,
|
||||||
|
VerificationTypeForgotPassword,
|
||||||
|
VerificationTypeInviteMember,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
18
server/constants/webhook_event.go
Normal file
18
server/constants/webhook_event.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// UserLoginWebhookEvent name for login event
|
||||||
|
UserLoginWebhookEvent = `user.login`
|
||||||
|
// UserCreatedWebhookEvent name for user creation event
|
||||||
|
// This is triggered when user entry is created but still not verified
|
||||||
|
UserCreatedWebhookEvent = `user.created`
|
||||||
|
// UserSignUpWebhookEvent name for signup event
|
||||||
|
UserSignUpWebhookEvent = `user.signup`
|
||||||
|
// UserAccessRevokedWebhookEvent name for user access revoke event
|
||||||
|
UserAccessRevokedWebhookEvent = `user.access_revoked`
|
||||||
|
// UserAccessEnabledWebhookEvent name for user access enable event
|
||||||
|
UserAccessEnabledWebhookEvent = `user.access_enabled`
|
||||||
|
// UserDeletedWebhookEvent name for user deleted event
|
||||||
|
UserDeletedWebhookEvent = `user.deleted`
|
||||||
|
)
|
@@ -3,15 +3,24 @@ package cookie
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetAdminCookie sets the admin cookie in the response
|
// SetAdminCookie sets the admin cookie in the response
|
||||||
func SetAdminCookie(gc *gin.Context, token string) {
|
func SetAdminCookie(gc *gin.Context, token string) {
|
||||||
secure := true
|
adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
|
||||||
httpOnly := true
|
if err != nil {
|
||||||
|
log.Debug("Error while getting admin cookie secure from env variable: %v", err)
|
||||||
|
adminCookieSecure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
secure := adminCookieSecure
|
||||||
|
httpOnly := adminCookieSecure
|
||||||
hostname := parsers.GetHost(gc)
|
hostname := parsers.GetHost(gc)
|
||||||
host, _ := parsers.GetHostParts(hostname)
|
host, _ := parsers.GetHostParts(hostname)
|
||||||
gc.SetCookie(constants.AdminCookieName, token, 3600, "/", host, secure, httpOnly)
|
gc.SetCookie(constants.AdminCookieName, token, 3600, "/", host, secure, httpOnly)
|
||||||
@@ -35,8 +44,14 @@ func GetAdminCookie(gc *gin.Context) (string, error) {
|
|||||||
|
|
||||||
// DeleteAdminCookie sets the response cookie to empty
|
// DeleteAdminCookie sets the response cookie to empty
|
||||||
func DeleteAdminCookie(gc *gin.Context) {
|
func DeleteAdminCookie(gc *gin.Context) {
|
||||||
secure := true
|
adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
|
||||||
httpOnly := true
|
if err != nil {
|
||||||
|
log.Debug("Error while getting admin cookie secure from env variable: %v", err)
|
||||||
|
adminCookieSecure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
secure := adminCookieSecure
|
||||||
|
httpOnly := adminCookieSecure
|
||||||
hostname := parsers.GetHost(gc)
|
hostname := parsers.GetHost(gc)
|
||||||
host, _ := parsers.GetHostParts(hostname)
|
host, _ := parsers.GetHostParts(hostname)
|
||||||
gc.SetCookie(constants.AdminCookieName, "", -1, "/", host, secure, httpOnly)
|
gc.SetCookie(constants.AdminCookieName, "", -1, "/", host, secure, httpOnly)
|
||||||
|
@@ -4,15 +4,24 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetSession sets the session cookie in the response
|
// SetSession sets the session cookie in the response
|
||||||
func SetSession(gc *gin.Context, sessionID string) {
|
func SetSession(gc *gin.Context, sessionID string) {
|
||||||
secure := true
|
appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
|
||||||
httpOnly := true
|
if err != nil {
|
||||||
|
log.Debug("Error while getting app cookie secure from env variable: %v", err)
|
||||||
|
appCookieSecure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
secure := appCookieSecure
|
||||||
|
httpOnly := appCookieSecure
|
||||||
hostname := parsers.GetHost(gc)
|
hostname := parsers.GetHost(gc)
|
||||||
host, _ := parsers.GetHostParts(hostname)
|
host, _ := parsers.GetHostParts(hostname)
|
||||||
domain := parsers.GetDomainName(hostname)
|
domain := parsers.GetDomainName(hostname)
|
||||||
@@ -20,18 +29,34 @@ func SetSession(gc *gin.Context, sessionID string) {
|
|||||||
domain = "." + domain
|
domain = "." + domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since app cookie can come from cross site it becomes important to set this in lax mode when insecure.
|
||||||
|
// Example person using custom UI on their app domain and making request to authorizer domain.
|
||||||
|
// For more information check:
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
|
||||||
|
// https://github.com/gin-gonic/gin/blob/master/context.go#L86
|
||||||
|
// TODO add ability to sameSite = none / strict from dashboard
|
||||||
|
if !appCookieSecure {
|
||||||
|
gc.SetSameSite(http.SameSiteLaxMode)
|
||||||
|
} else {
|
||||||
|
gc.SetSameSite(http.SameSiteNoneMode)
|
||||||
|
}
|
||||||
// TODO allow configuring from dashboard
|
// TODO allow configuring from dashboard
|
||||||
year := 60 * 60 * 24 * 365
|
year := 60 * 60 * 24 * 365
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteNoneMode)
|
|
||||||
gc.SetCookie(constants.AppCookieName+"_session", sessionID, year, "/", host, secure, httpOnly)
|
gc.SetCookie(constants.AppCookieName+"_session", sessionID, year, "/", host, secure, httpOnly)
|
||||||
gc.SetCookie(constants.AppCookieName+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
|
gc.SetCookie(constants.AppCookieName+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession sets session cookies to expire
|
// DeleteSession sets session cookies to expire
|
||||||
func DeleteSession(gc *gin.Context) {
|
func DeleteSession(gc *gin.Context) {
|
||||||
secure := true
|
appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
|
||||||
httpOnly := true
|
if err != nil {
|
||||||
|
log.Debug("Error while getting app cookie secure from env variable: %v", err)
|
||||||
|
appCookieSecure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
secure := appCookieSecure
|
||||||
|
httpOnly := appCookieSecure
|
||||||
hostname := parsers.GetHost(gc)
|
hostname := parsers.GetHost(gc)
|
||||||
host, _ := parsers.GetHostParts(hostname)
|
host, _ := parsers.GetHostParts(hostname)
|
||||||
domain := parsers.GetDomainName(hostname)
|
domain := parsers.GetDomainName(hostname)
|
||||||
|
37
server/db/models/email_templates.go
Normal file
37
server/db/models/email_templates.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmailTemplate model for database
|
||||||
|
type EmailTemplate struct {
|
||||||
|
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||||
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
|
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
|
||||||
|
Subject string `json:"subject" bson:"subject" cql:"subject"`
|
||||||
|
Template string `json:"template" bson:"template" cql:"template"`
|
||||||
|
Design string `json:"design" bson:"design" cql:"design"`
|
||||||
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsAPIEmailTemplate to return email template as graphql response object
|
||||||
|
func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
|
||||||
|
id := e.ID
|
||||||
|
if strings.Contains(id, Collections.EmailTemplate+"/") {
|
||||||
|
id = strings.TrimPrefix(id, Collections.EmailTemplate+"/")
|
||||||
|
}
|
||||||
|
return &model.EmailTemplate{
|
||||||
|
ID: id,
|
||||||
|
EventName: e.EventName,
|
||||||
|
Subject: e.Subject,
|
||||||
|
Template: e.Template,
|
||||||
|
Design: e.Design,
|
||||||
|
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
|
||||||
|
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
|
||||||
|
}
|
||||||
|
}
|
@@ -6,8 +6,8 @@ package models
|
|||||||
type Env struct {
|
type Env 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"`
|
||||||
EnvData string `gorm:"type:text" json:"env" bson:"env" cql:"env"`
|
EnvData string `json:"env" bson:"env" cql:"env"`
|
||||||
Hash string `gorm:"type:text" json:"hash" bson:"hash" cql:"hash"`
|
Hash string `json:"hash" bson:"hash" cql:"hash"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,10 @@ type CollectionList struct {
|
|||||||
VerificationRequest string
|
VerificationRequest string
|
||||||
Session string
|
Session string
|
||||||
Env string
|
Env string
|
||||||
|
Webhook string
|
||||||
|
WebhookLog string
|
||||||
|
EmailTemplate string
|
||||||
|
OTP string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -17,5 +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 + "webhooks",
|
||||||
|
WebhookLog: Prefix + "webhook_logs",
|
||||||
|
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"`
|
||||||
|
}
|
@@ -6,8 +6,7 @@ package models
|
|||||||
type Session struct {
|
type Session 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"`
|
||||||
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id" cql:"user_id"`
|
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id"`
|
||||||
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-" cql:"-"`
|
|
||||||
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
|
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
|
||||||
IP string `json:"ip" bson:"ip" cql:"ip"`
|
IP string `json:"ip" bson:"ip" cql:"ip"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
@@ -15,7 +17,7 @@ type User struct {
|
|||||||
|
|
||||||
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 `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"`
|
||||||
@@ -25,9 +27,10 @@ type User struct {
|
|||||||
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 `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"`
|
||||||
|
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
}
|
}
|
||||||
@@ -35,9 +38,11 @@ type User struct {
|
|||||||
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
|
||||||
email := user.Email
|
|
||||||
createdAt := user.CreatedAt
|
// id := user.ID
|
||||||
updatedAt := user.UpdatedAt
|
// if strings.Contains(id, Collections.User+"/") {
|
||||||
|
// id = strings.TrimPrefix(id, Collections.User+"/")
|
||||||
|
// }
|
||||||
return &model.User{
|
return &model.User{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
@@ -47,7 +52,7 @@ func (user *User) AsAPIUser() *model.User {
|
|||||||
FamilyName: user.FamilyName,
|
FamilyName: user.FamilyName,
|
||||||
MiddleName: user.MiddleName,
|
MiddleName: user.MiddleName,
|
||||||
Nickname: user.Nickname,
|
Nickname: user.Nickname,
|
||||||
PreferredUsername: &email,
|
PreferredUsername: refs.NewStringRef(user.Email),
|
||||||
Gender: user.Gender,
|
Gender: user.Gender,
|
||||||
Birthdate: user.Birthdate,
|
Birthdate: user.Birthdate,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
@@ -55,7 +60,15 @@ func (user *User) AsAPIUser() *model.User {
|
|||||||
Picture: user.Picture,
|
Picture: user.Picture,
|
||||||
Roles: strings.Split(user.Roles, ","),
|
Roles: strings.Split(user.Roles, ","),
|
||||||
RevokedTimestamp: user.RevokedTimestamp,
|
RevokedTimestamp: user.RevokedTimestamp,
|
||||||
CreatedAt: &createdAt,
|
IsMultiFactorAuthEnabled: user.IsMultiFactorAuthEnabled,
|
||||||
UpdatedAt: &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
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "github.com/authorizerdev/authorizer/server/graph/model"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
)
|
||||||
|
|
||||||
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
|
|
||||||
@@ -8,34 +13,31 @@ import "github.com/authorizerdev/authorizer/server/graph/model"
|
|||||||
type VerificationRequest struct {
|
type VerificationRequest struct {
|
||||||
Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty"` // for arangodb
|
||||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token"` // token is reserved keyword in cassandra
|
Token string `json:"token" bson:"token" cql:"jwt_token"` // token is reserved keyword in cassandra
|
||||||
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier"`
|
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier"`
|
||||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
|
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
|
||||||
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email"`
|
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email"`
|
||||||
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce"`
|
Nonce string `json:"nonce" bson:"nonce" cql:"nonce"`
|
||||||
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
|
RedirectURI string `json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
||||||
token := v.Token
|
id := v.ID
|
||||||
createdAt := v.CreatedAt
|
if strings.Contains(id, Collections.VerificationRequest+"/") {
|
||||||
updatedAt := v.UpdatedAt
|
id = strings.TrimPrefix(id, Collections.VerificationRequest+"/")
|
||||||
email := v.Email
|
}
|
||||||
nonce := v.Nonce
|
|
||||||
redirectURI := v.RedirectURI
|
|
||||||
expires := v.ExpiresAt
|
|
||||||
identifier := v.Identifier
|
|
||||||
return &model.VerificationRequest{
|
return &model.VerificationRequest{
|
||||||
ID: v.ID,
|
ID: id,
|
||||||
Token: &token,
|
Token: refs.NewStringRef(v.Token),
|
||||||
Identifier: &identifier,
|
Identifier: refs.NewStringRef(v.Identifier),
|
||||||
Expires: &expires,
|
Expires: refs.NewInt64Ref(v.ExpiresAt),
|
||||||
Email: &email,
|
Email: refs.NewStringRef(v.Email),
|
||||||
Nonce: &nonce,
|
Nonce: refs.NewStringRef(v.Nonce),
|
||||||
RedirectURI: &redirectURI,
|
RedirectURI: refs.NewStringRef(v.RedirectURI),
|
||||||
CreatedAt: &createdAt,
|
CreatedAt: refs.NewInt64Ref(v.CreatedAt),
|
||||||
UpdatedAt: &updatedAt,
|
UpdatedAt: refs.NewInt64Ref(v.UpdatedAt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
server/db/models/webhook.go
Normal file
44
server/db/models/webhook.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
|
|
||||||
|
// Webhook model for db
|
||||||
|
type Webhook struct {
|
||||||
|
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||||
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
|
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
|
||||||
|
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint"`
|
||||||
|
Headers string `json:"headers" bson:"headers" cql:"headers"`
|
||||||
|
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled"`
|
||||||
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsAPIWebhook to return webhook as graphql response object
|
||||||
|
func (w *Webhook) AsAPIWebhook() *model.Webhook {
|
||||||
|
headersMap := make(map[string]interface{})
|
||||||
|
json.Unmarshal([]byte(w.Headers), &headersMap)
|
||||||
|
|
||||||
|
id := w.ID
|
||||||
|
if strings.Contains(id, Collections.Webhook+"/") {
|
||||||
|
id = strings.TrimPrefix(id, Collections.Webhook+"/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Webhook{
|
||||||
|
ID: id,
|
||||||
|
EventName: refs.NewStringRef(w.EventName),
|
||||||
|
Endpoint: refs.NewStringRef(w.EndPoint),
|
||||||
|
Headers: headersMap,
|
||||||
|
Enabled: refs.NewBoolRef(w.Enabled),
|
||||||
|
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
|
||||||
|
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
|
||||||
|
}
|
||||||
|
}
|
39
server/db/models/webhook_log.go
Normal file
39
server/db/models/webhook_log.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
|
|
||||||
|
// WebhookLog model for db
|
||||||
|
type WebhookLog struct {
|
||||||
|
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||||
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
|
HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status"`
|
||||||
|
Response string `json:"response" bson:"response" cql:"response"`
|
||||||
|
Request string `json:"request" bson:"request" cql:"request"`
|
||||||
|
WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id"`
|
||||||
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsAPIWebhookLog to return webhook log as graphql response object
|
||||||
|
func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog {
|
||||||
|
id := w.ID
|
||||||
|
if strings.Contains(id, Collections.WebhookLog+"/") {
|
||||||
|
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
|
||||||
|
}
|
||||||
|
return &model.WebhookLog{
|
||||||
|
ID: id,
|
||||||
|
HTTPStatus: refs.NewInt64Ref(w.HttpStatus),
|
||||||
|
Response: refs.NewStringRef(w.Response),
|
||||||
|
Request: refs.NewStringRef(w.Request),
|
||||||
|
WebhookID: refs.NewStringRef(w.WebhookID),
|
||||||
|
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
|
||||||
|
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
|
||||||
|
}
|
||||||
|
}
|
152
server/db/providers/arangodb/email_template.go
Normal file
152
server/db/providers/arangodb/email_template.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package arangodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arangodb/go-driver"
|
||||||
|
arangoDriver "github.com/arangodb/go-driver"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
|
||||||
|
_, err := emailTemplateCollection.CreateDocument(ctx, emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
|
||||||
|
meta, err := emailTemplateCollection.UpdateDocument(ctx, emailTemplate.Key, emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = meta.Key
|
||||||
|
emailTemplate.ID = meta.ID.String()
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
emailTemplates := []*model.EmailTemplate{}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.EmailTemplate, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
|
sctx := driver.WithQueryFullCount(ctx)
|
||||||
|
cursor, err := p.db.Query(sctx, query, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = cursor.Statistics().FullCount()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
meta, err := cursor.ReadDocument(ctx, &emailTemplate)
|
||||||
|
|
||||||
|
if arangoDriver.IsNoMoreDocuments(err) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.Key != "" {
|
||||||
|
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: emailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
query := fmt.Sprintf("FOR d in %s FILTER d._key == @email_template_id RETURN d", models.Collections.EmailTemplate)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"email_template_id": emailTemplateID,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if emailTemplate.Key == "" {
|
||||||
|
return nil, fmt.Errorf("email template not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(ctx, &emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.EmailTemplate)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"event_name": eventName,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if emailTemplate.Key == "" {
|
||||||
|
return nil, fmt.Errorf("email template not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(ctx, &emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
eventTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
|
||||||
|
_, err := eventTemplateCollection.RemoveDocument(ctx, emailTemplate.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package arangodb
|
package arangodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,15 +12,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddEnv to save environment information in database
|
// AddEnv to save environment information in database
|
||||||
func (p *provider) AddEnv(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()
|
||||||
env.UpdatedAt = time.Now().Unix()
|
env.UpdatedAt = time.Now().Unix()
|
||||||
configCollection, _ := p.db.Collection(nil, models.Collections.Env)
|
configCollection, _ := p.db.Collection(ctx, models.Collections.Env)
|
||||||
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(nil), env)
|
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env, err
|
return env, err
|
||||||
}
|
}
|
||||||
@@ -29,10 +31,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEnv to update environment information in database
|
// UpdateEnv to update environment information in database
|
||||||
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||||
env.UpdatedAt = time.Now().Unix()
|
env.UpdatedAt = time.Now().Unix()
|
||||||
collection, _ := p.db.Collection(nil, models.Collections.Env)
|
collection, _ := p.db.Collection(ctx, models.Collections.Env)
|
||||||
meta, err := collection.UpdateDocument(nil, env.Key, env)
|
meta, err := collection.UpdateDocument(ctx, env.Key, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env, err
|
return env, err
|
||||||
}
|
}
|
||||||
@@ -43,11 +45,11 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEnv to get environment information from database
|
// GetEnv to get environment information from database
|
||||||
func (p *provider) GetEnv() (models.Env, error) {
|
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
|
||||||
var env models.Env
|
var env models.Env
|
||||||
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env)
|
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env)
|
||||||
|
|
||||||
cursor, err := p.db.Query(nil, query, nil)
|
cursor, err := p.db.Query(ctx, query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env, err
|
return env, err
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@ func (p *provider) GetEnv() (models.Env, error) {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err := cursor.ReadDocument(nil, &env)
|
_, err := cursor.ReadDocument(ctx, &env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env, err
|
return env, err
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
@@ -107,6 +107,61 @@ func NewProvider() (*provider, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webhookCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Webhook)
|
||||||
|
if !webhookCollectionExists {
|
||||||
|
_, err = arangodb.CreateCollection(ctx, models.Collections.Webhook, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookCollection, _ := arangodb.Collection(nil, models.Collections.Webhook)
|
||||||
|
webhookCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
|
||||||
|
Unique: true,
|
||||||
|
Sparse: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
webhookLogCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.WebhookLog)
|
||||||
|
if !webhookLogCollectionExists {
|
||||||
|
_, err = arangodb.CreateCollection(ctx, models.Collections.WebhookLog, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLogCollection, _ := arangodb.Collection(nil, models.Collections.WebhookLog)
|
||||||
|
webhookLogCollection.EnsureHashIndex(ctx, []string{"webhook_id"}, &arangoDriver.EnsureHashIndexOptions{
|
||||||
|
Sparse: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate)
|
||||||
|
if !emailTemplateCollectionExists {
|
||||||
|
_, err = arangodb.CreateCollection(ctx, models.Collections.EmailTemplate, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplateCollection, _ := arangodb.Collection(nil, models.Collections.EmailTemplate)
|
||||||
|
emailTemplateCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
|
||||||
|
Unique: 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
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package arangodb
|
package arangodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -9,31 +9,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddSession to save session information in database
|
// AddSession to save session information in database
|
||||||
func (p *provider) AddSession(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()
|
||||||
session.UpdatedAt = time.Now().Unix()
|
session.UpdatedAt = time.Now().Unix()
|
||||||
sessionCollection, _ := p.db.Collection(nil, models.Collections.Session)
|
sessionCollection, _ := p.db.Collection(ctx, models.Collections.Session)
|
||||||
_, err := sessionCollection.CreateDocument(nil, session)
|
_, err := sessionCollection.CreateDocument(ctx, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
func (p *provider) DeleteSession(userId string) error {
|
|
||||||
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @userId REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
|
|
||||||
bindVars := map[string]interface{}{
|
|
||||||
"userId": userId,
|
|
||||||
}
|
|
||||||
cursor, err := p.db.Query(nil, query, bindVars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cursor.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -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(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 == "" {
|
||||||
@@ -30,8 +34,8 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
|
|
||||||
user.CreatedAt = time.Now().Unix()
|
user.CreatedAt = time.Now().Unix()
|
||||||
user.UpdatedAt = time.Now().Unix()
|
user.UpdatedAt = time.Now().Unix()
|
||||||
userCollection, _ := p.db.Collection(nil, models.Collections.User)
|
userCollection, _ := p.db.Collection(ctx, models.Collections.User)
|
||||||
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(nil), user)
|
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -42,10 +46,10 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser to update user information in database
|
// UpdateUser to update user information in database
|
||||||
func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
|
||||||
user.UpdatedAt = time.Now().Unix()
|
user.UpdatedAt = time.Now().Unix()
|
||||||
collection, _ := p.db.Collection(nil, models.Collections.User)
|
collection, _ := p.db.Collection(ctx, models.Collections.User)
|
||||||
meta, err := collection.UpdateDocument(nil, user.Key, user)
|
meta, err := collection.UpdateDocument(ctx, user.Key, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -56,24 +60,34 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser to delete user information from database
|
// DeleteUser to delete user information from database
|
||||||
func (p *provider) DeleteUser(user models.User) error {
|
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||||
collection, _ := p.db.Collection(nil, models.Collections.User)
|
collection, _ := p.db.Collection(ctx, models.Collections.User)
|
||||||
_, err := collection.RemoveDocument(nil, user.Key)
|
_, err := collection.RemoveDocument(ctx, user.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{}{
|
||||||
|
"user_id": user.Key,
|
||||||
|
}
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
|
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
|
||||||
var users []*model.User
|
var users []*model.User
|
||||||
ctx := driver.WithQueryFullCount(context.Background())
|
sctx := driver.WithQueryFullCount(ctx)
|
||||||
|
|
||||||
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.User, pagination.Offset, pagination.Limit)
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.User, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
cursor, err := p.db.Query(ctx, query, nil)
|
cursor, err := p.db.Query(sctx, query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -84,7 +98,7 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
var user models.User
|
var user models.User
|
||||||
meta, err := cursor.ReadDocument(nil, &user)
|
meta, err := cursor.ReadDocument(ctx, &user)
|
||||||
|
|
||||||
if arangoDriver.IsNoMoreDocuments(err) {
|
if arangoDriver.IsNoMoreDocuments(err) {
|
||||||
break
|
break
|
||||||
@@ -104,7 +118,7 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
func (p *provider) GetUserByEmail(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("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.User)
|
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.User)
|
||||||
@@ -112,7 +126,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
|
|||||||
"email": email,
|
"email": email,
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor, err := p.db.Query(nil, query, bindVars)
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -125,7 +139,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err := cursor.ReadDocument(nil, &user)
|
_, err := cursor.ReadDocument(ctx, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -135,7 +149,7 @@ func (p *provider) GetUserByEmail(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
|
||||||
func (p *provider) GetUserByID(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("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", models.Collections.User)
|
query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", models.Collections.User)
|
||||||
@@ -143,7 +157,7 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
|
|||||||
"id": id,
|
"id": id,
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor, err := p.db.Query(nil, query, bindVars)
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -156,7 +170,7 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err := cursor.ReadDocument(nil, &user)
|
_, err := cursor.ReadDocument(ctx, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -164,3 +178,36 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
|
|||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
@@ -12,15 +12,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddVerification to save verification request in database
|
// AddVerification to save verification request in database
|
||||||
func (p *provider) AddVerificationRequest(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()
|
||||||
verificationRequest.UpdatedAt = time.Now().Unix()
|
verificationRequest.UpdatedAt = time.Now().Unix()
|
||||||
verificationRequestRequestCollection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
|
verificationRequestRequestCollection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest)
|
||||||
meta, err := verificationRequestRequestCollection.CreateDocument(nil, verificationRequest)
|
meta, err := verificationRequestRequestCollection.CreateDocument(ctx, verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -31,14 +32,14 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByToken to get verification request from database using token
|
// GetVerificationRequestByToken to get verification request from database using token
|
||||||
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", models.Collections.VerificationRequest)
|
query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", models.Collections.VerificationRequest)
|
||||||
bindVars := map[string]interface{}{
|
bindVars := map[string]interface{}{
|
||||||
"token": token,
|
"token": token,
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor, err := p.db.Query(nil, query, bindVars)
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err := cursor.ReadDocument(nil, &verificationRequest)
|
_, err := cursor.ReadDocument(ctx, &verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -61,7 +62,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByEmail to get verification request by email from database
|
// GetVerificationRequestByEmail to get verification request by email from database
|
||||||
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
|
|
||||||
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", models.Collections.VerificationRequest)
|
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", models.Collections.VerificationRequest)
|
||||||
@@ -70,7 +71,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor, err := p.db.Query(nil, query, bindVars)
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err := cursor.ReadDocument(nil, &verificationRequest)
|
_, err := cursor.ReadDocument(ctx, &verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -93,12 +94,12 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
|
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||||
var verificationRequests []*model.VerificationRequest
|
var verificationRequests []*model.VerificationRequest
|
||||||
ctx := driver.WithQueryFullCount(context.Background())
|
sctx := driver.WithQueryFullCount(ctx)
|
||||||
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.VerificationRequest, pagination.Offset, pagination.Limit)
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.VerificationRequest, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
cursor, err := p.db.Query(ctx, query, nil)
|
cursor, err := p.db.Query(sctx, query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
meta, err := cursor.ReadDocument(nil, &verificationRequest)
|
meta, err := cursor.ReadDocument(ctx, &verificationRequest)
|
||||||
|
|
||||||
if driver.IsNoMoreDocuments(err) {
|
if driver.IsNoMoreDocuments(err) {
|
||||||
break
|
break
|
||||||
@@ -130,7 +131,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
|
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
|
||||||
collection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
|
collection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
|
||||||
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
|
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
162
server/db/providers/arangodb/webhook.go
Normal file
162
server/db/providers/arangodb/webhook.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package arangodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arangodb/go-driver"
|
||||||
|
arangoDriver "github.com/arangodb/go-driver"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhook to add webhook
|
||||||
|
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
if webhook.ID == "" {
|
||||||
|
webhook.ID = uuid.New().String()
|
||||||
|
webhook.Key = webhook.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.Key = webhook.ID
|
||||||
|
webhook.CreatedAt = time.Now().Unix()
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
|
||||||
|
_, err := webhookCollection.CreateDocument(ctx, webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWebhook to update webhook
|
||||||
|
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
|
||||||
|
meta, err := webhookCollection.UpdateDocument(ctx, webhook.Key, webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.Key = meta.Key
|
||||||
|
webhook.ID = meta.ID.String()
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhooks to list webhook
|
||||||
|
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
|
||||||
|
webhooks := []*model.Webhook{}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.Webhook, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
|
sctx := driver.WithQueryFullCount(ctx)
|
||||||
|
cursor, err := p.db.Query(sctx, query, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = cursor.Statistics().FullCount()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var webhook models.Webhook
|
||||||
|
meta, err := cursor.ReadDocument(ctx, &webhook)
|
||||||
|
|
||||||
|
if arangoDriver.IsNoMoreDocuments(err) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.Key != "" {
|
||||||
|
webhooks = append(webhooks, webhook.AsAPIWebhook())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Webhooks{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
Webhooks: webhooks,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByID to get webhook by id
|
||||||
|
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
||||||
|
var webhook models.Webhook
|
||||||
|
query := fmt.Sprintf("FOR d in %s FILTER d._key == @webhook_id RETURN d", models.Collections.Webhook)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"webhook_id": webhookID,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if webhook.Key == "" {
|
||||||
|
return nil, fmt.Errorf("webhook not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(ctx, &webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByEventName to get webhook by event_name
|
||||||
|
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
||||||
|
var webhook models.Webhook
|
||||||
|
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.Webhook)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"event_name": eventName,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if webhook.Key == "" {
|
||||||
|
return nil, fmt.Errorf("webhook not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(ctx, &webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWebhook to delete webhook
|
||||||
|
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
|
||||||
|
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
|
||||||
|
_, err := webhookCollection.RemoveDocument(ctx, webhook.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("FOR d IN %s FILTER d.webhook_id == @webhook_id REMOVE { _key: d._key } IN %s", models.Collections.WebhookLog, models.Collections.WebhookLog)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"webhook_id": webhook.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
76
server/db/providers/arangodb/webhook_log.go
Normal file
76
server/db/providers/arangodb/webhook_log.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package arangodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arangodb/go-driver"
|
||||||
|
arangoDriver "github.com/arangodb/go-driver"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhookLog to add webhook log
|
||||||
|
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
||||||
|
if webhookLog.ID == "" {
|
||||||
|
webhookLog.ID = uuid.New().String()
|
||||||
|
webhookLog.Key = webhookLog.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLog.Key = webhookLog.ID
|
||||||
|
webhookLog.CreatedAt = time.Now().Unix()
|
||||||
|
webhookLog.UpdatedAt = time.Now().Unix()
|
||||||
|
webhookLogCollection, _ := p.db.Collection(ctx, models.Collections.WebhookLog)
|
||||||
|
_, err := webhookLogCollection.CreateDocument(ctx, webhookLog)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhookLog.AsAPIWebhookLog(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhookLogs to list webhook logs
|
||||||
|
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
|
||||||
|
webhookLogs := []*model.WebhookLog{}
|
||||||
|
bindVariables := map[string]interface{}{}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
|
if webhookID != "" {
|
||||||
|
query = fmt.Sprintf("FOR d in %s FILTER d.webhook_id == @webhook_id SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
|
||||||
|
bindVariables = map[string]interface{}{
|
||||||
|
"webhook_id": webhookID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sctx := driver.WithQueryFullCount(ctx)
|
||||||
|
cursor, err := p.db.Query(sctx, query, bindVariables)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = cursor.Statistics().FullCount()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var webhookLog models.WebhookLog
|
||||||
|
meta, err := cursor.ReadDocument(ctx, &webhookLog)
|
||||||
|
|
||||||
|
if arangoDriver.IsNoMoreDocuments(err) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.Key != "" {
|
||||||
|
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.WebhookLogs{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
WebhookLogs: webhookLogs,
|
||||||
|
}, nil
|
||||||
|
}
|
159
server/db/providers/cassandradb/email_template.go
Normal file
159
server/db/providers/cassandradb/email_template.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package cassandradb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
existingEmailTemplate, _ := p.GetEmailTemplateByEventName(ctx, emailTemplate.EventName)
|
||||||
|
if existingEmailTemplate != nil {
|
||||||
|
return nil, fmt.Errorf("Email template with %s event_name already exists", emailTemplate.EventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
|
||||||
|
decoder.UseNumber()
|
||||||
|
emailTemplateMap := map[string]interface{}{}
|
||||||
|
err = decoder.Decode(&emailTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFields := ""
|
||||||
|
for key, value := range emailTemplateMap {
|
||||||
|
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 := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.EmailTemplate, updateFields, emailTemplate.ID)
|
||||||
|
err = p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
emailTemplates := []*model.EmailTemplate{}
|
||||||
|
paginationClone := pagination
|
||||||
|
|
||||||
|
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.EmailTemplate)
|
||||||
|
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is no offset in cassandra
|
||||||
|
// so we fetch till limit + offset
|
||||||
|
// and return the results from offset to limit
|
||||||
|
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()
|
||||||
|
counter := int64(0)
|
||||||
|
for scanner.Next() {
|
||||||
|
if counter >= pagination.Offset {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
err := scanner.Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: emailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
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.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
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.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID)
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package cassandradb
|
package cassandradb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddEnv to save environment information in database
|
// AddEnv to save environment information in database
|
||||||
func (p *provider) AddEnv(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()
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEnv to update environment information in database
|
// UpdateEnv to update environment information in database
|
||||||
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||||
env.UpdatedAt = time.Now().Unix()
|
env.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID)
|
updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID)
|
||||||
@@ -39,7 +40,7 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEnv to get environment information from database
|
// GetEnv to get environment information from database
|
||||||
func (p *provider) GetEnv() (models.Env, error) {
|
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
|
||||||
var env models.Env
|
var env models.Env
|
||||||
|
|
||||||
query := fmt.Sprintf("SELECT id, env, hash, created_at, updated_at FROM %s LIMIT 1", KeySpace+"."+models.Collections.Env)
|
query := fmt.Sprintf("SELECT id, env, hash, created_at, updated_at FROM %s LIMIT 1", KeySpace+"."+models.Collections.Env)
|
||||||
|
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
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/crypto"
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
@@ -12,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 {
|
||||||
@@ -96,6 +98,9 @@ func NewProvider() (*provider, error) {
|
|||||||
NumRetries: 3,
|
NumRetries: 3,
|
||||||
}
|
}
|
||||||
cassandraClient.Consistency = gocql.LocalQuorum
|
cassandraClient.Consistency = gocql.LocalQuorum
|
||||||
|
cassandraClient.ConnectTimeout = 10 * time.Second
|
||||||
|
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 {
|
||||||
@@ -140,6 +145,11 @@ func NewProvider() (*provider, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
sessionIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_session_user_id ON %s.%s (user_id)", KeySpace, models.Collections.Session)
|
||||||
|
err = session.Query(sessionIndexQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
userCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, email_verified_at bigint, password text, signup_methods text, given_name text, family_name text, middle_name text, nickname text, gender text, birthdate text, phone_number text, phone_number_verified_at bigint, picture text, roles text, updated_at bigint, created_at bigint, revoked_timestamp bigint, PRIMARY KEY (id))", KeySpace, models.Collections.User)
|
userCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, email_verified_at bigint, password text, signup_methods text, given_name text, family_name text, middle_name text, nickname text, gender text, birthdate text, phone_number text, phone_number_verified_at bigint, picture text, roles text, updated_at bigint, created_at bigint, revoked_timestamp bigint, PRIMARY KEY (id))", KeySpace, models.Collections.User)
|
||||||
err = session.Query(userCollectionQuery).Exec()
|
err = session.Query(userCollectionQuery).Exec()
|
||||||
@@ -151,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)
|
||||||
@@ -174,6 +191,57 @@ func NewProvider() (*provider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webhookCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, endpoint text, enabled boolean, headers text, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.Webhook)
|
||||||
|
err = session.Query(webhookCollectionQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webhookIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_webhook_event_name ON %s.%s (event_name)", KeySpace, models.Collections.Webhook)
|
||||||
|
err = session.Query(webhookIndexQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLogCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, http_status bigint, response text, request text, webhook_id text,updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.WebhookLog)
|
||||||
|
err = session.Query(webhookLogCollectionQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webhookLogIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_webhook_log_webhook_id ON %s.%s (webhook_id)", KeySpace, models.Collections.WebhookLog)
|
||||||
|
err = session.Query(webhookLogIndexQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplateCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, template text, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.EmailTemplate)
|
||||||
|
err = session.Query(emailTemplateCollectionQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emailTemplateIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_email_template_event_name ON %s.%s (event_name)", KeySpace, models.Collections.EmailTemplate)
|
||||||
|
err = session.Query(emailTemplateIndexQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
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,
|
||||||
}, err
|
}, err
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package cassandradb
|
package cassandradb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddSession to save session information in database
|
// AddSession to save session information in database
|
||||||
func (p *provider) AddSession(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()
|
||||||
}
|
}
|
||||||
@@ -24,13 +25,3 @@ func (p *provider) AddSession(session models.Session) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
func (p *provider) DeleteSession(userId string) error {
|
|
||||||
deleteSessionQuery := fmt.Sprintf("DELETE FROM %s WHERE user_id = '%s'", KeySpace+"."+models.Collections.Session, userId)
|
|
||||||
err := p.db.Query(deleteSessionQuery).Exec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package cassandradb
|
package cassandradb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -16,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddUser to save user information in database
|
// AddUser to save user information in database
|
||||||
func (p *provider) AddUser(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()
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser to update user information in database
|
// UpdateUser to update user information in database
|
||||||
func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
|
||||||
user.UpdatedAt = time.Now().Unix()
|
user.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
bytes, err := json.Marshal(user)
|
bytes, err := json.Marshal(user)
|
||||||
@@ -97,13 +98,139 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
|||||||
|
|
||||||
updateFields := ""
|
updateFields := ""
|
||||||
for key, value := range userMap {
|
for key, value := range userMap {
|
||||||
if value != nil && key != "_id" {
|
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 := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, updateFields, user.ID)
|
||||||
|
err = p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser to delete user information from database
|
||||||
|
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||||
|
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, user.ID)
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
getSessionsQuery := fmt.Sprintf("SELECT id FROM %s WHERE user_id = '%s' ALLOW FILTERING", KeySpace+"."+models.Collections.Session, user.ID)
|
||||||
|
scanner := p.db.Query(getSessionsQuery).Iter().Scanner()
|
||||||
|
sessionIDs := ""
|
||||||
|
for scanner.Next() {
|
||||||
|
var wlID string
|
||||||
|
err = scanner.Scan(&wlID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sessionIDs += fmt.Sprintf("'%s',", wlID)
|
||||||
|
}
|
||||||
|
sessionIDs = strings.TrimSuffix(sessionIDs, ",")
|
||||||
|
deleteSessionQuery := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", KeySpace+"."+models.Collections.Session, sessionIDs)
|
||||||
|
err = p.db.Query(deleteSessionQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUsers to get list of users from database
|
||||||
|
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
|
||||||
|
responseUsers := []*model.User{}
|
||||||
|
paginationClone := pagination
|
||||||
|
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.User)
|
||||||
|
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is no offset in cassandra
|
||||||
|
// so we fetch till limit + offset
|
||||||
|
// 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, 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()
|
||||||
|
counter := int64(0)
|
||||||
|
for scanner.Next() {
|
||||||
|
if counter >= pagination.Offset {
|
||||||
|
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.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
responseUsers = append(responseUsers, user.AsAPIUser())
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
return &model.Users{
|
||||||
|
Users: responseUsers,
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByEmail to get user information from database using email address
|
||||||
|
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
|
||||||
|
var user models.User
|
||||||
|
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE 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.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByID to get user information from database using user ID
|
||||||
|
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
|
||||||
|
var user models.User
|
||||||
|
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE 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.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUsers to update multiple users, with parameters of user IDs slice
|
||||||
|
// If ids set to nil / empty all the users will be updated
|
||||||
|
func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error {
|
||||||
|
// set updated_at time for all users
|
||||||
|
data["updated_at"] = time.Now().Unix()
|
||||||
|
|
||||||
|
updateFields := ""
|
||||||
|
for key, value := range data {
|
||||||
if key == "_id" {
|
if key == "_id" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == "_key" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if value == nil {
|
if value == nil {
|
||||||
updateFields += fmt.Sprintf("%s = null,", key)
|
updateFields += fmt.Sprintf("%s = null,", key)
|
||||||
continue
|
continue
|
||||||
@@ -119,75 +246,56 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
|||||||
updateFields = strings.Trim(updateFields, " ")
|
updateFields = strings.Trim(updateFields, " ")
|
||||||
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 := ""
|
||||||
|
if ids != nil && len(ids) > 0 {
|
||||||
err = p.db.Query(query).Exec()
|
idsString := ""
|
||||||
if err != nil {
|
for _, id := range ids {
|
||||||
return user, err
|
idsString += fmt.Sprintf("'%s', ", id)
|
||||||
}
|
}
|
||||||
|
idsString = strings.Trim(idsString, " ")
|
||||||
return user, nil
|
idsString = strings.TrimSuffix(idsString, ",")
|
||||||
}
|
query = fmt.Sprintf("UPDATE %s SET %s WHERE id IN (%s)", KeySpace+"."+models.Collections.User, updateFields, idsString)
|
||||||
|
|
||||||
// DeleteUser to delete user information from database
|
|
||||||
func (p *provider) DeleteUser(user models.User) error {
|
|
||||||
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, user.ID)
|
|
||||||
err := p.db.Query(query).Exec()
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
|
||||||
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
|
|
||||||
responseUsers := []*model.User{}
|
|
||||||
paginationClone := pagination
|
|
||||||
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.User)
|
|
||||||
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// there is no offset in cassandra
|
// get all ids
|
||||||
// so we fetch till limit + offset
|
getUserIDsQuery := fmt.Sprintf(`SELECT id FROM %s`, KeySpace+"."+models.Collections.User)
|
||||||
// and return the results from offset to limit
|
scanner := p.db.Query(getUserIDsQuery).Iter().Scanner()
|
||||||
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)
|
// only 100 ids are allowed in 1 query
|
||||||
|
// hence we need create multiple update queries
|
||||||
scanner := p.db.Query(query).Iter().Scanner()
|
idsString := ""
|
||||||
counter := int64(0)
|
idsStringArray := []string{idsString}
|
||||||
|
counter := 1
|
||||||
for scanner.Next() {
|
for scanner.Next() {
|
||||||
if counter >= pagination.Offset {
|
var id string
|
||||||
var user models.User
|
err := scanner.Scan(&id)
|
||||||
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)
|
if err == nil {
|
||||||
if err != nil {
|
idsString += fmt.Sprintf("'%s', ", id)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
responseUsers = append(responseUsers, user.AsAPIUser())
|
|
||||||
}
|
}
|
||||||
counter++
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &model.Users{
|
|
||||||
Users: responseUsers,
|
|
||||||
Pagination: &paginationClone,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserByEmail to get user information from database using email address
|
for _, idStr := range idsStringArray {
|
||||||
func (p *provider) GetUserByEmail(email string) (models.User, error) {
|
idStr = strings.Trim(idStr, " ")
|
||||||
var user models.User
|
idStr = strings.TrimSuffix(idStr, ",")
|
||||||
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", KeySpace+"."+models.Collections.User, email)
|
query = fmt.Sprintf("UPDATE %s SET %s WHERE id IN (%s)", KeySpace+"."+models.Collections.User, updateFields, idStr)
|
||||||
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).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserByID to get user information from database using user ID
|
|
||||||
func (p *provider) GetUserByID(id string) (models.User, error) {
|
|
||||||
var user models.User
|
|
||||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, 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)
|
|
||||||
if err != nil {
|
|
||||||
return user, err
|
|
||||||
}
|
}
|
||||||
return user, nil
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package cassandradb
|
package cassandradb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddVerification to save verification request in database
|
// AddVerification to save verification request in database
|
||||||
func (p *provider) AddVerificationRequest(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()
|
||||||
}
|
}
|
||||||
@@ -28,7 +29,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByToken to get verification request from database using token
|
// GetVerificationRequestByToken to get verification request from database using token
|
||||||
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE jwt_token = '%s' LIMIT 1`, KeySpace+"."+models.Collections.VerificationRequest, token)
|
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE jwt_token = '%s' LIMIT 1`, KeySpace+"."+models.Collections.VerificationRequest, token)
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByEmail to get verification request by email from database
|
// GetVerificationRequestByEmail to get verification request by email from database
|
||||||
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE email = '%s' AND identifier = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.VerificationRequest, email, identifier)
|
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE email = '%s' AND identifier = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.VerificationRequest, email, identifier)
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
|
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||||
var verificationRequests []*model.VerificationRequest
|
var verificationRequests []*model.VerificationRequest
|
||||||
|
|
||||||
paginationClone := pagination
|
paginationClone := pagination
|
||||||
@@ -89,7 +90,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
|
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
|
||||||
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.VerificationRequest, verificationRequest.ID)
|
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.VerificationRequest, verificationRequest.ID)
|
||||||
err := p.db.Query(query).Exec()
|
err := p.db.Query(query).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
172
server/db/providers/cassandradb/webhook.go
Normal file
172
server/db/providers/cassandradb/webhook.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package cassandradb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhook to add webhook
|
||||||
|
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
if webhook.ID == "" {
|
||||||
|
webhook.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.Key = webhook.ID
|
||||||
|
webhook.CreatedAt = time.Now().Unix()
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
existingHook, _ := p.GetWebhookByEventName(ctx, webhook.EventName)
|
||||||
|
if existingHook != nil {
|
||||||
|
return nil, fmt.Errorf("Webhook with %s event_name already exists", webhook.EventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
|
||||||
|
err := p.db.Query(insertQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWebhook to update webhook
|
||||||
|
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
|
||||||
|
decoder.UseNumber()
|
||||||
|
webhookMap := map[string]interface{}{}
|
||||||
|
err = decoder.Decode(&webhookMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFields := ""
|
||||||
|
for key, value := range webhookMap {
|
||||||
|
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 := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, updateFields, webhook.ID)
|
||||||
|
err = p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhooks to list webhook
|
||||||
|
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
|
||||||
|
webhooks := []*model.Webhook{}
|
||||||
|
paginationClone := pagination
|
||||||
|
|
||||||
|
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.Webhook)
|
||||||
|
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is no offset in cassandra
|
||||||
|
// so we fetch till limit + offset
|
||||||
|
// and return the results from offset to limit
|
||||||
|
query := fmt.Sprintf("SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.Webhook, pagination.Limit+pagination.Offset)
|
||||||
|
|
||||||
|
scanner := p.db.Query(query).Iter().Scanner()
|
||||||
|
counter := int64(0)
|
||||||
|
for scanner.Next() {
|
||||||
|
if counter >= pagination.Offset {
|
||||||
|
var webhook models.Webhook
|
||||||
|
err := scanner.Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webhooks = append(webhooks, webhook.AsAPIWebhook())
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Webhooks{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
Webhooks: webhooks,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByID to get webhook by id
|
||||||
|
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
||||||
|
var webhook models.Webhook
|
||||||
|
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.Webhook, webhookID)
|
||||||
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByEventName to get webhook by event_name
|
||||||
|
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
||||||
|
var webhook models.Webhook
|
||||||
|
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName)
|
||||||
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWebhook to delete webhook
|
||||||
|
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
|
||||||
|
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, webhook.ID)
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
getWebhookLogQuery := fmt.Sprintf("SELECT id FROM %s WHERE webhook_id = '%s' ALLOW FILTERING", KeySpace+"."+models.Collections.WebhookLog, webhook.ID)
|
||||||
|
scanner := p.db.Query(getWebhookLogQuery).Iter().Scanner()
|
||||||
|
webhookLogIDs := ""
|
||||||
|
for scanner.Next() {
|
||||||
|
var wlID string
|
||||||
|
err = scanner.Scan(&wlID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
webhookLogIDs += fmt.Sprintf("'%s',", wlID)
|
||||||
|
}
|
||||||
|
webhookLogIDs = strings.TrimSuffix(webhookLogIDs, ",")
|
||||||
|
query = fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", KeySpace+"."+models.Collections.WebhookLog, webhookLogIDs)
|
||||||
|
err = p.db.Query(query).Exec()
|
||||||
|
return err
|
||||||
|
}
|
70
server/db/providers/cassandradb/webhook_log.go
Normal file
70
server/db/providers/cassandradb/webhook_log.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package cassandradb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhookLog to add webhook log
|
||||||
|
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
||||||
|
if webhookLog.ID == "" {
|
||||||
|
webhookLog.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLog.Key = webhookLog.ID
|
||||||
|
webhookLog.CreatedAt = time.Now().Unix()
|
||||||
|
webhookLog.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
insertQuery := fmt.Sprintf("INSERT INTO %s (id, http_status, response, request, webhook_id, created_at, updated_at) VALUES ('%s', %d,'%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.WebhookLog, webhookLog.ID, webhookLog.HttpStatus, webhookLog.Response, webhookLog.Request, webhookLog.WebhookID, webhookLog.CreatedAt, webhookLog.UpdatedAt)
|
||||||
|
err := p.db.Query(insertQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhookLog.AsAPIWebhookLog(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhookLogs to list webhook logs
|
||||||
|
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
|
||||||
|
webhookLogs := []*model.WebhookLog{}
|
||||||
|
paginationClone := pagination
|
||||||
|
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.WebhookLog)
|
||||||
|
// there is no offset in cassandra
|
||||||
|
// so we fetch till limit + offset
|
||||||
|
// and return the results from offset to limit
|
||||||
|
query := fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.WebhookLog, pagination.Limit+pagination.Offset)
|
||||||
|
|
||||||
|
if webhookID != "" {
|
||||||
|
totalCountQuery = fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE webhook_id='%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.WebhookLog, webhookID)
|
||||||
|
query = fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s WHERE webhook_id = '%s' LIMIT %d ALLOW FILTERING", KeySpace+"."+models.Collections.WebhookLog, webhookID, pagination.Limit+pagination.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := p.db.Query(query).Iter().Scanner()
|
||||||
|
counter := int64(0)
|
||||||
|
for scanner.Next() {
|
||||||
|
if counter >= pagination.Offset {
|
||||||
|
var webhookLog models.WebhookLog
|
||||||
|
err := scanner.Scan(&webhookLog.ID, &webhookLog.HttpStatus, &webhookLog.Response, &webhookLog.Request, &webhookLog.WebhookID, &webhookLog.CreatedAt, &webhookLog.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.WebhookLogs{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
WebhookLogs: webhookLogs,
|
||||||
|
}, nil
|
||||||
|
}
|
115
server/db/providers/mongodb/email_template.go
Normal file
115
server/db/providers/mongodb/email_template.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
_, err := emailTemplateCollection.InsertOne(ctx, emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
_, err := emailTemplateCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": emailTemplate.ID}}, bson.M{"$set": emailTemplate}, options.MergeUpdateOptions())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
var emailTemplates []*model.EmailTemplate
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetLimit(pagination.Limit)
|
||||||
|
opts.SetSkip(pagination.Offset)
|
||||||
|
opts.SetSort(bson.M{"created_at": -1})
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
count, err := emailTemplateCollection.CountDocuments(ctx, bson.M{}, options.Count())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone.Total = count
|
||||||
|
|
||||||
|
cursor, err := emailTemplateCollection.Find(ctx, bson.M{}, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
for cursor.Next(ctx) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
err := cursor.Decode(&emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: emailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
err := emailTemplateCollection.FindOne(ctx, bson.M{"_id": emailTemplateID}).Decode(&emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
err := emailTemplateCollection.FindOne(ctx, bson.M{"event_name": eventName}).Decode(&emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
_, err := emailTemplateCollection.DeleteOne(nil, bson.M{"_id": emailTemplate.ID}, options.Delete())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package mongodb
|
package mongodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddEnv to save environment information in database
|
// AddEnv to save environment information in database
|
||||||
func (p *provider) AddEnv(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()
|
||||||
}
|
}
|
||||||
@@ -20,7 +21,7 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
|
|||||||
env.UpdatedAt = time.Now().Unix()
|
env.UpdatedAt = time.Now().Unix()
|
||||||
env.Key = env.ID
|
env.Key = env.ID
|
||||||
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
|
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
|
||||||
_, err := configCollection.InsertOne(nil, env)
|
_, err := configCollection.InsertOne(ctx, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env, err
|
return env, err
|
||||||
}
|
}
|
||||||
@@ -28,10 +29,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEnv to update environment information in database
|
// UpdateEnv to update environment information in database
|
||||||
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||||
env.UpdatedAt = time.Now().Unix()
|
env.UpdatedAt = time.Now().Unix()
|
||||||
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
|
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
|
||||||
_, err := configCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions())
|
_, err := configCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env, err
|
return env, err
|
||||||
}
|
}
|
||||||
@@ -39,14 +40,14 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEnv to get environment information from database
|
// GetEnv to get environment information from database
|
||||||
func (p *provider) GetEnv() (models.Env, error) {
|
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
|
||||||
var env models.Env
|
var env models.Env
|
||||||
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
|
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
|
||||||
cursor, err := configCollection.Find(nil, bson.M{}, options.Find())
|
cursor, err := configCollection.Find(ctx, bson.M{}, options.Find())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env, err
|
return env, err
|
||||||
}
|
}
|
||||||
defer cursor.Close(nil)
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
for cursor.Next(nil) {
|
for cursor.Next(nil) {
|
||||||
err := cursor.Decode(&env)
|
err := cursor.Decode(&env)
|
||||||
|
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
|
||||||
|
}
|
@@ -83,6 +83,42 @@ func NewProvider() (*provider, error) {
|
|||||||
|
|
||||||
mongodb.CreateCollection(ctx, models.Collections.Env, options.CreateCollection())
|
mongodb.CreateCollection(ctx, models.Collections.Env, options.CreateCollection())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.Webhook, options.CreateCollection())
|
||||||
|
webhookCollection := mongodb.Collection(models.Collections.Webhook, options.Collection())
|
||||||
|
webhookCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
{
|
||||||
|
Keys: bson.M{"event_name": 1},
|
||||||
|
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.WebhookLog, options.CreateCollection())
|
||||||
|
webhookLogCollection := mongodb.Collection(models.Collections.WebhookLog, options.Collection())
|
||||||
|
webhookLogCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
{
|
||||||
|
Keys: bson.M{"webhook_id": 1},
|
||||||
|
Options: options.Index().SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.EmailTemplate, options.CreateCollection())
|
||||||
|
emailTemplateCollection := mongodb.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
emailTemplateCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
{
|
||||||
|
Keys: bson.M{"event_name": 1},
|
||||||
|
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||||
|
},
|
||||||
|
}, 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
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
package mongodb
|
package mongodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddSession to save session information in database
|
// AddSession to save session information in database
|
||||||
func (p *provider) AddSession(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()
|
||||||
}
|
}
|
||||||
@@ -19,17 +19,7 @@ func (p *provider) AddSession(session models.Session) error {
|
|||||||
session.CreatedAt = time.Now().Unix()
|
session.CreatedAt = time.Now().Unix()
|
||||||
session.UpdatedAt = time.Now().Unix()
|
session.UpdatedAt = time.Now().Unix()
|
||||||
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
|
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
|
||||||
_, err := sessionCollection.InsertOne(nil, session)
|
_, err := sessionCollection.InsertOne(ctx, session)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
func (p *provider) DeleteSession(userId string) error {
|
|
||||||
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
|
|
||||||
_, err := sessionCollection.DeleteMany(nil, bson.M{"user_id": userId}, options.Delete())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package mongodb
|
package mongodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
@@ -8,12 +9,14 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddUser to save user information in database
|
// AddUser to save user information in database
|
||||||
func (p *provider) AddUser(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()
|
||||||
}
|
}
|
||||||
@@ -29,7 +32,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
user.UpdatedAt = time.Now().Unix()
|
user.UpdatedAt = time.Now().Unix()
|
||||||
user.Key = user.ID
|
user.Key = user.ID
|
||||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
_, err := userCollection.InsertOne(nil, user)
|
_, err := userCollection.InsertOne(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -38,10 +41,10 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser to update user information in database
|
// UpdateUser to update user information in database
|
||||||
func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
|
||||||
user.UpdatedAt = time.Now().Unix()
|
user.UpdatedAt = time.Now().Unix()
|
||||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
_, err := userCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
|
_, err := userCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -49,9 +52,15 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser to delete user information from database
|
// DeleteUser to delete user information from database
|
||||||
func (p *provider) DeleteUser(user models.User) error {
|
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
_, err := userCollection.DeleteOne(nil, bson.M{"_id": user.ID}, options.Delete())
|
_, err := userCollection.DeleteOne(ctx, bson.M{"_id": user.ID}, options.Delete())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
|
||||||
|
_, err = sessionCollection.DeleteMany(ctx, bson.M{"user_id": user.ID}, options.Delete())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -60,7 +69,7 @@ func (p *provider) DeleteUser(user models.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
|
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
|
||||||
var users []*model.User
|
var users []*model.User
|
||||||
opts := options.Find()
|
opts := options.Find()
|
||||||
opts.SetLimit(pagination.Limit)
|
opts.SetLimit(pagination.Limit)
|
||||||
@@ -70,20 +79,20 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
|
|||||||
paginationClone := pagination
|
paginationClone := pagination
|
||||||
|
|
||||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count())
|
count, err := userCollection.CountDocuments(ctx, bson.M{}, options.Count())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationClone.Total = count
|
paginationClone.Total = count
|
||||||
|
|
||||||
cursor, err := userCollection.Find(nil, bson.M{}, opts)
|
cursor, err := userCollection.Find(ctx, bson.M{}, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cursor.Close(nil)
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
for cursor.Next(nil) {
|
for cursor.Next(ctx) {
|
||||||
var user models.User
|
var user models.User
|
||||||
err := cursor.Decode(&user)
|
err := cursor.Decode(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -99,10 +108,10 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
func (p *provider) GetUserByEmail(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
|
||||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
err := userCollection.FindOne(nil, bson.M{"email": email}).Decode(&user)
|
err := userCollection.FindOne(ctx, bson.M{"email": email}).Decode(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@@ -111,14 +120,38 @@ func (p *provider) GetUserByEmail(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
|
||||||
func (p *provider) GetUserByID(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
|
||||||
|
|
||||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
err := userCollection.FindOne(nil, bson.M{"_id": id}).Decode(&user)
|
err := userCollection.FindOne(ctx, bson.M{"_id": id}).Decode(&user)
|
||||||
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()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package mongodb
|
package mongodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddVerification to save verification request in database
|
// AddVerification to save verification request in database
|
||||||
func (p *provider) AddVerificationRequest(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()
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
|||||||
verificationRequest.UpdatedAt = time.Now().Unix()
|
verificationRequest.UpdatedAt = time.Now().Unix()
|
||||||
verificationRequest.Key = verificationRequest.ID
|
verificationRequest.Key = verificationRequest.ID
|
||||||
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
||||||
_, err := verificationRequestCollection.InsertOne(nil, verificationRequest)
|
_, err := verificationRequestCollection.InsertOne(ctx, verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -29,11 +30,11 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByToken to get verification request from database using token
|
// GetVerificationRequestByToken to get verification request from database using token
|
||||||
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
|
|
||||||
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
||||||
err := verificationRequestCollection.FindOne(nil, bson.M{"token": token}).Decode(&verificationRequest)
|
err := verificationRequestCollection.FindOne(ctx, bson.M{"token": token}).Decode(&verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -42,11 +43,11 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByEmail to get verification request by email from database
|
// GetVerificationRequestByEmail to get verification request by email from database
|
||||||
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
|
|
||||||
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
||||||
err := verificationRequestCollection.FindOne(nil, bson.M{"email": email, "identifier": identifier}).Decode(&verificationRequest)
|
err := verificationRequestCollection.FindOne(ctx, bson.M{"email": email, "identifier": identifier}).Decode(&verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequest, err
|
return verificationRequest, err
|
||||||
}
|
}
|
||||||
@@ -55,7 +56,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
|
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||||
var verificationRequests []*model.VerificationRequest
|
var verificationRequests []*model.VerificationRequest
|
||||||
|
|
||||||
opts := options.Find()
|
opts := options.Find()
|
||||||
@@ -65,17 +66,17 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
|
|||||||
|
|
||||||
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
||||||
|
|
||||||
verificationRequestCollectionCount, err := verificationRequestCollection.CountDocuments(nil, bson.M{})
|
verificationRequestCollectionCount, err := verificationRequestCollection.CountDocuments(ctx, bson.M{})
|
||||||
paginationClone := pagination
|
paginationClone := pagination
|
||||||
paginationClone.Total = verificationRequestCollectionCount
|
paginationClone.Total = verificationRequestCollectionCount
|
||||||
|
|
||||||
cursor, err := verificationRequestCollection.Find(nil, bson.M{}, opts)
|
cursor, err := verificationRequestCollection.Find(ctx, bson.M{}, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cursor.Close(nil)
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
for cursor.Next(nil) {
|
for cursor.Next(ctx) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
err := cursor.Decode(&verificationRequest)
|
err := cursor.Decode(&verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -91,9 +92,9 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
|
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
|
||||||
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
||||||
_, err := verificationRequestCollection.DeleteOne(nil, bson.M{"_id": verificationRequest.ID}, options.Delete())
|
_, err := verificationRequestCollection.DeleteOne(ctx, bson.M{"_id": verificationRequest.ID}, options.Delete())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
120
server/db/providers/mongodb/webhook.go
Normal file
120
server/db/providers/mongodb/webhook.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhook to add webhook
|
||||||
|
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
if webhook.ID == "" {
|
||||||
|
webhook.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.Key = webhook.ID
|
||||||
|
webhook.CreatedAt = time.Now().Unix()
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
|
||||||
|
_, err := webhookCollection.InsertOne(ctx, webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWebhook to update webhook
|
||||||
|
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
|
||||||
|
_, err := webhookCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": webhook.ID}}, bson.M{"$set": webhook}, options.MergeUpdateOptions())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhooks to list webhook
|
||||||
|
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
|
||||||
|
var webhooks []*model.Webhook
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetLimit(pagination.Limit)
|
||||||
|
opts.SetSkip(pagination.Offset)
|
||||||
|
opts.SetSort(bson.M{"created_at": -1})
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
|
||||||
|
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
|
||||||
|
count, err := webhookCollection.CountDocuments(ctx, bson.M{}, options.Count())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone.Total = count
|
||||||
|
|
||||||
|
cursor, err := webhookCollection.Find(ctx, bson.M{}, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
for cursor.Next(ctx) {
|
||||||
|
var webhook models.Webhook
|
||||||
|
err := cursor.Decode(&webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webhooks = append(webhooks, webhook.AsAPIWebhook())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Webhooks{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
Webhooks: webhooks,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByID to get webhook by id
|
||||||
|
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
||||||
|
var webhook models.Webhook
|
||||||
|
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
|
||||||
|
err := webhookCollection.FindOne(ctx, bson.M{"_id": webhookID}).Decode(&webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByEventName to get webhook by event_name
|
||||||
|
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
||||||
|
var webhook models.Webhook
|
||||||
|
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
|
||||||
|
err := webhookCollection.FindOne(ctx, bson.M{"event_name": eventName}).Decode(&webhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWebhook to delete webhook
|
||||||
|
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
|
||||||
|
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
|
||||||
|
_, err := webhookCollection.DeleteOne(nil, bson.M{"_id": webhook.ID}, options.Delete())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
|
||||||
|
_, err = webhookLogCollection.DeleteMany(nil, bson.M{"webhook_id": webhook.ID}, options.Delete())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
74
server/db/providers/mongodb/webhook_log.go
Normal file
74
server/db/providers/mongodb/webhook_log.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhookLog to add webhook log
|
||||||
|
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
||||||
|
if webhookLog.ID == "" {
|
||||||
|
webhookLog.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLog.Key = webhookLog.ID
|
||||||
|
webhookLog.CreatedAt = time.Now().Unix()
|
||||||
|
webhookLog.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
|
||||||
|
_, err := webhookLogCollection.InsertOne(ctx, webhookLog)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webhookLog.AsAPIWebhookLog(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhookLogs to list webhook logs
|
||||||
|
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
|
||||||
|
webhookLogs := []*model.WebhookLog{}
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetLimit(pagination.Limit)
|
||||||
|
opts.SetSkip(pagination.Offset)
|
||||||
|
opts.SetSort(bson.M{"created_at": -1})
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
query := bson.M{}
|
||||||
|
|
||||||
|
if webhookID != "" {
|
||||||
|
query = bson.M{"webhook_id": webhookID}
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
|
||||||
|
count, err := webhookLogCollection.CountDocuments(ctx, query, options.Count())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone.Total = count
|
||||||
|
|
||||||
|
cursor, err := webhookLogCollection.Find(ctx, query, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
for cursor.Next(ctx) {
|
||||||
|
var webhookLog models.WebhookLog
|
||||||
|
err := cursor.Decode(&webhookLog)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.WebhookLogs{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
WebhookLogs: webhookLogs,
|
||||||
|
}, nil
|
||||||
|
}
|
48
server/db/providers/provider_template/email_template.go
Normal file
48
server/db/providers/provider_template/email_template.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package provider_template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package provider_template
|
package provider_template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddEnv to save environment information in database
|
// AddEnv to save environment information in database
|
||||||
func (p *provider) AddEnv(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()
|
||||||
}
|
}
|
||||||
@@ -19,13 +20,13 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEnv to update environment information in database
|
// UpdateEnv to update environment information in database
|
||||||
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||||
env.UpdatedAt = time.Now().Unix()
|
env.UpdatedAt = time.Now().Unix()
|
||||||
return env, nil
|
return env, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEnv to get environment information from database
|
// GetEnv to get environment information from database
|
||||||
func (p *provider) GetEnv() (models.Env, error) {
|
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
|
||||||
var env models.Env
|
var env models.Env
|
||||||
|
|
||||||
return env, nil
|
return env, 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
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package provider_template
|
package provider_template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddSession to save session information in database
|
// AddSession to save session information in database
|
||||||
func (p *provider) AddSession(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()
|
||||||
}
|
}
|
||||||
@@ -19,6 +20,6 @@ func (p *provider) AddSession(session models.Session) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
// DeleteSession to delete session information from database
|
||||||
func (p *provider) DeleteSession(userId string) error {
|
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package provider_template
|
package provider_template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddUser to save user information in database
|
// AddUser to save user information in database
|
||||||
func (p *provider) AddUser(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()
|
||||||
}
|
}
|
||||||
@@ -31,31 +32,40 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser to update user information in database
|
// UpdateUser to update user information in database
|
||||||
func (p *provider) UpdateUser(user models.User) (models.User, error) {
|
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
|
||||||
user.UpdatedAt = time.Now().Unix()
|
user.UpdatedAt = time.Now().Unix()
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser to delete user information from database
|
// DeleteUser to delete user information from database
|
||||||
func (p *provider) DeleteUser(user models.User) error {
|
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
|
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
func (p *provider) GetUserByEmail(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
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByID to get user information from database using user ID
|
// GetUserByID to get user information from database using user ID
|
||||||
func (p *provider) GetUserByID(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
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package provider_template
|
package provider_template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddVerification to save verification request in database
|
// AddVerification to save verification request in database
|
||||||
func (p *provider) AddVerificationRequest(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()
|
||||||
}
|
}
|
||||||
@@ -21,25 +22,25 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByToken to get verification request from database using token
|
// GetVerificationRequestByToken to get verification request from database using token
|
||||||
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
|
|
||||||
return verificationRequest, nil
|
return verificationRequest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVerificationRequestByEmail to get verification request by email from database
|
// GetVerificationRequestByEmail to get verification request by email from database
|
||||||
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
|
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
|
|
||||||
return verificationRequest, nil
|
return verificationRequest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
|
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
|
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
49
server/db/providers/provider_template/webhook.go
Normal file
49
server/db/providers/provider_template/webhook.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package provider_template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhook to add webhook
|
||||||
|
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
if webhook.ID == "" {
|
||||||
|
webhook.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.Key = webhook.ID
|
||||||
|
webhook.CreatedAt = time.Now().Unix()
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWebhook to update webhook
|
||||||
|
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
|
||||||
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
return webhook.AsAPIWebhook(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhooks to list webhook
|
||||||
|
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByID to get webhook by id
|
||||||
|
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebhookByEventName to get webhook by event_name
|
||||||
|
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWebhook to delete webhook
|
||||||
|
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
|
||||||
|
// Also delete webhook logs for given webhook id
|
||||||
|
return nil
|
||||||
|
}
|
27
server/db/providers/provider_template/webhook_log.go
Normal file
27
server/db/providers/provider_template/webhook_log.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package provider_template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWebhookLog to add webhook log
|
||||||
|
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
|
||||||
|
if webhookLog.ID == "" {
|
||||||
|
webhookLog.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLog.Key = webhookLog.ID
|
||||||
|
webhookLog.CreatedAt = time.Now().Unix()
|
||||||
|
webhookLog.UpdatedAt = time.Now().Unix()
|
||||||
|
return webhookLog.AsAPIWebhookLog(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWebhookLogs to list webhook logs
|
||||||
|
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
@@ -1,44 +1,85 @@
|
|||||||
package providers
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// AddUser to save user information in database
|
// AddUser to save user information in database
|
||||||
AddUser(user models.User) (models.User, error)
|
AddUser(ctx context.Context, user models.User) (models.User, error)
|
||||||
// UpdateUser to update user information in database
|
// UpdateUser to update user information in database
|
||||||
UpdateUser(user models.User) (models.User, error)
|
UpdateUser(ctx context.Context, user models.User) (models.User, error)
|
||||||
// DeleteUser to delete user information from database
|
// DeleteUser to delete user information from database
|
||||||
DeleteUser(user models.User) error
|
DeleteUser(ctx context.Context, user models.User) error
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
ListUsers(pagination model.Pagination) (*model.Users, error)
|
ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error)
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
GetUserByEmail(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(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(verificationRequest models.VerificationRequest) (models.VerificationRequest, error)
|
AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error)
|
||||||
// GetVerificationRequestByToken to get verification request from database using token
|
// GetVerificationRequestByToken to get verification request from database using token
|
||||||
GetVerificationRequestByToken(token string) (models.VerificationRequest, error)
|
GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error)
|
||||||
// GetVerificationRequestByEmail to get verification request by email from database
|
// GetVerificationRequestByEmail to get verification request by email from database
|
||||||
GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error)
|
GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error)
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error)
|
ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error)
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
DeleteVerificationRequest(verificationRequest models.VerificationRequest) error
|
DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error
|
||||||
|
|
||||||
// AddSession to save session information in database
|
// AddSession to save session information in database
|
||||||
AddSession(session models.Session) error
|
AddSession(ctx context.Context, session models.Session) error
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
DeleteSession(userId string) error
|
|
||||||
|
|
||||||
// AddEnv to save environment information in database
|
// AddEnv to save environment information in database
|
||||||
AddEnv(env models.Env) (models.Env, error)
|
AddEnv(ctx context.Context, env models.Env) (models.Env, error)
|
||||||
// UpdateEnv to update environment information in database
|
// UpdateEnv to update environment information in database
|
||||||
UpdateEnv(env models.Env) (models.Env, error)
|
UpdateEnv(ctx context.Context, env models.Env) (models.Env, error)
|
||||||
// GetEnv to get environment information from database
|
// GetEnv to get environment information from database
|
||||||
GetEnv() (models.Env, error)
|
GetEnv(ctx context.Context) (models.Env, error)
|
||||||
|
|
||||||
|
// AddWebhook to add webhook
|
||||||
|
AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error)
|
||||||
|
// UpdateWebhook to update webhook
|
||||||
|
UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error)
|
||||||
|
// ListWebhooks to list webhook
|
||||||
|
ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error)
|
||||||
|
// GetWebhookByID to get webhook by id
|
||||||
|
GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error)
|
||||||
|
// GetWebhookByEventName to get webhook by event_name
|
||||||
|
GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error)
|
||||||
|
// DeleteWebhook to delete webhook
|
||||||
|
DeleteWebhook(ctx context.Context, webhook *model.Webhook) error
|
||||||
|
|
||||||
|
// AddWebhookLog to add webhook log
|
||||||
|
AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error)
|
||||||
|
// ListWebhookLogs to list webhook logs
|
||||||
|
ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error)
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error)
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error)
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error)
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error)
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
100
server/db/providers/sql/email_template.go
Normal file
100
server/db/providers/sql/email_template.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
res := p.db.Create(&emailTemplate)
|
||||||
|
if res.Error != nil {
|
||||||
|
return nil, res.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
res := p.db.Save(&emailTemplate)
|
||||||
|
if res.Error != nil {
|
||||||
|
return nil, res.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
var emailTemplates []models.EmailTemplate
|
||||||
|
|
||||||
|
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&emailTemplates)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
totalRes := p.db.Model(&models.EmailTemplate{}).Count(&total)
|
||||||
|
if totalRes.Error != nil {
|
||||||
|
return nil, totalRes.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = total
|
||||||
|
|
||||||
|
responseEmailTemplates := []*model.EmailTemplate{}
|
||||||
|
for _, w := range emailTemplates {
|
||||||
|
responseEmailTemplates = append(responseEmailTemplates, w.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: responseEmailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
|
||||||
|
result := p.db.Where("id = ?", emailTemplateID).First(&emailTemplate)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
|
||||||
|
result := p.db.Where("event_name = ?", eventName).First(&emailTemplate)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
result := p.db.Delete(&models.EmailTemplate{
|
||||||
|
ID: emailTemplate.ID,
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package sql
|
package sql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AddEnv to save environment information in database
|
// AddEnv to save environment information in database
|
||||||
func (p *provider) AddEnv(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()
|
||||||
}
|
}
|
||||||
@@ -25,7 +26,7 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEnv to update environment information in database
|
// UpdateEnv to update environment information in database
|
||||||
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
|
||||||
env.UpdatedAt = time.Now().Unix()
|
env.UpdatedAt = time.Now().Unix()
|
||||||
result := p.db.Save(&env)
|
result := p.db.Save(&env)
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEnv to get environment information from database
|
// GetEnv to get environment information from database
|
||||||
func (p *provider) GetEnv() (models.Env, error) {
|
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
|
||||||
var env models.Env
|
var env models.Env
|
||||||
result := p.db.First(&env)
|
result := p.db.First(&env)
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user