Compare commits

...

140 Commits

Author SHA1 Message Date
anik-ghosh-au7
85630a59c1 feat: add webhook payload example 2022-08-02 00:56:21 +05:30
anik-ghosh-au7
b4ef196bfb fix: update email template variables 2022-08-01 14:07:06 +05:30
anik-ghosh-au7
099b2a39b4 feat: add delete email template modal 2022-07-30 22:47:00 +05:30
anik-ghosh-au7
2d07baedf4 feat: fix update email template editor 2022-07-30 20:28:36 +05:30
anik-ghosh-au7
8b34e001ef feat: fix update email template editor 2022-07-30 20:15:49 +05:30
anik-ghosh-au7
617dcdde53 feat: fix update email template modal 2022-07-30 18:43:02 +05:30
anik-ghosh-au7
f2fb800323 feat: dashboard add email-template page 2022-07-30 16:05:35 +05:30
Lakhan Samani
db4d711cba feat: add subject to email template 2022-07-29 16:15:57 +05:30
Lakhan Samani
9ba1239c11 Merge pull request #204 from anik-ghosh-au7/main
fix: collections names
2022-07-23 15:46:35 +05:30
anik-ghosh-au7
ed7ed73980 fix: collections names 2022-07-23 15:44:56 +05:30
Lakhan Samani
0f081ac3c8 Update README.md 2022-07-20 23:08:48 +05:30
Lakhan Samani
3aa0fb20ce Update CONTRIBUTING.md 2022-07-20 23:08:44 +05:30
Lakhan Samani
891c885f20 fix: webhook ui 2022-07-17 17:18:45 +05:30
Lakhan Samani
89606615dc Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-07-17 17:06:03 +05:30
Lakhan Samani
ecab47b2ea Merge pull request #202 from anik-ghosh-au7/feat/webhooks
Feat/webhooks
2022-07-17 17:05:51 +05:30
Lakhan Samani
882756ef3a fix: handle different response 2022-07-17 17:05:35 +05:30
anik-ghosh-au7
a208c87c29 update: webhooks 2022-07-17 16:50:58 +05:30
Lakhan Samani
70ea463f60 feat: handle empty response from webhook endpoint 2022-07-17 16:25:16 +05:30
anik-ghosh-au7
79c94fcaf0 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/webhooks 2022-07-17 16:03:21 +05:30
anik-ghosh-au7
3b925bb072 update: webhooks 2022-07-17 16:03:07 +05:30
anik-ghosh-au7
df17ea8f40 update: webhooks 2022-07-17 14:48:20 +05:30
anik-ghosh-au7
94066d4408 update: webhooks 2022-07-17 14:42:46 +05:30
Lakhan Samani
41468b5b60 Merge pull request #201 from authorizerdev/feat/add-email-template-apis
feat: add email template apis
2022-07-17 14:20:34 +05:30
anik-ghosh-au7
1c61fcc17a update: webhooks 2022-07-17 13:52:31 +05:30
Lakhan Samani
a102924fd7 fix: remove debug logs 2022-07-17 13:39:23 +05:30
anik-ghosh-au7
390846c85f update: webhooks 2022-07-17 13:38:18 +05:30
Lakhan Samani
a48b809a89 feat: add tests for email template resolvers 2022-07-17 13:37:34 +05:30
Lakhan Samani
cd46da60a0 feat: implement resolvers for email template 2022-07-17 12:32:01 +05:30
Lakhan Samani
50f52a99b4 fix: github user emails 2022-07-17 12:07:17 +05:30
anik-ghosh-au7
150b1e5712 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/webhooks 2022-07-17 11:48:54 +05:30
Lakhan Samani
1f7eee43e2 feat: add email template implementation for arangodb provider 2022-07-17 11:37:04 +05:30
Lakhan Samani
7c441fff14 feat: add email template implementation for cassandra provider 2022-07-17 11:21:51 +05:30
Lakhan Samani
647cc1d9bf feat: add email template implementation for mongodb provider 2022-07-17 11:01:47 +05:30
Lakhan Samani
97b1d8d66f Merge branch 'main' into feat/add-email-template-apis 2022-07-17 10:39:02 +05:30
Lakhan Samani
2cce1c4e93 fix: webhook update headers 2022-07-17 10:36:16 +05:30
anik-ghosh-au7
8b1511a07b update: webhooks 2022-07-16 23:10:05 +05:30
anik-ghosh-au7
a69dd95992 update: webhooks 2022-07-16 15:59:21 +05:30
anik-ghosh-au7
d3260f4f32 update: webhooks 2022-07-16 15:24:50 +05:30
anik-ghosh-au7
301bde4da2 update: webhooks 2022-07-16 09:53:29 +05:30
anik-ghosh-au7
913c5c94fb update: webhooks 2022-07-16 09:42:10 +05:30
Lakhan Samani
610896b6f5 fix: refs for email templatE 2022-07-15 22:13:00 +05:30
anik-ghosh-au7
33f79872be Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/webhooks 2022-07-15 22:12:29 +05:30
anik-ghosh-au7
8fc52d76dc fix: TT-69 2022-07-15 22:12:08 +05:30
Lakhan Samani
aa12757155 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/add-email-template-apis 2022-07-15 22:11:58 +05:30
Lakhan Samani
847c364ad1 fix: refs 2022-07-15 22:11:08 +05:30
anik-ghosh-au7
eabc943452 update: webhooks 2022-07-15 13:17:09 +05:30
anik-ghosh-au7
41a0f15e16 update: webhooks 2022-07-15 13:04:32 +05:30
Lakhan Samani
e2294c24d0 feat: add email template implementation for sql provider 2022-07-15 12:35:35 +05:30
anik-ghosh-au7
a3c0a0422c update: webhooks 2022-07-15 12:22:47 +05:30
anik-ghosh-au7
d837b1590a update: webhooks 2022-07-15 12:20:51 +05:30
Lakhan Samani
283e570ebb feat: init email template schema for all providers 2022-07-15 10:23:45 +05:30
Lakhan Samani
14c74f6566 feat: add email template schema 2022-07-15 10:12:24 +05:30
anik-ghosh-au7
8e655daa71 update: webhooks 2022-07-14 23:41:44 +05:30
Lakhan Samani
fed092bb65 fix: invite email template 2022-07-13 21:16:31 +05:30
Lakhan Samani
6d28290605 Merge pull request #199 from authorizerdev/fix/password-changing
fix(update_profile): changing password if not signed up via basic auth
2022-07-13 20:46:56 +05:30
Lakhan Samani
2de0ea57d0 fix(update_profile): changing password if not signed up via basic
Resolves #198
2022-07-13 20:45:21 +05:30
Lakhan Samani
f2886e6da8 fix: disable other db test for quick test 2022-07-12 11:57:46 +05:30
Lakhan Samani
6b57bce6d9 fix: cassandra + mongo + arangodb issues with webhook 2022-07-12 11:48:42 +05:30
Lakhan Samani
bfbeb6add2 fix: couple session deletion with user deletion 2022-07-12 08:42:32 +05:30
Lakhan Samani
1fe0d65874 feat: add support for planetscale
Resolves #195
2022-07-11 22:37:07 +05:30
Lakhan Samani
bfaa0f9d89 fix: make list webhooks params optional 2022-07-11 22:05:44 +05:30
Lakhan Samani
4f5a6c77f8 Merge pull request #194 from authorizerdev/feat/webhook
feat: add webhook apis + integrate in events
2022-07-11 19:56:48 +05:30
Lakhan Samani
018a13ab3c feat: add tests for webhook resolvers 2022-07-11 19:40:54 +05:30
Lakhan Samani
334041d0e4 fix: delete user event flow 2022-07-11 11:13:32 +05:30
Lakhan Samani
6a8309a231 feat: register event for revoke/enable access + delete user 2022-07-11 11:12:30 +05:30
Lakhan Samani
6347b60753 fix: rename revoke refresh token handler for better reading 2022-07-11 11:10:30 +05:30
Lakhan Samani
bbb064b939 feat: add register event 2022-07-11 10:42:42 +05:30
Lakhan Samani
e91a819067 feat: implement resolvers 2022-07-10 21:49:33 +05:30
Lakhan Samani
09c3eafe6b feat: add mongodb database methods for webhook 2022-07-09 12:23:48 +05:30
Lakhan Samani
bb51775d34 feat: add cassandradb database methods for webhook 2022-07-09 12:16:54 +05:30
Lakhan Samani
6d586b16e4 feat: add arangodb database methods for webhook 2022-07-09 11:44:14 +05:30
Lakhan Samani
e8eb62769e feat: add sql database methods for webhook 2022-07-09 11:21:32 +05:30
Lakhan Samani
0ffb3f67f1 fix: index for arangodb 2022-07-08 19:10:37 +05:30
Lakhan Samani
ec62686fbc feat: add database methods for webhookLog 2022-07-08 19:09:23 +05:30
Lakhan Samani
a8064e79a1 feat: add template for webhook db methods 2022-07-06 10:38:21 +05:30
Lakhan Samani
265331801f feat: add database models 2022-07-04 22:37:13 +05:30
Lakhan Samani
6a74a50493 feat: add support for scylladb
Resolves #177
2022-07-04 21:57:14 +05:30
Lakhan Samani
8c27f20534 Merge pull request #193 from authorizerdev/fix/invalidate-session-token
fix: add provider to token creation
2022-07-01 22:14:50 +05:30
Lakhan Samani
29c6003ea3 fix: remove test log 2022-07-01 22:04:58 +05:30
Lakhan Samani
ae34fc7c2b fix: update_env resolver 2022-07-01 22:02:34 +05:30
Lakhan Samani
2a5d5d43b0 fix: add namespace to session token keys 2022-06-29 22:24:00 +05:30
Lakhan Samani
e6a4670ba9 fix: add provider to token creation 2022-06-29 09:54:12 +05:30
Lakhan Samani
64d64b4099 feat: add ability to disable strong password 2022-06-18 15:31:57 +05:30
Lakhan Samani
88f9a10f21 Merge pull request #192 from authorizerdev/feat/apple-login
feat: add apple login
2022-06-16 09:48:02 +05:30
Lakhan Samani
4e08d4f8fd fix: update app 2022-06-16 09:47:16 +05:30
Lakhan Samani
1c4dda9299 fix: remove debug logs 2022-06-16 07:34:10 +05:30
Lakhan Samani
ab18fa5832 fix: use raw base64 url decoding 2022-06-15 21:55:41 +05:30
Lakhan Samani
484d0c0882 chore: update app 2022-06-14 16:39:06 +05:30
Lakhan Samani
be59c3615f fix: add comment for scope 2022-06-14 15:47:08 +05:30
Lakhan Samani
db351f7771 fix: remove debug logs 2022-06-14 15:45:06 +05:30
Lakhan Samani
91c29c4092 fix: redirect 2022-06-14 15:43:23 +05:30
Lakhan Samani
415b97535e fix: update scope param 2022-06-14 15:05:56 +05:30
Lakhan Samani
7d1272d815 fix: update scope for apple login 2022-06-14 14:41:31 +05:30
Lakhan Samani
c9ba0b13f8 fix: update scope for apple login 2022-06-14 13:37:05 +05:30
Lakhan Samani
fadd9f6168 fix: update scope for apple login 2022-06-14 13:11:39 +05:30
Lakhan Samani
395e2e2a85 fix: update scope for apple login 2022-06-14 12:35:23 +05:30
Lakhan Samani
6335084835 fix: add post method support for oauth callback 2022-06-14 12:17:43 +05:30
Lakhan Samani
eab336cd3d fix: apple login params 2022-06-14 12:06:46 +05:30
Lakhan Samani
f4691fca1f fix: id token parsing 2022-06-14 11:38:04 +05:30
Lakhan Samani
341d4fbae5 fix: scope for apple login 2022-06-14 11:21:26 +05:30
Lakhan Samani
e467b45ab1 fix: apple client secret field 2022-06-14 11:11:09 +05:30
Lakhan Samani
7edfad3486 fix: apple client secret field 2022-06-14 10:56:47 +05:30
Lakhan Samani
80578b88ac feat: update app 2022-06-13 07:37:26 +05:30
Lakhan Samani
5646e7a0e7 feat: add test code to process apple user 2022-06-12 18:30:33 +05:30
Lakhan Samani
53a592ef63 feat: add base for apple login 2022-06-12 14:49:48 +05:30
Lakhan Samani
3337dbd0a4 Merge pull request #191 from authorizerdev/fix/session-invalidation
fix: session invalidation
2022-06-12 09:08:57 +05:30
Lakhan Samani
82a2a42f84 fix: user session access 2022-06-12 00:27:21 +05:30
Lakhan Samani
ac49b5bb70 fix: session tests 2022-06-11 19:24:53 +05:30
Lakhan Samani
926ab07c07 fix: session invalidation 2022-06-11 19:10:39 +05:30
Lakhan Samani
7a2dbea019 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-06-09 23:43:28 +05:30
Lakhan Samani
dff50097e8 feat: add support for cockroachdb 2022-06-09 23:43:21 +05:30
Lakhan Samani
aff9d3af20 Merge pull request #187 from authorizerdev/fix-parallel-access
fix: parallel access of env vars
2022-06-09 23:13:34 +05:30
Lakhan Samani
02eb1d6677 fix: add const for test env 2022-06-09 23:13:22 +05:30
Lakhan Samani
78a673e4ad fix: fix parallel access of env vars 2022-06-08 09:50:30 +05:30
Lakhan Samani
e0d8644264 fix: role validation while signup 2022-06-07 08:00:30 +05:30
Lakhan Samani
d8c662eaad fix: dashboard roles 2022-06-07 07:30:01 +05:30
Lakhan Samani
6d1d259f71 Merge pull request #182 from authorizerdev/feat/add-linkedin-login
feat: add linkedin login
2022-06-06 22:09:08 +05:30
Lakhan Samani
2841853d37 feat: add linkedin login 2022-06-06 22:08:32 +05:30
Lakhan Samani
360dd3c3bd fix: redirect uri 2022-06-05 22:46:56 +05:30
Lakhan Samani
c6add0cca6 fix: give higher priority to authorizer url 2022-06-05 22:13:10 +05:30
Lakhan Samani
7ac6252aac fix: app login page signup url
add debug logs
2022-06-05 21:44:16 +05:30
Lakhan Samani
5d2d1c342b fix: allow setting host for cassandradb without prot 2022-06-05 12:13:55 +05:30
Lakhan Samani
6da0a85936 fix: remove unused code 2022-06-04 09:26:02 +05:30
Lakhan Samani
116972d725 feat: add support for ScyllaDB
Resolves #177
2022-06-04 08:59:26 +05:30
Lakhan Samani
d1e1e287db Merge pull request #174 from authorizerdev/fix/memory-store
fix: replica cache consistency
2022-06-01 23:47:55 +05:30
Lakhan Samani
a7f04f8754 fix: fix mutex for testing purpose 2022-05-31 15:06:53 +05:30
Lakhan Samani
69b56c9912 fix: disable mutex for testing purpose 2022-05-31 15:00:11 +05:30
Lakhan Samani
98015708a2 fix: bool flag for redis 2022-05-31 13:27:43 +05:30
Lakhan Samani
1b5a7b8fb0 fix: don't allow redis disabling from dashboard 2022-05-31 13:26:03 +05:30
Lakhan Samani
8b9bcdfdbe fix: message 2022-05-31 13:24:24 +05:30
Lakhan Samani
ba429da05f fix: env query 2022-05-31 13:18:42 +05:30
Lakhan Samani
7c7bb42003 fix: message 2022-05-31 13:14:08 +05:30
Lakhan Samani
eeff88c853 fix: env saving 2022-05-31 13:11:54 +05:30
Lakhan Samani
cf8762b7a0 fix: slice envs 2022-05-31 08:14:03 +05:30
Lakhan Samani
c61c3024ec fix: upgrade tests 2022-05-30 12:47:50 +05:30
Lakhan Samani
7e3bd6a721 fix: import cycle issues 2022-05-30 11:54:16 +05:30
Lakhan Samani
1146468a03 fix: memory store upgrade in token helpers 2022-05-30 11:00:00 +05:30
Lakhan Samani
268b22ffb2 fix: memory store upgrade in resolvers 2022-05-30 09:19:55 +05:30
Lakhan Samani
43359f1dba fix: update store method till handlers 2022-05-29 17:22:46 +05:30
Lakhan Samani
1941cf4299 fix: move sessionstore -> memstore 2022-05-27 23:20:38 +05:30
237 changed files with 15713 additions and 2705 deletions

View File

@@ -1,3 +1,4 @@
ENV=production
DATABASE_URL=data.db
DATABASE_TYPE=sqlite
CUSTOM_ACCESS_TOKEN_SCRIPT="function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}"

9
.env.test Normal file
View File

@@ -0,0 +1,9 @@
ENV=test
DATABASE_URL=test.db
DATABASE_TYPE=sqlite
CUSTOM_ACCESS_TOKEN_SCRIPT="function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}"
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USERNAME=test
SMTP_PASSWORD=test
SENDER_EMAIL="info@authorizer.dev"

View File

@@ -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`
7. Build App `make build-app`
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`
### Testing

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ dashboard/build
build
.env
data.db
test.db
.DS_Store
.env.local
*.tar.gz

View File

@@ -10,7 +10,16 @@ build-dashboard:
clean:
rm -rf build
test:
cd server && go clean --testcache && go test -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-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_mongodb_db
docker rm -vf authorizer_scylla_db
docker rm -vf authorizer_arangodb
generate:
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate

View File

@@ -89,7 +89,7 @@ This guide helps you practice using Authorizer to evaluate it before you use it
5. Build Dashboard `make build-dashboard`
6. Build App `make build-app`
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`
### Deploy Authorizer using binaries

42
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.25.0",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -26,9 +26,9 @@
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
"dependencies": {
"node-fetch": "^2.6.1"
},
@@ -37,11 +37,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.14.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -816,7 +816,7 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/typescript": {
"version": "4.3.5",
@@ -838,12 +838,12 @@
"node_modules/webidl-conversions": {
"version": "3.0.1",
"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": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -852,19 +852,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
"requires": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.14.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -1482,7 +1482,7 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"typescript": {
"version": "4.3.5",
@@ -1497,12 +1497,12 @@
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"

View File

@@ -11,7 +11,7 @@
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.25.0",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",

View File

@@ -72,11 +72,13 @@ export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
</Footer>
</Fragment>
)}
{config.is_sign_up_enabled && (
<FooterContent>
Don't have an account? <Link to="/app/signup"> Sign Up</Link>
</FooterContent>
)}
{config.is_basic_authentication_enabled &&
!config.is_magic_link_login_enabled &&
config.is_sign_up_enabled && (
<FooterContent>
Don't have an account? <Link to="/app/signup"> Sign Up</Link>
</FooterContent>
)}
</Fragment>
);
}

View File

@@ -17,6 +17,9 @@
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.2",
"dayjs": "^1.10.7",
"draft-js": "^0.11.7",
"draft-js-import-html": "^1.4.1",
"draftjs-to-html": "^0.9.1",
"esbuild": "^0.14.9",
"focus-visible": "^5.2.0",
"framer-motion": "^5.5.5",
@@ -24,11 +27,16 @@
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-draft-wysiwyg": "^1.15.0",
"react-dropzone": "^12.0.4",
"react-icons": "^4.3.1",
"react-router-dom": "^6.2.1",
"typescript": "^4.5.4",
"urql": "^2.0.6"
},
"devDependencies": {
"@types/draftjs-to-html": "^0.8.1",
"@types/react-draft-wysiwyg": "^1.13.4"
}
},
"node_modules/@babel/code-frame": {
@@ -1145,6 +1153,25 @@
"react-dom": "^16.8.0 || 17.x"
}
},
"node_modules/@types/draft-js": {
"version": "0.11.9",
"resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.9.tgz",
"integrity": "sha512-cQJBZjjIlGaPA1tOY+wGz2KhlPtAAZOIXpUvGPxPRw5uzZ2tcj8m6Yu1QDV9YgP36+cqE3cUvgkARBzgUiuI/Q==",
"dev": true,
"dependencies": {
"@types/react": "*",
"immutable": "~3.7.4"
}
},
"node_modules/@types/draftjs-to-html": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@types/draftjs-to-html/-/draftjs-to-html-0.8.1.tgz",
"integrity": "sha512-NBkphQs+qZ/sAz/j1pCUaxkPAOx00LTsE88aMSSfcvK+UfCpjHJDqIMCkm6wKotuJvY5w0BtdRazQ0sAaXzPdg==",
"dev": true,
"dependencies": {
"@types/draft-js": "*"
}
},
"node_modules/@types/history": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
@@ -1191,6 +1218,16 @@
"@types/react": "*"
}
},
"node_modules/@types/react-draft-wysiwyg": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/@types/react-draft-wysiwyg/-/react-draft-wysiwyg-1.13.4.tgz",
"integrity": "sha512-wasD1t78JDmQvdPDRPf/mf5FSHMlncunW0F6KMOKB3awzi3Wi21yHMGsRAUOkfTr3R8F+yceG8fSLz0kYWu/QA==",
"dev": true,
"dependencies": {
"@types/draft-js": "*",
"@types/react": "*"
}
},
"node_modules/@types/react-router": {
"version": "5.1.17",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
@@ -1259,6 +1296,11 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
},
"node_modules/attr-accept": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
@@ -1306,6 +1348,11 @@
"node": ">=0.8.0"
}
},
"node_modules/classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -1340,6 +1387,16 @@
"toggle-selection": "^1.0.6"
}
},
"node_modules/core-js": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
"integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
@@ -1355,6 +1412,14 @@
"node": ">=8"
}
},
"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-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
@@ -1383,6 +1448,68 @@
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
},
"node_modules/draft-js": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.7.tgz",
"integrity": "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg==",
"dependencies": {
"fbjs": "^2.0.0",
"immutable": "~3.7.4",
"object-assign": "^4.1.1"
},
"peerDependencies": {
"react": ">=0.14.0",
"react-dom": ">=0.14.0"
}
},
"node_modules/draft-js-import-element": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/draft-js-import-element/-/draft-js-import-element-1.4.0.tgz",
"integrity": "sha512-WmYT5PrCm47lGL5FkH6sRO3TTAcn7qNHsD3igiPqLG/RXrqyKrqN4+wBgbcT2lhna/yfWTRtgzAbQsSJoS1Meg==",
"dependencies": {
"draft-js-utils": "^1.4.0",
"synthetic-dom": "^1.4.0"
},
"peerDependencies": {
"draft-js": ">=0.10.0",
"immutable": "3.x.x"
}
},
"node_modules/draft-js-import-html": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/draft-js-import-html/-/draft-js-import-html-1.4.1.tgz",
"integrity": "sha512-KOZmtgxZriCDgg5Smr3Y09TjubvXe7rHPy/2fuLSsL+aSzwUDwH/aHDA/k47U+WfpmL4qgyg4oZhqx9TYJV0tg==",
"dependencies": {
"draft-js-import-element": "^1.4.0"
},
"peerDependencies": {
"draft-js": ">=0.10.0",
"immutable": "3.x.x"
}
},
"node_modules/draft-js-utils": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.1.tgz",
"integrity": "sha512-xE81Y+z/muC5D5z9qWmKfxEW1XyXfsBzSbSBk2JRsoD0yzMGGHQm/0MtuqHl/EUDkaBJJLjJ2EACycoDMY/OOg==",
"peerDependencies": {
"draft-js": ">=0.10.0",
"immutable": "3.x.x"
}
},
"node_modules/draftjs-to-html": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/draftjs-to-html/-/draftjs-to-html-0.9.1.tgz",
"integrity": "sha512-fFstE6+IayaVFBEvaFt/wN8vdj8FsTRzij7dy7LI9QIwf5LgfHFi9zSpvCg+feJ2tbYVqHxUkjcibwpsTpgFVQ=="
},
"node_modules/draftjs-utils": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg==",
"peerDependencies": {
"draft-js": "^0.11.x",
"immutable": "3.x.x || 4.x.x"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -1647,6 +1774,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fbjs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-2.0.0.tgz",
"integrity": "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ==",
"dependencies": {
"core-js": "^3.6.4",
"cross-fetch": "^3.0.4",
"fbjs-css-vars": "^1.0.0",
"loose-envify": "^1.0.0",
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.18"
}
},
"node_modules/fbjs-css-vars": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
},
"node_modules/file-selector": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
@@ -1802,6 +1949,23 @@
"react-is": "^16.7.0"
}
},
"node_modules/html-to-draftjs": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ==",
"peerDependencies": {
"draft-js": "^0.10.x || ^0.11.x",
"immutable": "3.x.x || 4.x.x"
}
},
"node_modules/immutable": {
"version": "3.7.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
"integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -1856,6 +2020,14 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/linkify-it": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
"dependencies": {
"uc.micro": "^1.0.1"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -1877,6 +2049,25 @@
"loose-envify": "cli.js"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -1945,6 +2136,14 @@
"tslib": "^2.1.0"
}
},
"node_modules/promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"dependencies": {
"asap": "~2.0.3"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -1991,6 +2190,24 @@
"react": "17.0.2"
}
},
"node_modules/react-draft-wysiwyg": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz",
"integrity": "sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA==",
"dependencies": {
"classnames": "^2.2.6",
"draftjs-utils": "^0.10.2",
"html-to-draftjs": "^1.5.0",
"linkify-it": "^2.2.0",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"draft-js": "^0.10.x || ^0.11.x",
"immutable": "3.x.x || 4.x.x",
"react": "0.13.x || 0.14.x || ^15.0.0-0 || 15.x.x || ^16.0.0-0 || ^16.x.x || ^17.x.x || ^18.x.x",
"react-dom": "0.13.x || 0.14.x || ^15.0.0-0 || 15.x.x || ^16.0.0-0 || ^16.x.x || ^17.x.x || ^18.x.x"
}
},
"node_modules/react-dropzone": {
"version": "12.0.4",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
@@ -2207,6 +2424,11 @@
"object-assign": "^4.1.1"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -2240,6 +2462,11 @@
"node": ">=4"
}
},
"node_modules/synthetic-dom": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/synthetic-dom/-/synthetic-dom-1.4.0.tgz",
"integrity": "sha512-mHv51ZsmZ+ShT/4s5kg+MGUIhY7Ltq4v03xpN1c8T1Krb5pScsh/lzEjyhrVD0soVDbThbd2e+4dD9vnDG4rhg=="
},
"node_modules/tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
@@ -2258,6 +2485,11 @@
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
@@ -2275,6 +2507,29 @@
"node": ">=4.2.0"
}
},
"node_modules/ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"node_modules/urql": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
@@ -2333,6 +2588,20 @@
"loose-envify": "^1.0.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wonka": {
"version": "4.0.15",
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",
@@ -3170,6 +3439,25 @@
"tslib": "^2.1.0"
}
},
"@types/draft-js": {
"version": "0.11.9",
"resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.9.tgz",
"integrity": "sha512-cQJBZjjIlGaPA1tOY+wGz2KhlPtAAZOIXpUvGPxPRw5uzZ2tcj8m6Yu1QDV9YgP36+cqE3cUvgkARBzgUiuI/Q==",
"dev": true,
"requires": {
"@types/react": "*",
"immutable": "~3.7.4"
}
},
"@types/draftjs-to-html": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@types/draftjs-to-html/-/draftjs-to-html-0.8.1.tgz",
"integrity": "sha512-NBkphQs+qZ/sAz/j1pCUaxkPAOx00LTsE88aMSSfcvK+UfCpjHJDqIMCkm6wKotuJvY5w0BtdRazQ0sAaXzPdg==",
"dev": true,
"requires": {
"@types/draft-js": "*"
}
},
"@types/history": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
@@ -3216,6 +3504,16 @@
"@types/react": "*"
}
},
"@types/react-draft-wysiwyg": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/@types/react-draft-wysiwyg/-/react-draft-wysiwyg-1.13.4.tgz",
"integrity": "sha512-wasD1t78JDmQvdPDRPf/mf5FSHMlncunW0F6KMOKB3awzi3Wi21yHMGsRAUOkfTr3R8F+yceG8fSLz0kYWu/QA==",
"dev": true,
"requires": {
"@types/draft-js": "*",
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.17",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
@@ -3277,6 +3575,11 @@
}
}
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
},
"attr-accept": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
@@ -3314,6 +3617,11 @@
}
}
},
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3348,6 +3656,11 @@
"toggle-selection": "^1.0.6"
}
},
"core-js": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
"integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg=="
},
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
@@ -3360,6 +3673,14 @@
"yaml": "^1.7.2"
}
},
"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-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
@@ -3388,6 +3709,48 @@
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
},
"draft-js": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.7.tgz",
"integrity": "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg==",
"requires": {
"fbjs": "^2.0.0",
"immutable": "~3.7.4",
"object-assign": "^4.1.1"
}
},
"draft-js-import-element": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/draft-js-import-element/-/draft-js-import-element-1.4.0.tgz",
"integrity": "sha512-WmYT5PrCm47lGL5FkH6sRO3TTAcn7qNHsD3igiPqLG/RXrqyKrqN4+wBgbcT2lhna/yfWTRtgzAbQsSJoS1Meg==",
"requires": {
"draft-js-utils": "^1.4.0",
"synthetic-dom": "^1.4.0"
}
},
"draft-js-import-html": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/draft-js-import-html/-/draft-js-import-html-1.4.1.tgz",
"integrity": "sha512-KOZmtgxZriCDgg5Smr3Y09TjubvXe7rHPy/2fuLSsL+aSzwUDwH/aHDA/k47U+WfpmL4qgyg4oZhqx9TYJV0tg==",
"requires": {
"draft-js-import-element": "^1.4.0"
}
},
"draft-js-utils": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.1.tgz",
"integrity": "sha512-xE81Y+z/muC5D5z9qWmKfxEW1XyXfsBzSbSBk2JRsoD0yzMGGHQm/0MtuqHl/EUDkaBJJLjJ2EACycoDMY/OOg=="
},
"draftjs-to-html": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/draftjs-to-html/-/draftjs-to-html-0.9.1.tgz",
"integrity": "sha512-fFstE6+IayaVFBEvaFt/wN8vdj8FsTRzij7dy7LI9QIwf5LgfHFi9zSpvCg+feJ2tbYVqHxUkjcibwpsTpgFVQ=="
},
"draftjs-utils": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg=="
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3534,6 +3897,26 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
},
"fbjs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-2.0.0.tgz",
"integrity": "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ==",
"requires": {
"core-js": "^3.6.4",
"cross-fetch": "^3.0.4",
"fbjs-css-vars": "^1.0.0",
"loose-envify": "^1.0.0",
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.18"
}
},
"fbjs-css-vars": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
},
"file-selector": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
@@ -3657,6 +4040,16 @@
"react-is": "^16.7.0"
}
},
"html-to-draftjs": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ=="
},
"immutable": {
"version": "3.7.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
"integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw=="
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -3702,6 +4095,14 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"linkify-it": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
"requires": {
"uc.micro": "^1.0.1"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -3720,6 +4121,14 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -3775,6 +4184,14 @@
}
}
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "~2.0.3"
}
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -3812,6 +4229,18 @@
"scheduler": "^0.20.2"
}
},
"react-draft-wysiwyg": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz",
"integrity": "sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA==",
"requires": {
"classnames": "^2.2.6",
"draftjs-utils": "^0.10.2",
"html-to-draftjs": "^1.5.0",
"linkify-it": "^2.2.0",
"prop-types": "^15.7.2"
}
},
"react-dropzone": {
"version": "12.0.4",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
@@ -3965,6 +4394,11 @@
"object-assign": "^4.1.1"
}
},
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -3992,6 +4426,11 @@
"has-flag": "^3.0.0"
}
},
"synthetic-dom": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/synthetic-dom/-/synthetic-dom-1.4.0.tgz",
"integrity": "sha512-mHv51ZsmZ+ShT/4s5kg+MGUIhY7Ltq4v03xpN1c8T1Krb5pScsh/lzEjyhrVD0soVDbThbd2e+4dD9vnDG4rhg=="
},
"tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
@@ -4007,6 +4446,11 @@
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
@@ -4017,6 +4461,16 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg=="
},
"ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ=="
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"urql": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
@@ -4055,6 +4509,20 @@
"loose-envify": "^1.0.0"
}
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wonka": {
"version": "4.0.15",
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",

View File

@@ -19,6 +19,9 @@
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.2",
"dayjs": "^1.10.7",
"draft-js": "^0.11.7",
"draft-js-import-html": "^1.4.1",
"draftjs-to-html": "^0.9.1",
"esbuild": "^0.14.9",
"focus-visible": "^5.2.0",
"framer-motion": "^5.5.5",
@@ -26,10 +29,15 @@
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-draft-wysiwyg": "^1.15.0",
"react-dropzone": "^12.0.4",
"react-icons": "^4.3.1",
"react-router-dom": "^6.2.1",
"typescript": "^4.5.4",
"urql": "^2.0.6"
},
"devDependencies": {
"@types/draftjs-to-html": "^0.8.1",
"@types/react-draft-wysiwyg": "^1.13.4"
}
}

View 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;

View 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;

View File

@@ -1,88 +1,89 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from "../../components/InputField";
import { TextInputType } from "../../constants";
import InputField from '../../components/InputField';
import { TextInputType } from '../../constants';
const DatabaseCredentials = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Database Credentials
</Text>
<Stack spacing={6} padding="3% 0">
<Text fontStyle="italic" fontSize="sm" color="blackAlpha.500" mt={3}>
Note: Database related environment variables cannot be updated from
dashboard :(
</Text>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">DataBase Name:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.DATABASE_NAME}
isDisabled={true}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">DataBase Type:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.DATABASE_TYPE}
isDisabled={true}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">DataBase URL:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.DATABASE_URL}
isDisabled={true}
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Database Credentials
</Text>
<Stack spacing={6} padding="3% 0">
<Text fontStyle="italic" fontSize="sm" color="blackAlpha.500" mt={3}>
Note: Database related environment variables cannot be updated from
dashboard. Please use .env file or OS environment variables to update
it.
</Text>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">DataBase Name:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.DATABASE_NAME}
isDisabled={true}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">DataBase Type:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.DATABASE_TYPE}
isDisabled={true}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">DataBase URL:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.DATABASE_URL}
isDisabled={true}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default DatabaseCredentials;
export default DatabaseCredentials;

View File

@@ -3,7 +3,7 @@ import { Flex, Stack, Text } from '@chakra-ui/react';
import InputField from '../InputField';
import { SwitchInputType } from '../../constants';
const UICustomization = ({ variables, setVariables }: any) => {
const Features = ({ variables, setVariables }: any) => {
return (
<div>
{' '}
@@ -71,9 +71,21 @@ const UICustomization = ({ variables, setVariables }: any) => {
/>
</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>
</Stack>
</div>
);
};
export default UICustomization;
export default Features;

View File

@@ -9,7 +9,13 @@ import {
Divider,
useMediaQuery,
} from '@chakra-ui/react';
import { FaGoogle, FaGithub, FaFacebookF } from 'react-icons/fa';
import {
FaGoogle,
FaGithub,
FaFacebookF,
FaLinkedin,
FaApple,
} from 'react-icons/fa';
import { TextInputType, HiddenInputType } from '../../constants';
const OAuthConfig = ({
@@ -182,6 +188,82 @@ const OAuthConfig = ({
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaLinkedin 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.LINKEDIN_CLIENT_ID}
placeholder="LinkedIn 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.LINKEDIN_CLIENT_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>
</Stack>
</Box>
</div>

View File

@@ -1,67 +1,68 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import { ArrayInputType } from "../../constants";
import InputField from "../InputField";
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import { ArrayInputType } from '../../constants';
import InputField from '../InputField';
const Roles = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Roles
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
overflow="hidden"
>
<InputField
borderRadius={7}
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Default Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.DEFAULT_ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Protected Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.PROTECTED_ROLES}
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Roles
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
overflow="hidden"
>
<InputField
borderRadius={7}
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Default Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.DEFAULT_ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Protected Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.PROTECTED_ROLES}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default Roles;
export default Roles;

View File

@@ -1,36 +1,42 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import InputField from "../InputField";
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../InputField';
const SessionStorage = ({ variables, setVariables, RedisURL }: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Session Storage
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Redis URL:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={RedisURL}
placeholder="Redis URL"
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Session Storage
</Text>
<Text fontStyle="italic" fontSize="sm" color="blackAlpha.500" mt={3}>
Note: Redis related environment variables cannot be updated from
dashboard. Please use .env file or OS environment variables to update
it.
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Redis URL:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
disabled
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={RedisURL}
placeholder="Redis URL"
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default SessionStorage;
export default SessionStorage;

View File

@@ -12,7 +12,6 @@ import {
Select,
Textarea,
Switch,
Code,
Text,
} from '@chakra-ui/react';
import {

View File

@@ -22,7 +22,7 @@ import {
InputRightElement,
Text,
Link,
Tooltip
Tooltip,
} from '@chakra-ui/react';
import { useClient } from 'urql';
import { FaUserPlus, FaMinusCircle, FaPlus, FaUpload } from 'react-icons/fa';
@@ -187,22 +187,22 @@ const InviteMembersModal = ({
isDisabled={disabled}
size="sm"
>
<Center h="100%">
{disabled ? (
<Tooltip
mr={8}
mt={1}
hasArrow
bg="gray.300"
color="black"
label="Email verification is disabled, refer to 'UI Customization' tab within 'Environment' to enable it."
>
Invite Members
</Tooltip>
) : (
"Invite Members"
)}
</Center>{" "}
<Center h="100%">
{disabled ? (
<Tooltip
mr={8}
mt={1}
hasArrow
bg="gray.300"
color="black"
label="Email verification is disabled, refer to 'Features' tab within 'Environment' to enable it."
>
Invite Members
</Tooltip>
) : (
'Invite Members'
)}
</Center>{' '}
</Button>
<Modal isOpen={isOpen} onClose={closeModalHandler} size="xl">
<ModalOverlay />

View File

@@ -30,6 +30,8 @@ import {
FiMenu,
FiUsers,
FiChevronDown,
FiLink,
FiFileText,
} from 'react-icons/fi';
import { BiCustomize } from 'react-icons/bi';
import { AiOutlineKey } from 'react-icons/ai';
@@ -98,9 +100,9 @@ const LinkItems: Array<LinkItemProps> = [
},
{ name: 'Access Token', icon: SiOpenaccess, route: '/access-token' },
{
name: 'UI Customization',
name: 'Features',
icon: BiCustomize,
route: '/ui-customization',
route: '/features',
},
{ name: 'Database', icon: RiDatabase2Line, route: '/db-cred' },
{
@@ -111,6 +113,8 @@ const LinkItems: Array<LinkItemProps> = [
],
},
{ name: 'Users', icon: FiUsers, route: '/users' },
{ name: 'Webhooks', icon: FiLink, route: '/webhooks' },
{ name: 'Email Templates', icon: FiFileText, route: '/email-templates' },
];
interface SidebarProps extends BoxProps {

View File

@@ -0,0 +1,375 @@
import React, { useEffect, useState } from 'react';
import {
Button,
Center,
Flex,
Input,
InputGroup,
MenuItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Select,
Text,
useDisclosure,
useToast,
} from '@chakra-ui/react';
import { FaPlus } from 'react-icons/fa';
import { useClient } from 'urql';
import { Editor } from 'react-draft-wysiwyg';
import { EditorState, convertToRaw, Modifier } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import { stateFromHTML } from 'draft-js-import-html';
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;
}
interface UpdateEmailTemplateInputPropTypes {
view: UpdateModalViews;
selectedTemplate?: selectedEmailTemplateDataTypes;
fetchEmailTemplatesData: Function;
}
interface templateVariableDataTypes {
text: string;
value: string;
}
interface emailTemplateDataType {
[EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string;
}
interface validatorDataType {
[EmailTemplateInputDataFields.SUBJECT]: boolean;
}
const initTemplateData: emailTemplateDataType = {
[EmailTemplateInputDataFields.EVENT_NAME]:
emailTemplateEventNames.BASIC_AUTH_SIGNUP,
[EmailTemplateInputDataFields.SUBJECT]: '',
};
const initTemplateValidatorData: validatorDataType = {
[EmailTemplateInputDataFields.SUBJECT]: true,
};
const UpdateEmailTemplate = ({
view,
selectedTemplate,
fetchEmailTemplatesData,
}: UpdateEmailTemplateInputPropTypes) => {
const client = useClient();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const [loading, setLoading] = useState<boolean>(false);
const [editorState, setEditorState] = React.useState<EditorState>(
EditorState.createEmpty()
);
const [templateVariables, setTemplateVariables] = useState<
templateVariableDataTypes[]
>([]);
const [templateData, setTemplateData] = useState<emailTemplateDataType>({
...initTemplateData,
});
const [validator, setValidator] = useState<validatorDataType>({
...initTemplateValidatorData,
});
const onEditorStateChange = (editorState: EditorState) => {
setEditorState(editorState);
};
const inputChangehandler = (inputType: string, value: any) => {
if (inputType !== EmailTemplateInputDataFields.EVENT_NAME) {
setValidator({
...validator,
[inputType]: value?.trim().length,
});
}
setTemplateData({ ...templateData, [inputType]: value });
};
const validateData = () => {
const rawData: string = draftToHtml(
convertToRaw(editorState.getCurrentContent())
).trim();
return (
!loading &&
rawData &&
rawData !== '<p></p>' &&
rawData !== '<h1></h1>' &&
templateData[EmailTemplateInputDataFields.EVENT_NAME].length > 0 &&
templateData[EmailTemplateInputDataFields.SUBJECT].length > 0 &&
validator[EmailTemplateInputDataFields.SUBJECT]
);
};
const saveData = async () => {
if (!validateData()) return;
setLoading(true);
const params = {
[EmailTemplateInputDataFields.EVENT_NAME]:
templateData[EmailTemplateInputDataFields.EVENT_NAME],
[EmailTemplateInputDataFields.SUBJECT]:
templateData[EmailTemplateInputDataFields.SUBJECT],
[EmailTemplateInputDataFields.TEMPLATE]: draftToHtml(
convertToRaw(editorState.getCurrentContent())
).trim(),
};
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);
setEditorState(
EditorState.createWithContent(stateFromHTML(selectedTemplate.template))
);
} else {
setTemplateData({ ...initTemplateData });
setEditorState(EditorState.createEmpty());
}
};
useEffect(() => {
if (
isOpen &&
view === UpdateModalViews.Edit &&
selectedTemplate &&
Object.keys(selectedTemplate || {}).length
) {
const { id, created_at, template, ...rest } = selectedTemplate;
setTemplateData(rest);
setEditorState(EditorState.createWithContent(stateFromHTML(template)));
}
}, [isOpen]);
useEffect(() => {
const updatedTemplateVariables = Object.entries(
emailTemplateVariables
).reduce((acc, varData): any => {
if (
(templateData[EmailTemplateInputDataFields.EVENT_NAME] !==
emailTemplateEventNames.VERIFY_OTP &&
varData[1] === emailTemplateVariables.otp) ||
(templateData[EmailTemplateInputDataFields.EVENT_NAME] ===
emailTemplateEventNames.VERIFY_OTP &&
varData[1] === emailTemplateVariables.verification_url)
) {
return acc;
}
return [
...acc,
{
text: varData[0],
value: varData[1],
},
];
}, []);
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={onClose} size="3xl">
<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"
>
<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="5%"
>
<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="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex>Template Body</Flex>
<Text
style={{
fontSize: 14,
}}
color="gray.400"
>{`To select dynamic variables open curly braces "{"`}</Text>
</Flex>
<Editor
editorState={editorState}
onEditorStateChange={onEditorStateChange}
editorStyle={{
border: '1px solid #d9d9d9',
borderRadius: '5px',
marginTop: '2%',
height: '35vh',
}}
mention={{
separator: ' ',
trigger: '{',
suggestions: templateVariables,
}}
/>
</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;

View File

@@ -0,0 +1,667 @@
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,
} 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="center"
alignItems="center"
marginBottom="5%"
flexDirection="column"
>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<Flex>
Payload
<Text color="gray.500" ml={1}>
(example)
</Text>
</Flex>
<Button
onClick={() => setIsShowingPayload(!isShowingPayload)}
variant="ghost"
>
{isShowingPayload ? <FaAngleUp /> : <FaAngleDown />}
</Button>
</Flex>
<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>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<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="scroll">
{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>
</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;

View 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;

View File

@@ -7,6 +7,8 @@ export const TextInputType = {
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST',
@@ -31,6 +33,8 @@ export const HiddenInputType = {
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET',
@@ -62,6 +66,8 @@ export const SwitchInputType = {
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
};
export const DateInputType = {
@@ -98,6 +104,10 @@ export interface envVarTypes {
GITHUB_CLIENT_SECRET: string;
FACEBOOK_CLIENT_ID: string;
FACEBOOK_CLIENT_SECRET: string;
LINKEDIN_CLIENT_ID: string;
LINKEDIN_CLIENT_SECRET: string;
APPLE_CLIENT_ID: string;
APPLE_CLIENT_SECRET: string;
ROLES: [string] | [];
DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | [];
@@ -122,6 +132,7 @@ export interface envVarTypes {
DISABLE_EMAIL_VERIFICATION: boolean;
DISABLE_BASIC_AUTHENTICATION: boolean;
DISABLE_SIGN_UP: boolean;
DISABLE_STRONG_PASSWORD: boolean;
OLD_ADMIN_SECRET: string;
DATABASE_NAME: string;
DATABASE_TYPE: string;
@@ -138,7 +149,109 @@ export const envSubViews = {
WHITELIST_VARIABLES: 'whitelist-variables',
ORGANIZATION_INFO: 'organization-info',
ACCESS_TOKEN: 'access-token',
UI_CUSTOMIZATION: 'ui-customization',
FEATURES: 'features',
ADMIN_SECRET: 'admin-secret',
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',
}
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 = {
BASIC_AUTH_SIGNUP: 'basic_auth_signup',
MAGIC_LINK_LOGIN: 'magic_link_login',
UPDATE_EMAIL: 'update_email',
FORGOT_PASSWORD: 'forgot_password',
VERIFY_OTP: 'verify_otp',
};
export enum webhookVerifiedStatus {
VERIFIED = 'verified',
NOT_VERIFIED = 'not_verified',
PENDING = 'verification_pending',
}
export const emailTemplateVariables = {
'user.id': '{user.id}}',
'user.email': '{user.email}}',
'user.given_name': '{user.given_name}}',
'user.family_name': '{user.family_name}}',
'user.signup_methods': '{user.signup_methods}}',
'user.email_verified': '{user.email_verified}}',
'user.picture': '{user.picture}}',
'user.roles': '{user.roles}}',
'user.middle_name': '{user.middle_name}}',
'user.nickname': '{user.nickname}}',
'user.preferred_username': '{user.preferred_username}}',
'user.gender': '{user.gender}}',
'user.birthdate': '{user.birthdate}}',
'user.phone_number': '{user.phone_number}}',
'user.phone_number_verified': '{user.phone_number_verified}}',
'user.created_at': '{user.created_at}}',
'user.updated_at': '{user.updated_at}}',
'organization.name': '{organization.name}}',
'organization.logo': '{organization.logo}}',
verification_url: '{verification_url}}',
otp: '{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"
}`;

View File

@@ -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
}
}
`;

View File

@@ -26,9 +26,13 @@ export const EnvVariablesQuery = `
GITHUB_CLIENT_SECRET,
FACEBOOK_CLIENT_ID,
FACEBOOK_CLIENT_SECRET,
ROLES,
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET,
APPLE_CLIENT_ID,
APPLE_CLIENT_SECRET,
DEFAULT_ROLES,
PROTECTED_ROLES,
ROLES,
JWT_TYPE,
JWT_SECRET,
JWT_ROLE_CLAIM,
@@ -49,6 +53,8 @@ export const EnvVariablesQuery = `
DISABLE_EMAIL_VERIFICATION,
DISABLE_BASIC_AUTHENTICATION,
DISABLE_SIGN_UP,
DISABLE_STRONG_PASSWORD,
DISABLE_REDIS_FOR_ENV,
CUSTOM_ACCESS_TOKEN_SCRIPT,
DATABASE_NAME,
DATABASE_TYPE,
@@ -95,3 +101,63 @@ 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) {
EmailTemplates {
id
event_name
subject
created_at
template
}
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
}
}
}
`;

View File

@@ -0,0 +1,347 @@
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, WebhooksDataQuery } 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;
}
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, EmailTemplates: 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;

View File

@@ -25,7 +25,7 @@ import EmailConfigurations from '../components/EnvComponents/EmailConfiguration'
import DomainWhiteListing from '../components/EnvComponents/DomainWhitelisting';
import OrganizationInfo from '../components/EnvComponents/OrganizationInfo';
import AccessToken from '../components/EnvComponents/AccessToken';
import UICustomization from '../components/EnvComponents/UICustomization';
import Features from '../components/EnvComponents/Features';
import SecurityAdminSecret from '../components/EnvComponents/SecurityAdminSecret';
import DatabaseCredentials from '../components/EnvComponents/DatabaseCredentials';
@@ -46,6 +46,10 @@ const Environment = () => {
GITHUB_CLIENT_SECRET: '',
FACEBOOK_CLIENT_ID: '',
FACEBOOK_CLIENT_SECRET: '',
LINKEDIN_CLIENT_ID: '',
LINKEDIN_CLIENT_SECRET: '',
APPLE_CLIENT_ID: '',
APPLE_CLIENT_SECRET: '',
ROLES: [],
DEFAULT_ROLES: [],
PROTECTED_ROLES: [],
@@ -70,6 +74,7 @@ const Environment = () => {
DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_SIGN_UP: false,
DISABLE_STRONG_PASSWORD: false,
OLD_ADMIN_SECRET: '',
DATABASE_NAME: '',
DATABASE_TYPE: '',
@@ -83,6 +88,8 @@ const Environment = () => {
GOOGLE_CLIENT_SECRET: false,
GITHUB_CLIENT_SECRET: false,
FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false,
JWT_SECRET: false,
SMTP_PASSWORD: false,
ADMIN_SECRET: false,
@@ -259,12 +266,9 @@ const Environment = () => {
setVariables={setEnvVariables}
/>
);
case envSubViews.UI_CUSTOMIZATION:
case envSubViews.FEATURES:
return (
<UICustomization
variables={envVariables}
setVariables={setEnvVariables}
/>
<Features variables={envVariables} setVariables={setEnvVariables} />
);
case envSubViews.ADMIN_SECRET:
return (

View 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;

View File

@@ -3,37 +3,41 @@ import { Outlet, Route, Routes } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext';
import { DashboardLayout } from '../layouts/DashboardLayout';
import EmailTemplates from '../pages/EmailTemplates';
const Auth = lazy(() => import('../pages/Auth'));
const Environment = lazy(() => import('../pages/Environment'));
const Home = lazy(() => import('../pages/Home'));
const Users = lazy(() => import('../pages/Users'));
const Webhooks = lazy(() => import('../pages/Webhooks'));
export const AppRoutes = () => {
const { isLoggedIn } = useAuthContext();
if (isLoggedIn) {
return (
<div>
<Suspense fallback={<></>}>
<Routes>
<Route
element={
<DashboardLayout>
<Outlet />
</DashboardLayout>
}
>
<Route path="/" element={<Outlet />}>
<Route index element={<Environment />} />
<Route path="/:sec" element={<Environment />} />
</Route>
<Route path="users" element={<Users />} />
<Route path="*" element={<Home />} />
</Route>
</Routes>
</Suspense>
</div>
<div>
<Suspense fallback={<></>}>
<Routes>
<Route
element={
<DashboardLayout>
<Outlet />
</DashboardLayout>
}
>
<Route path="/" element={<Outlet />}>
<Route index element={<Environment />} />
<Route path="/:sec" element={<Environment />} />
</Route>
<Route path="users" element={<Users />} />
<Route path="webhooks" element={<Webhooks />} />
<Route path="email-templates" element={<EmailTemplates />} />
<Route path="*" element={<Home />} />
</Route>
</Routes>
</Suspense>
</div>
);
}
return (

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "authorizer",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

14
server/cli/cli.go Normal file
View File

@@ -0,0 +1,14 @@
package cli
var (
// ARG_DB_URL is the cli arg variable for the database url
ARG_DB_URL *string
// ARG_DB_TYPE is the cli arg variable for the database type
ARG_DB_TYPE *string
// ARG_ENV_FILE is the cli arg variable for the env file
ARG_ENV_FILE *string
// ARG_LOG_LEVEL is the cli arg variable for the log level
ARG_LOG_LEVEL *string
// ARG_REDIS_URL is the cli arg variable for the redis url
ARG_REDIS_URL *string
)

View File

@@ -0,0 +1,18 @@
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"
)

View File

@@ -0,0 +1,8 @@
package constants
const (
// AppCookieName is the name of the cookie that is used to store the application token
AppCookieName = "cookie"
// AdminCookieName is the name of the cookie that is used to store the admin token
AdminCookieName = "authorizer-admin"
)

View File

@@ -19,4 +19,10 @@ const (
DbTypeMariaDB = "mariadb"
// DbTypeCassandra is the cassandra database type
DbTypeCassandraDB = "cassandradb"
// DbTypeScyllaDB is the scylla database type
DbTypeScyllaDB = "scylladb"
// DbTypeCockroachDB is the cockroach database type
DbTypeCockroachDB = "cockroachdb"
// DbTypePlanetScaleDB is the planetscale database type
DbTypePlanetScaleDB = "planetscale"
)

View File

@@ -3,14 +3,8 @@ package constants
var VERSION = "0.0.1"
const (
// Envstore identifier
// StringStore string store identifier
StringStoreIdentifier = "stringStore"
// BoolStore bool store identifier
BoolStoreIdentifier = "boolStore"
// SliceStore slice store identifier
SliceStoreIdentifier = "sliceStore"
// TestEnv is used for testing
TestEnv = "test"
// EnvKeyEnv key for env variable ENV
EnvKeyEnv = "ENV"
// EnvKeyEnvPath key for cli arg variable ENV_PATH
@@ -19,7 +13,6 @@ const (
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
// EnvKeyPort key for env variable PORT
EnvKeyPort = "PORT"
// EnvKeyAccessTokenExpiryTime key for env variable ACCESS_TOKEN_EXPIRY_TIME
EnvKeyAccessTokenExpiryTime = "ACCESS_TOKEN_EXPIRY_TIME"
// EnvKeyAdminSecret key for env variable ADMIN_SECRET
@@ -62,34 +55,12 @@ const (
EnvKeyJwtPrivateKey = "JWT_PRIVATE_KEY"
// EnvKeyJwtPublicKey key for env variable JWT_PUBLIC_KEY
EnvKeyJwtPublicKey = "JWT_PUBLIC_KEY"
// EnvKeyAllowedOrigins key for env variable ALLOWED_ORIGINS
EnvKeyAllowedOrigins = "ALLOWED_ORIGINS"
// EnvKeyAppURL key for env variable APP_URL
EnvKeyAppURL = "APP_URL"
// EnvKeyRedisURL key for env variable REDIS_URL
EnvKeyRedisURL = "REDIS_URL"
// EnvKeyCookieName key for env variable COOKIE_NAME
EnvKeyCookieName = "COOKIE_NAME"
// EnvKeyAdminCookieName key for env variable ADMIN_COOKIE_NAME
EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME"
// EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL
EnvKeyResetPasswordURL = "RESET_PASSWORD_URL"
// EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
EnvKeyDisableBasicAuthentication = "DISABLE_BASIC_AUTHENTICATION"
// EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
EnvKeyDisableLoginPage = "DISABLE_LOGIN_PAGE"
// EnvKeyDisableSignUp key for env variable DISABLE_SIGN_UP
EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
// EnvKeyRoles key for env variable ROLES
EnvKeyRoles = "ROLES"
// EnvKeyProtectedRoles key for env variable PROTECTED_ROLES
EnvKeyProtectedRoles = "PROTECTED_ROLES"
// EnvKeyDefaultRoles key for env variable DEFAULT_ROLES
EnvKeyDefaultRoles = "DEFAULT_ROLES"
// EnvKeyJwtRoleClaim key for env variable JWT_ROLE_CLAIM
EnvKeyJwtRoleClaim = "JWT_ROLE_CLAIM"
// EnvKeyGoogleClientID key for env variable GOOGLE_CLIENT_ID
@@ -104,6 +75,14 @@ const (
EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID"
// EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET
EnvKeyFacebookClientSecret = "FACEBOOK_CLIENT_SECRET"
// EnvKeyLinkedinClientID key for env variable LINKEDIN_CLIENT_ID
EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID"
// EnvKeyLinkedinClientSecret key for env variable 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"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
@@ -120,6 +99,32 @@ const (
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
// EnvKeyJWK key for env variable JWK
EnvKeyJWK = "JWK"
// Boolean variables
// EnvKeyIsProd key for env variable IS_PROD
EnvKeyIsProd = "IS_PROD"
// EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
EnvKeyDisableBasicAuthentication = "DISABLE_BASIC_AUTHENTICATION"
// EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
EnvKeyDisableLoginPage = "DISABLE_LOGIN_PAGE"
// EnvKeyDisableSignUp key for env variable DISABLE_SIGN_UP
EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
// EnvKeyDisableRedisForEnv key for env variable DISABLE_REDIS_FOR_ENV
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
// EnvKeyDisableStrongPassword key for env variable DISABLE_STRONG_PASSWORD
EnvKeyDisableStrongPassword = "DISABLE_STRONG_PASSWORD"
// Slice variables
// EnvKeyRoles key for env variable ROLES
EnvKeyRoles = "ROLES"
// EnvKeyProtectedRoles key for env variable PROTECTED_ROLES
EnvKeyProtectedRoles = "PROTECTED_ROLES"
// EnvKeyDefaultRoles key for env variable DEFAULT_ROLES
EnvKeyDefaultRoles = "DEFAULT_ROLES"
// EnvKeyAllowedOrigins key for env variable ALLOWED_ORIGINS
EnvKeyAllowedOrigins = "ALLOWED_ORIGINS"
)

View File

@@ -8,4 +8,10 @@ const (
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
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
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~))"
)

View File

@@ -1,14 +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"
)

View File

@@ -7,4 +7,6 @@ const (
TokenTypeAccessToken = "access_token"
// TokenTypeIdentityToken is the identity_token token type
TokenTypeIdentityToken = "id_token"
// TokenTypeSessionToken is the session_token type used for browser session
TokenTypeSessionToken = "session_token"
)

View 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`
)

View File

@@ -4,8 +4,7 @@ import (
"net/url"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/gin-gonic/gin"
)
@@ -13,15 +12,14 @@ import (
func SetAdminCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
gc.SetCookie(constants.AdminCookieName, token, 3600, "/", host, secure, httpOnly)
}
// GetAdminCookie gets the admin cookie from the request
func GetAdminCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
cookie, err := gc.Request.Cookie(constants.AdminCookieName)
if err != nil {
return "", err
}
@@ -39,8 +37,7 @@ func GetAdminCookie(gc *gin.Context) (string, error) {
func DeleteAdminCookie(gc *gin.Context) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
gc.SetCookie(constants.AdminCookieName, "", -1, "/", host, secure, httpOnly)
}

View File

@@ -5,8 +5,7 @@ import (
"net/url"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/gin-gonic/gin"
)
@@ -14,9 +13,9 @@ import (
func SetSession(gc *gin.Context, sessionID string) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
@@ -25,33 +24,33 @@ func SetSession(gc *gin.Context, sessionID string) {
year := 60 * 60 * 24 * 365
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
gc.SetCookie(constants.AppCookieName+"_session", sessionID, year, "/", host, secure, httpOnly)
gc.SetCookie(constants.AppCookieName+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
}
// DeleteSession sets session cookies to expire
func DeleteSession(gc *gin.Context) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", "", -1, "/", domain, secure, httpOnly)
gc.SetCookie(constants.AppCookieName+"_session", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(constants.AppCookieName+"_session_domain", "", -1, "/", domain, secure, httpOnly)
}
// GetSession gets the session cookie from context
func GetSession(gc *gin.Context) (string, error) {
var cookie *http.Cookie
var err error
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session")
cookie, err = gc.Request.Cookie(constants.AppCookieName + "_session")
if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain")
cookie, err = gc.Request.Cookie(constants.AppCookieName + "_session_domain")
if err != nil {
return "", err
}

View File

@@ -7,14 +7,18 @@ import (
"io"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
)
var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 0o5}
// EncryptAES method is to encrypt or hide any classified text
func EncryptAES(text string) (string, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
k, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)
if err != nil {
return "", err
}
key := []byte(k)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
@@ -28,7 +32,11 @@ func EncryptAES(text string) (string, error) {
// DecryptAES method is to extract back the encrypted text
func DecryptAES(text string) (string, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
k, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)
if err != nil {
return "", err
}
key := []byte(k)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
@@ -46,9 +54,13 @@ func DecryptAES(text string) (string, error) {
// EncryptAESEnv encrypts data using AES algorithm
// kept for the backward compatibility of env data encryption
func EncryptAESEnv(text []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key)
var res []byte
k, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)
if err != nil {
return res, err
}
key := []byte(k)
c, err := aes.NewCipher(key)
if err != nil {
return res, err
}
@@ -81,9 +93,13 @@ func EncryptAESEnv(text []byte) ([]byte, error) {
// DecryptAES decrypts data using AES algorithm
// Kept for the backward compatibility of env data decryption
func DecryptAESEnv(ciphertext []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key)
var res []byte
k, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)
if err != nil {
return res, err
}
key := []byte(k)
c, err := aes.NewCipher(key)
if err != nil {
return res, err
}

View File

@@ -5,7 +5,7 @@ import (
"encoding/json"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
"golang.org/x/crypto/bcrypt"
"gopkg.in/square/go-jose.v2"
)
@@ -37,20 +37,35 @@ func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) {
// this is called while initializing app / when env is updated
func GenerateJWKBasedOnEnv() (string, error) {
jwk := ""
algo := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
algo, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
if err != nil {
return jwk, err
}
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
if err != nil {
return jwk, err
}
jwtSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)
if err != nil {
return jwk, err
}
var err error
// check if jwt secret is provided
if IsHMACA(algo) {
jwk, err = GetPubJWK(algo, clientID, []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))
jwk, err = GetPubJWK(algo, clientID, []byte(jwtSecret))
if err != nil {
return "", err
}
}
jwtPublicKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
if err != nil {
return jwk, err
}
if IsRSA(algo) {
publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(jwtPublicKey)
if err != nil {
return "", err
}
@@ -62,7 +77,11 @@ func GenerateJWKBasedOnEnv() (string, error) {
}
if IsECDSA(algo) {
publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
jwtPublicKey, err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
if err != nil {
return jwk, err
}
publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(jwtPublicKey)
if err != nil {
return "", err
}
@@ -77,13 +96,16 @@ func GenerateJWKBasedOnEnv() (string, error) {
}
// EncryptEnvData is used to encrypt the env data
func EncryptEnvData(data envstore.Store) (string, error) {
func EncryptEnvData(data map[string]interface{}) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
storeData := envstore.EnvStoreObj.GetEnvStoreClone()
storeData, err := memorystore.Provider.GetEnvStore()
if err != nil {
return "", err
}
err = json.Unmarshal(jsonBytes, &storeData)
if err != nil {

View File

@@ -9,7 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/db/providers/cassandradb"
"github.com/authorizerdev/authorizer/server/db/providers/mongodb"
"github.com/authorizerdev/authorizer/server/db/providers/sql"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// Provider returns the current database provider
@@ -18,13 +18,15 @@ var Provider providers.Provider
func InitDB() error {
var err error
isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeCassandraDB
isArangoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
isMongoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
isCassandra := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeCassandraDB
envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv()
isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB
isArangoDB := envs.DatabaseType == constants.DbTypeArangodb
isMongoDB := envs.DatabaseType == constants.DbTypeMongodb
isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB || envs.DatabaseType == constants.DbTypeScyllaDB
if isSQL {
log.Info("Initializing SQL Driver for: ", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType))
log.Info("Initializing SQL Driver for: ", envs.DatabaseType)
Provider, err = sql.NewProvider()
if err != nil {
log.Fatal("Failed to initialize SQL driver: ", err)

View File

@@ -0,0 +1,35 @@
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 `gorm:"type:text" json:"subject" bson:"subject" cql:"subject"`
Template string `gorm:"type:text" json:"template" bson:"template" cql:"template"`
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,
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
}
}

View File

@@ -6,6 +6,9 @@ type CollectionList struct {
VerificationRequest string
Session string
Env string
Webhook string
WebhookLog string
EmailTemplate string
}
var (
@@ -17,5 +20,8 @@ var (
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Env: Prefix + "env",
Webhook: Prefix + "webhooks",
WebhookLog: Prefix + "webhook_logs",
EmailTemplate: Prefix + "email_templates",
}
)

View File

@@ -6,8 +6,7 @@ package models
type Session struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id" cql:"user_id"`
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-" cql:"-"`
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id"`
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
IP string `json:"ip" bson:"ip" cql:"ip"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`

View File

@@ -4,6 +4,7 @@ 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
@@ -35,12 +36,13 @@ type User struct {
func (user *User) AsAPIUser() *model.User {
isEmailVerified := user.EmailVerifiedAt != nil
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
email := user.Email
createdAt := user.CreatedAt
updatedAt := user.UpdatedAt
revokedTimestamp := user.RevokedTimestamp
id := user.ID
if strings.Contains(id, Collections.WebhookLog+"/") {
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
}
return &model.User{
ID: user.ID,
ID: id,
Email: user.Email,
EmailVerified: isEmailVerified,
SignupMethods: user.SignupMethods,
@@ -48,15 +50,15 @@ func (user *User) AsAPIUser() *model.User {
FamilyName: user.FamilyName,
MiddleName: user.MiddleName,
Nickname: user.Nickname,
PreferredUsername: &email,
PreferredUsername: refs.NewStringRef(user.Email),
Gender: user.Gender,
Birthdate: user.Birthdate,
PhoneNumber: user.PhoneNumber,
PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture,
Roles: strings.Split(user.Roles, ","),
RevokedTimestamp: revokedTimestamp,
CreatedAt: &createdAt,
UpdatedAt: &updatedAt,
RevokedTimestamp: user.RevokedTimestamp,
CreatedAt: refs.NewInt64Ref(user.CreatedAt),
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
}
}

View File

@@ -1,6 +1,11 @@
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
@@ -19,23 +24,20 @@ type VerificationRequest struct {
}
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
token := v.Token
createdAt := v.CreatedAt
updatedAt := v.UpdatedAt
email := v.Email
nonce := v.Nonce
redirectURI := v.RedirectURI
expires := v.ExpiresAt
identifier := v.Identifier
id := v.ID
if strings.Contains(id, Collections.WebhookLog+"/") {
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
}
return &model.VerificationRequest{
ID: v.ID,
Token: &token,
Identifier: &identifier,
Expires: &expires,
Email: &email,
Nonce: &nonce,
RedirectURI: &redirectURI,
CreatedAt: &createdAt,
UpdatedAt: &updatedAt,
ID: id,
Token: refs.NewStringRef(v.Token),
Identifier: refs.NewStringRef(v.Identifier),
Expires: refs.NewInt64Ref(v.ExpiresAt),
Email: refs.NewStringRef(v.Email),
Nonce: refs.NewStringRef(v.Nonce),
RedirectURI: refs.NewStringRef(v.RedirectURI),
CreatedAt: refs.NewInt64Ref(v.CreatedAt),
UpdatedAt: refs.NewInt64Ref(v.UpdatedAt),
}
}

View 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 `gorm:"type:text" json:"endpoint" bson:"endpoint" cql:"endpoint"`
Headers string `gorm:"type:text" json:"headers" bson:"headers" cql:"headers"`
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
}
// 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),
}
}

View 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 `gorm:"type:text" json:"response" bson:"response" cql:"response"`
Request string `gorm:"type:text" json:"request" bson:"request" cql:"request"`
WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
}
// 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),
}
}

View File

@@ -0,0 +1,151 @@
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.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
}

View File

@@ -1,6 +1,7 @@
package arangodb
import (
"context"
"fmt"
"time"
@@ -11,15 +12,15 @@ import (
)
// 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 == "" {
env.ID = uuid.New().String()
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
configCollection, _ := p.db.Collection(nil, models.Collections.Env)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(nil), env)
configCollection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), env)
if err != nil {
return env, err
}
@@ -29,10 +30,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
// 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()
collection, _ := p.db.Collection(nil, models.Collections.Env)
meta, err := collection.UpdateDocument(nil, env.Key, env)
collection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := collection.UpdateDocument(ctx, env.Key, env)
if err != nil {
return env, err
}
@@ -43,11 +44,11 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
}
// 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
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 {
return env, err
}
@@ -60,7 +61,7 @@ func (p *provider) GetEnv() (models.Env, error) {
}
break
}
_, err := cursor.ReadDocument(nil, &env)
_, err := cursor.ReadDocument(ctx, &env)
if err != nil {
return env, err
}

View File

@@ -6,9 +6,8 @@ import (
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/http"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
)
type provider struct {
@@ -22,8 +21,9 @@ type provider struct {
// NewProvider to initialize arangodb connection
func NewProvider() (*provider, error) {
ctx := context.Background()
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)},
Endpoints: []string{dbURL},
})
if err != nil {
return nil, err
@@ -37,16 +37,16 @@ func NewProvider() (*provider, error) {
}
var arangodb driver.Database
arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
dbName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseName
arangodb_exists, err := arangoClient.DatabaseExists(nil, dbName)
if arangodb_exists {
arangodb, err = arangoClient.Database(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
arangodb, err = arangoClient.Database(nil, dbName)
if err != nil {
return nil, err
}
} else {
arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil)
arangodb, err = arangoClient.CreateDatabase(nil, dbName, nil)
if err != nil {
return nil, err
}
@@ -107,6 +107,47 @@ 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,
})
return &provider{
db: arangodb,
}, err

View File

@@ -1,7 +1,7 @@
package arangodb
import (
"fmt"
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -9,31 +9,17 @@ import (
)
// 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 == "" {
session.ID = uuid.New().String()
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection, _ := p.db.Collection(nil, models.Collections.Session)
_, err := sessionCollection.CreateDocument(nil, session)
sessionCollection, _ := p.db.Collection(ctx, models.Collections.Session)
_, err := sessionCollection.CreateDocument(ctx, session)
if err != nil {
return err
}
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
}

View File

@@ -3,32 +3,35 @@ package arangodb
import (
"context"
"fmt"
"strings"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/google/uuid"
)
// 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 == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return user, err
}
user.Roles = defaultRoles
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
userCollection, _ := p.db.Collection(nil, models.Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(nil), user)
userCollection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), user)
if err != nil {
return user, err
}
@@ -39,10 +42,10 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// 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()
collection, _ := p.db.Collection(nil, models.Collections.User)
meta, err := collection.UpdateDocument(nil, user.Key, user)
collection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := collection.UpdateDocument(ctx, user.Key, user)
if err != nil {
return user, err
}
@@ -53,24 +56,34 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
collection, _ := p.db.Collection(nil, models.Collections.User)
_, err := collection.RemoveDocument(nil, user.Key)
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
collection, _ := p.db.Collection(ctx, models.Collections.User)
_, err := collection.RemoveDocument(ctx, user.Key)
if err != nil {
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.ID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return err
}
defer cursor.Close()
return nil
}
// 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
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)
cursor, err := p.db.Query(ctx, query, nil)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
@@ -81,7 +94,7 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
for {
var user models.User
meta, err := cursor.ReadDocument(nil, &user)
meta, err := cursor.ReadDocument(ctx, &user)
if arangoDriver.IsNoMoreDocuments(err) {
break
@@ -101,7 +114,7 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// 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
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.User)
@@ -109,7 +122,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
"email": email,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return user, err
}
@@ -122,7 +135,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
break
}
_, err := cursor.ReadDocument(nil, &user)
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return user, err
}
@@ -132,7 +145,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// 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
query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", models.Collections.User)
@@ -140,7 +153,7 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
"id": id,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return user, err
}
@@ -153,7 +166,7 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
}
break
}
_, err := cursor.ReadDocument(nil, &user)
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return user, err
}

View File

@@ -12,15 +12,15 @@ import (
)
// 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 == "" {
verificationRequest.ID = uuid.New().String()
}
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
verificationRequestRequestCollection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
meta, err := verificationRequestRequestCollection.CreateDocument(nil, verificationRequest)
verificationRequestRequestCollection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest)
meta, err := verificationRequestRequestCollection.CreateDocument(ctx, verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -31,14 +31,14 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// 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
query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", models.Collections.VerificationRequest)
bindVars := map[string]interface{}{
"token": token,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return verificationRequest, err
}
@@ -51,7 +51,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
break
}
_, err := cursor.ReadDocument(nil, &verificationRequest)
_, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -61,7 +61,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
// 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
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", models.Collections.VerificationRequest)
@@ -70,7 +70,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
"identifier": identifier,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return verificationRequest, err
}
@@ -83,7 +83,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
break
}
_, err := cursor.ReadDocument(nil, &verificationRequest)
_, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -93,12 +93,12 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
// 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
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)
cursor, err := p.db.Query(ctx, query, nil)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
@@ -109,7 +109,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
for {
var verificationRequest models.VerificationRequest
meta, err := cursor.ReadDocument(nil, &verificationRequest)
meta, err := cursor.ReadDocument(ctx, &verificationRequest)
if driver.IsNoMoreDocuments(err) {
break
@@ -130,7 +130,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// 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)
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
if err != nil {

View File

@@ -0,0 +1,161 @@
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.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
}

View File

@@ -0,0 +1,75 @@
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.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
}

View 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, template, created_at, updated_at) VALUES ('%s', '%s', '%s','%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Subject, 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, 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.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, 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.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, 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.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
}

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"fmt"
"time"
@@ -10,7 +11,7 @@ import (
)
// 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 == "" {
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
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()
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
func (p *provider) GetEnv() (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
query := fmt.Sprintf("SELECT id, env, hash, created_at, updated_at FROM %s LIMIT 1", KeySpace+"."+models.Collections.Env)

View File

@@ -5,11 +5,12 @@ import (
"crypto/x509"
"fmt"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/gocql/gocql"
cansandraDriver "github.com/gocql/gocql"
)
@@ -23,15 +24,21 @@ var KeySpace string
// NewProvider to initialize arangodb connection
func NewProvider() (*provider, error) {
dbURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
if dbURL == "" {
dbURL = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseHost)
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePort) != "" {
dbURL = fmt.Sprintf("%s:%s", dbURL, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePort))
dbHost := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseHost
dbPort := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePort
if dbPort != "" && dbHost != "" {
dbURL = fmt.Sprintf("%s:%s", dbHost, dbPort)
} else if dbHost != "" {
dbURL = dbHost
}
}
KeySpace = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)
KeySpace = memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseName
if KeySpace == "" {
KeySpace = constants.EnvKeyDatabaseName
}
clusterURL := []string{}
if strings.Contains(dbURL, ",") {
clusterURL = strings.Split(dbURL, ",")
@@ -39,25 +46,31 @@ func NewProvider() (*provider, error) {
clusterURL = append(clusterURL, dbURL)
}
cassandraClient := cansandraDriver.NewCluster(clusterURL...)
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseUsername) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePassword) != "" {
dbUsername := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseUsername
dbPassword := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePassword
if dbUsername != "" && dbPassword != "" {
cassandraClient.Authenticator = &cansandraDriver.PasswordAuthenticator{
Username: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseUsername),
Password: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePassword),
Username: dbUsername,
Password: dbPassword,
}
}
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCert) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCACert) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCertKey) != "" {
certString, err := crypto.DecryptB64(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCert))
dbCert := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseCert
dbCACert := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseCACert
dbCertKey := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseCertKey
if dbCert != "" && dbCACert != "" && dbCertKey != "" {
certString, err := crypto.DecryptB64(dbCert)
if err != nil {
return nil, err
}
keyString, err := crypto.DecryptB64(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCertKey))
keyString, err := crypto.DecryptB64(dbCertKey)
if err != nil {
return nil, err
}
caString, err := crypto.DecryptB64(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseCACert))
caString, err := crypto.DecryptB64(dbCACert)
if err != nil {
return nil, err
}
@@ -84,6 +97,8 @@ func NewProvider() (*provider, error) {
NumRetries: 3,
}
cassandraClient.Consistency = gocql.LocalQuorum
cassandraClient.ConnectTimeout = 10 * time.Second
cassandraClient.ProtoVersion = 4
session, err := cassandraClient.CreateSession()
if err != nil {
@@ -128,6 +143,11 @@ func NewProvider() (*provider, error) {
if err != nil {
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)
err = session.Query(userCollectionQuery).Exec()
@@ -162,6 +182,45 @@ func NewProvider() (*provider, error) {
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;`, KeySpace, models.Collections.EmailTemplate)
err = session.Query(emailTemplateAlterQuery).Exec()
if err != nil {
return nil, err
}
return &provider{
db: session,
}, err

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"fmt"
"time"
@@ -9,7 +10,7 @@ import (
)
// 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 == "" {
session.ID = uuid.New().String()
}
@@ -24,13 +25,3 @@ func (p *provider) AddSession(session models.Session) error {
}
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
}

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"encoding/json"
"fmt"
"reflect"
@@ -9,20 +10,24 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/gocql/gocql"
"github.com/google/uuid"
)
// 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 == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return user, err
}
user.Roles = defaultRoles
}
user.CreatedAt = time.Now().Unix()
@@ -75,7 +80,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// 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()
bytes, err := json.Marshal(user)
@@ -93,10 +98,11 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
updateFields := ""
for key, value := range userMap {
if value != nil && key != "_id" {
if key == "_id" {
continue
}
if key == "_id" {
if key == "_key" {
continue
}
@@ -126,14 +132,36 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// 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 {
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, user.ID)
err := p.db.Query(query).Exec()
return err
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(pagination model.Pagination) (*model.Users, error) {
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)
@@ -167,9 +195,9 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// 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
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("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
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
@@ -178,7 +206,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// 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
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)

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"fmt"
"time"
@@ -11,7 +12,7 @@ import (
)
// 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 == "" {
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
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
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
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
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
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
paginationClone := pagination
@@ -89,7 +90,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// 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)
err := p.db.Query(query).Exec()
if err != nil {

View 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
}

View 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
}

View 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
}

View File

@@ -1,6 +1,7 @@
package mongodb
import (
"context"
"fmt"
"time"
@@ -11,7 +12,7 @@ import (
)
// 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 == "" {
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.Key = env.ID
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
_, err := configCollection.InsertOne(nil, env)
_, err := configCollection.InsertOne(ctx, env)
if err != nil {
return env, err
}
@@ -28,10 +29,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
// 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()
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 {
return env, err
}
@@ -39,14 +40,14 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
}
// 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
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 {
return env, err
}
defer cursor.Close(nil)
defer cursor.Close(ctx)
for cursor.Next(nil) {
err := cursor.Decode(&env)

View File

@@ -4,9 +4,8 @@ import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
@@ -19,7 +18,8 @@ type provider struct {
// NewProvider to initialize mongodb connection
func NewProvider() (*provider, error) {
mongodbOptions := options.Client().ApplyURI(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL))
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
mongodbOptions := options.Client().ApplyURI(dbURL)
maxWait := time.Duration(5 * time.Second)
mongodbOptions.ConnectTimeout = &maxWait
mongoClient, err := mongo.NewClient(mongodbOptions)
@@ -37,18 +37,19 @@ func NewProvider() (*provider, error) {
return nil, err
}
mongodb := mongoClient.Database(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
dbName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseName
mongodb := mongoClient.Database(dbName, options.Database())
mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection())
userCollection := mongodb.Collection(models.Collections.User, options.Collection())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
{
Keys: bson.M{"email": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
{
Keys: bson.M{"phone_number": 1},
Options: options.Index().SetUnique(true).SetSparse(true).SetPartialFilterExpression(map[string]interface{}{
"phone_number": map[string]string{"$type": "string"},
@@ -59,13 +60,13 @@ func NewProvider() (*provider, error) {
mongodb.CreateCollection(ctx, models.Collections.VerificationRequest, options.CreateCollection())
verificationRequestCollection := mongodb.Collection(models.Collections.VerificationRequest, options.Collection())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
{
Keys: bson.M{"email": 1, "identifier": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
{
Keys: bson.M{"token": 1},
Options: options.Index().SetSparse(true),
},
@@ -74,7 +75,7 @@ func NewProvider() (*provider, error) {
mongodb.CreateCollection(ctx, models.Collections.Session, options.CreateCollection())
sessionCollection := mongodb.Collection(models.Collections.Session, options.Collection())
sessionCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
{
Keys: bson.M{"user_id": 1},
Options: options.Index().SetSparse(true),
},
@@ -82,6 +83,33 @@ func NewProvider() (*provider, error) {
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())
return &provider{
db: mongodb,
}, nil

View File

@@ -1,16 +1,16 @@
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"
)
// 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 == "" {
session.ID = uuid.New().String()
}
@@ -19,17 +19,7 @@ func (p *provider) AddSession(session models.Session) error {
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
_, err := sessionCollection.InsertOne(nil, 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())
_, err := sessionCollection.InsertOne(ctx, session)
if err != nil {
return err
}

View File

@@ -1,32 +1,36 @@
package mongodb
import (
"strings"
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 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 == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return user, err
}
user.Roles = defaultRoles
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.InsertOne(nil, user)
_, err := userCollection.InsertOne(ctx, user)
if err != nil {
return user, err
}
@@ -35,10 +39,10 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// 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()
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 {
return user, err
}
@@ -46,9 +50,15 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// 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())
_, 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 {
return err
}
@@ -57,7 +67,7 @@ func (p *provider) DeleteUser(user models.User) error {
}
// 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
opts := options.Find()
opts.SetLimit(pagination.Limit)
@@ -65,23 +75,22 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
opts.SetSort(bson.M{"created_at": -1})
paginationClone := pagination
// TODO add pagination total
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 {
return nil, err
}
paginationClone.Total = count
cursor, err := userCollection.Find(nil, bson.M{}, opts)
cursor, err := userCollection.Find(ctx, bson.M{}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(nil)
defer cursor.Close(ctx)
for cursor.Next(nil) {
for cursor.Next(ctx) {
var user models.User
err := cursor.Decode(&user)
if err != nil {
@@ -97,10 +106,10 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// 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
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 {
return user, err
}
@@ -109,11 +118,11 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// 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
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 {
return user, err
}

View File

@@ -1,6 +1,7 @@
package mongodb
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -11,7 +12,7 @@ import (
)
// 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 == "" {
verificationRequest.ID = uuid.New().String()
@@ -19,7 +20,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
verificationRequest.UpdatedAt = time.Now().Unix()
verificationRequest.Key = verificationRequest.ID
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.InsertOne(nil, verificationRequest)
_, err := verificationRequestCollection.InsertOne(ctx, verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -29,11 +30,11 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// 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
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 {
return verificationRequest, err
}
@@ -42,11 +43,11 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
// 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
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 {
return verificationRequest, err
}
@@ -55,7 +56,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
// 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
opts := options.Find()
@@ -65,17 +66,17 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
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.Total = verificationRequestCollectionCount
cursor, err := verificationRequestCollection.Find(nil, bson.M{}, opts)
cursor, err := verificationRequestCollection.Find(ctx, bson.M{}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(nil)
defer cursor.Close(ctx)
for cursor.Next(nil) {
for cursor.Next(ctx) {
var verificationRequest models.VerificationRequest
err := cursor.Decode(&verificationRequest)
if err != nil {
@@ -91,9 +92,9 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// 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())
_, 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 {
return err
}

View 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
}

View 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
}

View 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
}

View File

@@ -1,6 +1,7 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -8,7 +9,7 @@ import (
)
// 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 == "" {
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
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()
return env, nil
}
// 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
return env, nil

View File

@@ -1,6 +1,7 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -8,7 +9,7 @@ import (
)
// 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 == "" {
session.ID = uuid.New().String()
}
@@ -19,6 +20,6 @@ func (p *provider) AddSession(session models.Session) error {
}
// 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
}

View File

@@ -1,24 +1,28 @@
package provider_template
import (
"strings"
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/google/uuid"
)
// 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 == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return user, err
}
user.Roles = defaultRoles
}
user.CreatedAt = time.Now().Unix()
@@ -28,30 +32,30 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// 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()
return user, nil
}
// 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
}
// 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
}
// 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
return user, nil
}
// 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
return user, nil

View File

@@ -1,6 +1,7 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -9,7 +10,7 @@ import (
)
// 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 == "" {
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
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
return verificationRequest, nil
}
// 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
return verificationRequest, nil
}
// 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
}
// 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
}

View 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
}

View 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
}

View File

@@ -1,44 +1,75 @@
package providers
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
)
type Provider interface {
// 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(user models.User) (models.User, error)
UpdateUser(ctx context.Context, user models.User) (models.User, error)
// 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(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(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(id string) (models.User, error)
GetUserByID(ctx context.Context, id string) (models.User, error)
// 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(token string) (models.VerificationRequest, error)
GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error)
// 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(pagination model.Pagination) (*model.VerificationRequests, error)
ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error)
// 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(session models.Session) error
// DeleteSession to delete session information from database
DeleteSession(userId string) error
AddSession(ctx context.Context, session models.Session) error
// 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(env models.Env) (models.Env, error)
UpdateEnv(ctx context.Context, env models.Env) (models.Env, error)
// 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
}

View 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
}

View File

@@ -1,6 +1,7 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -8,7 +9,7 @@ import (
)
// 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 == "" {
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
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()
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
func (p *provider) GetEnv() (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
result := p.db.First(&env)

View File

@@ -7,7 +7,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
@@ -41,22 +41,26 @@ func NewProvider() (*provider, error) {
TablePrefix: models.Prefix,
},
}
switch envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) {
case constants.DbTypePostgres, constants.DbTypeYugabyte:
sqlDB, err = gorm.Open(postgres.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
dbType := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseType
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
switch dbType {
case constants.DbTypePostgres, constants.DbTypeYugabyte, constants.DbTypeCockroachDB:
sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig)
case constants.DbTypeSqlite:
sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
case constants.DbTypeMysql, constants.DbTypeMariaDB:
sqlDB, err = gorm.Open(mysql.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
sqlDB, err = gorm.Open(sqlite.Open(dbURL), ormConfig)
case constants.DbTypeMysql, constants.DbTypeMariaDB, constants.DbTypePlanetScaleDB:
sqlDB, err = gorm.Open(mysql.Open(dbURL), ormConfig)
case constants.DbTypeSqlserver:
sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
sqlDB, err = gorm.Open(sqlserver.Open(dbURL), ormConfig)
}
if err != nil {
return nil, err
}
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{})
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -9,7 +10,7 @@ import (
)
// 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 == "" {
session.ID = uuid.New().String()
}
@@ -26,13 +27,3 @@ func (p *provider) AddSession(session models.Session) error {
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
result := p.db.Where("user_id = ?", userId).Delete(&models.Session{})
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -1,25 +1,29 @@
package sql
import (
"strings"
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/google/uuid"
"gorm.io/gorm/clause"
)
// 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 == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return user, err
}
user.Roles = defaultRoles
}
user.CreatedAt = time.Now().Unix()
@@ -39,7 +43,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// 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()
result := p.db.Save(&user)
@@ -52,18 +56,23 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// 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 {
result := p.db.Delete(&user)
if result.Error != nil {
return result.Error
}
result = p.db.Where("user_id = ?", user.ID).Delete(&models.Session{})
if result.Error != nil {
return result.Error
}
return nil
}
// 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 []models.User
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&users)
if result.Error != nil {
@@ -91,10 +100,9 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// 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
result := p.db.Where("email = ?", email).First(&user)
if result.Error != nil {
return user, result.Error
}
@@ -103,11 +111,10 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// 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
result := p.db.Where("id = ?", id).First(&user)
if result.Error != nil {
return user, result.Error
}

View File

@@ -1,6 +1,7 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -10,7 +11,7 @@ import (
)
// 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 == "" {
verificationRequest.ID = uuid.New().String()
}
@@ -31,7 +32,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// 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
result := p.db.Where("token = ?", token).First(&verificationRequest)
@@ -43,7 +44,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
// 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
result := p.db.Where("email = ? AND identifier = ?", email, identifier).First(&verificationRequest)
@@ -56,7 +57,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
// 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 []models.VerificationRequest
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&verificationRequests)
@@ -85,7 +86,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// 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 {
result := p.db.Delete(&verificationRequest)
if result.Error != nil {

View File

@@ -0,0 +1,104 @@
package sql
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()
res := p.db.Create(&webhook)
if res.Error != nil {
return nil, res.Error
}
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()
result := p.db.Save(&webhook)
if result.Error != nil {
return nil, result.Error
}
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
var webhooks []models.Webhook
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhooks)
if result.Error != nil {
return nil, result.Error
}
var total int64
totalRes := p.db.Model(&models.Webhook{}).Count(&total)
if totalRes.Error != nil {
return nil, totalRes.Error
}
paginationClone := pagination
paginationClone.Total = total
responseWebhooks := []*model.Webhook{}
for _, w := range webhooks {
responseWebhooks = append(responseWebhooks, w.AsAPIWebhook())
}
return &model.Webhooks{
Pagination: &paginationClone,
Webhooks: responseWebhooks,
}, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
result := p.db.Where("id = ?", webhookID).First(&webhook)
if result.Error != nil {
return nil, result.Error
}
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
result := p.db.Where("event_name = ?", eventName).First(&webhook)
if result.Error != nil {
return nil, result.Error
}
return webhook.AsAPIWebhook(), nil
}
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
result := p.db.Delete(&models.Webhook{
ID: webhook.ID,
})
if result.Error != nil {
return result.Error
}
result = p.db.Where("webhook_id = ?", webhook.ID).Delete(&models.WebhookLog{})
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -0,0 +1,68 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// 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()
res := p.db.Clauses(
clause.OnConflict{
DoNothing: true,
}).Create(&webhookLog)
if res.Error != nil {
return nil, res.Error
}
return webhookLog.AsAPIWebhookLog(), nil
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
var webhookLogs []models.WebhookLog
var result *gorm.DB
var totalRes *gorm.DB
var total int64
if webhookID != "" {
result = p.db.Where("webhook_id = ?", webhookID).Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhookLogs)
totalRes = p.db.Where("webhook_id = ?", webhookID).Model(&models.WebhookLog{}).Count(&total)
} else {
result = p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhookLogs)
totalRes = p.db.Model(&models.WebhookLog{}).Count(&total)
}
if result.Error != nil {
return nil, result.Error
}
if totalRes.Error != nil {
return nil, totalRes.Error
}
paginationClone := pagination
paginationClone.Total = total
responseWebhookLogs := []*model.WebhookLog{}
for _, w := range webhookLogs {
responseWebhookLogs = append(responseWebhookLogs, w.AsAPIWebhookLog())
}
return &model.WebhookLogs{
WebhookLogs: responseWebhookLogs,
Pagination: &paginationClone,
}, nil
}

View File

@@ -11,7 +11,7 @@ import (
gomail "gopkg.in/mail.v2"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// addEmailTemplate is used to add html template in email body
@@ -33,17 +33,57 @@ func addEmailTemplate(a string, b map[string]interface{}, templateName string) s
// SendMail function to send mail
func SendMail(to []string, Subject, bodyMessage string) error {
// dont trigger email sending in case of test
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "test" {
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
if err != nil {
return err
}
if envKey == constants.TestEnv {
return nil
}
m := gomail.NewMessage()
m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)
if err != nil {
log.Errorf("Error while getting sender email from env variable: %v", err)
return err
}
smtpPort, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySmtpPort)
if err != nil {
log.Errorf("Error while getting smtp port from env variable: %v", err)
return err
}
smtpHost, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySmtpHost)
if err != nil {
log.Errorf("Error while getting smtp host from env variable: %v", err)
return err
}
smtpUsername, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername)
if err != nil {
log.Errorf("Error while getting smtp username from env variable: %v", err)
return err
}
smtpPassword, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword)
if err != nil {
log.Errorf("Error while getting smtp password from env variable: %v", err)
return err
}
isProd, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsProd)
if err != nil {
log.Errorf("Error while getting env variable: %v", err)
return err
}
m.SetHeader("From", senderEmail)
m.SetHeader("To", to...)
m.SetHeader("Subject", Subject)
m.SetBody("text/html", bodyMessage)
port, _ := strconv.Atoi(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
d := gomail.NewDialer(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
port, _ := strconv.Atoi(smtpPort)
d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword)
if !isProd {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
if err := d.DialAndSend(m); err != nil {

View File

@@ -2,14 +2,19 @@ package email
import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// SendForgotPasswordMail to send forgot password email
func SendForgotPasswordMail(toEmail, token, hostname string) error {
resetPasswordUrl := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
resetPasswordUrl, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if err != nil {
return err
}
if resetPasswordUrl == "" {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
if err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password"); err != nil {
return err
}
}
// The receiver needs to be in slice as the receive supports multiple receiver
@@ -103,8 +108,14 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
`
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = resetPasswordUrl + "?token=" + token
message = addEmailTemplate(message, data, "reset_password_email.tmpl")

View File

@@ -4,7 +4,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// InviteEmail to send invite email
@@ -70,7 +70,7 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hi there 👋</p>
<p>Join us! You are invited to sign-up for <b>{{.org_name}}</b>. Please accept the invitation by clicking the clicking the button below.</p> <br/>
<p>Join us! You are invited to sign-up for <b>{{.org_name}}</b>. Please accept the invitation by clicking the button below.</p> <br/>
<a
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a>
</td>
@@ -99,13 +99,20 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
</html>
`
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = verificationURL + "?token=" + token + "&redirect_uri=" + redirectURI
message = addEmailTemplate(message, data, "invite_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
err := SendMail(Receiver, Subject, message)
err = SendMail(Receiver, Subject, message)
if err != nil {
log.Warn("error sending email: ", err)
}

View File

@@ -4,7 +4,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// SendVerificationMail to send verification email
@@ -99,13 +99,20 @@ func SendVerificationMail(toEmail, token, hostname string) error {
</html>
`
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = hostname + "/verify_email?token=" + token
message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
err := SendMail(Receiver, Subject, message)
err = SendMail(Receiver, Subject, message)
if err != nil {
log.Warn("error sending email: ", err)
}

707
server/env/env.go vendored
View File

@@ -2,212 +2,247 @@ package env
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"github.com/google/uuid"
"github.com/joho/godotenv"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/utils"
)
// InitRequiredEnv to initialize EnvData and through error if required env are not present
func InitRequiredEnv() error {
envPath := os.Getenv(constants.EnvKeyEnvPath)
if envPath == "" {
envPath = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnvPath)
if envPath == "" {
envPath = `.env`
}
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envPath = *envstore.ARG_ENV_FILE
}
log.Info("env path: ", envPath)
err := godotenv.Load(envPath)
if err != nil {
log.Info("using OS env instead of %s file", envPath)
}
dbURL := os.Getenv(constants.EnvKeyDatabaseURL)
dbType := os.Getenv(constants.EnvKeyDatabaseType)
dbName := os.Getenv(constants.EnvKeyDatabaseName)
dbPort := os.Getenv(constants.EnvKeyDatabasePort)
dbHost := os.Getenv(constants.EnvKeyDatabaseHost)
dbUsername := os.Getenv(constants.EnvKeyDatabaseUsername)
dbPassword := os.Getenv(constants.EnvKeyDatabasePassword)
dbCert := os.Getenv(constants.EnvKeyDatabaseCert)
dbCertKey := os.Getenv(constants.EnvKeyDatabaseCertKey)
dbCACert := os.Getenv(constants.EnvKeyDatabaseCACert)
if strings.TrimSpace(dbType) == "" {
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
dbType = strings.TrimSpace(*envstore.ARG_DB_TYPE)
}
if dbType == "" {
log.Debug("DATABASE_TYPE is not set")
return errors.New("invalid database type. DATABASE_TYPE is empty")
}
}
if strings.TrimSpace(dbURL) == "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL) == "" {
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
dbURL = strings.TrimSpace(*envstore.ARG_DB_URL)
}
if dbURL == "" && dbPort == "" && dbHost == "" && dbUsername == "" && dbPassword == "" {
log.Debug("DATABASE_URL is not set")
return errors.New("invalid database url. DATABASE_URL is required")
}
}
if dbName == "" {
if dbName == "" {
dbName = "authorizer"
}
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseHost, dbHost)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabasePort, dbPort)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseUsername, dbUsername)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabasePassword, dbPassword)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseCert, dbCert)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseCertKey, dbCertKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseCACert, dbCACert)
return nil
}
// InitEnv to initialize EnvData and through error if required env are not present
func InitAllEnv() error {
envData, err := GetEnvData()
if err != nil {
log.Info("No env data found in db, using local clone of env data")
// get clone of current store
envData = envstore.EnvStoreObj.GetEnvStoreClone()
envData, err = memorystore.Provider.GetEnvStore()
if err != nil {
log.Debug("Error while getting env data from memorystore: ", err)
return err
}
}
clientID := envData.StringEnv[constants.EnvKeyClientID]
// unique client id for each instance
if clientID == "" {
cid, ok := envData[constants.EnvKeyClientID]
clientID := ""
if !ok || cid == "" {
clientID = uuid.New().String()
envData.StringEnv[constants.EnvKeyClientID] = clientID
envData[constants.EnvKeyClientID] = clientID
} else {
clientID = cid.(string)
}
clientSecret := envData.StringEnv[constants.EnvKeyClientSecret]
// unique client id for each instance
if clientSecret == "" {
clientSecret = uuid.New().String()
envData.StringEnv[constants.EnvKeyClientSecret] = clientSecret
// unique client secret for each instance
if val, ok := envData[constants.EnvKeyClientSecret]; !ok || val != "" {
envData[constants.EnvKeyClientSecret] = uuid.New().String()
}
if envData.StringEnv[constants.EnvKeyEnv] == "" {
envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv)
if envData.StringEnv[constants.EnvKeyEnv] == "" {
envData.StringEnv[constants.EnvKeyEnv] = "production"
// os string envs
osEnv := os.Getenv(constants.EnvKeyEnv)
osAppURL := os.Getenv(constants.EnvKeyAppURL)
osAuthorizerURL := os.Getenv(constants.EnvKeyAuthorizerURL)
osPort := os.Getenv(constants.EnvKeyPort)
osAccessTokenExpiryTime := os.Getenv(constants.EnvKeyAccessTokenExpiryTime)
osAdminSecret := os.Getenv(constants.EnvKeyAdminSecret)
osSmtpHost := os.Getenv(constants.EnvKeySmtpHost)
osSmtpPort := os.Getenv(constants.EnvKeySmtpPort)
osSmtpUsername := os.Getenv(constants.EnvKeySmtpUsername)
osSmtpPassword := os.Getenv(constants.EnvKeySmtpPassword)
osSenderEmail := os.Getenv(constants.EnvKeySenderEmail)
osJwtType := os.Getenv(constants.EnvKeyJwtType)
osJwtSecret := os.Getenv(constants.EnvKeyJwtSecret)
osJwtPrivateKey := os.Getenv(constants.EnvKeyJwtPrivateKey)
osJwtPublicKey := os.Getenv(constants.EnvKeyJwtPublicKey)
osJwtRoleClaim := os.Getenv(constants.EnvKeyJwtRoleClaim)
osCustomAccessTokenScript := os.Getenv(constants.EnvKeyCustomAccessTokenScript)
osGoogleClientID := os.Getenv(constants.EnvKeyGoogleClientID)
osGoogleClientSecret := os.Getenv(constants.EnvKeyGoogleClientSecret)
osGithubClientID := os.Getenv(constants.EnvKeyGithubClientID)
osGithubClientSecret := os.Getenv(constants.EnvKeyGithubClientSecret)
osFacebookClientID := os.Getenv(constants.EnvKeyFacebookClientID)
osFacebookClientSecret := os.Getenv(constants.EnvKeyFacebookClientSecret)
osLinkedInClientID := os.Getenv(constants.EnvKeyLinkedInClientID)
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
osAppleClientID := os.Getenv(constants.EnvKeyAppleClientID)
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
// os bool vars
osDisableBasicAuthentication := os.Getenv(constants.EnvKeyDisableBasicAuthentication)
osDisableEmailVerification := os.Getenv(constants.EnvKeyDisableEmailVerification)
osDisableMagicLinkLogin := os.Getenv(constants.EnvKeyDisableMagicLinkLogin)
osDisableLoginPage := os.Getenv(constants.EnvKeyDisableLoginPage)
osDisableSignUp := os.Getenv(constants.EnvKeyDisableSignUp)
osDisableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv)
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
// os slice vars
osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins)
osRoles := os.Getenv(constants.EnvKeyRoles)
osDefaultRoles := os.Getenv(constants.EnvKeyDefaultRoles)
osProtectedRoles := os.Getenv(constants.EnvKeyProtectedRoles)
ienv, ok := envData[constants.EnvKeyEnv]
if !ok || ienv == "" {
envData[constants.EnvKeyEnv] = osEnv
if envData[constants.EnvKeyEnv] == "" {
envData[constants.EnvKeyEnv] = "production"
}
if envData.StringEnv[constants.EnvKeyEnv] == "production" {
envData.BoolEnv[constants.EnvKeyIsProd] = true
if envData[constants.EnvKeyEnv] == "production" {
envData[constants.EnvKeyIsProd] = true
} else {
envData.BoolEnv[constants.EnvKeyIsProd] = false
envData[constants.EnvKeyIsProd] = false
}
}
if envData.StringEnv[constants.EnvKeyAppURL] == "" {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
}
if envData.StringEnv[constants.EnvKeyAuthorizerURL] == "" {
envData.StringEnv[constants.EnvKeyAuthorizerURL] = os.Getenv(constants.EnvKeyAuthorizerURL)
}
if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = os.Getenv(constants.EnvKeyPort)
if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = "8080"
}
}
if envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] == "" {
envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] = os.Getenv(constants.EnvKeyAccessTokenExpiryTime)
if envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] == "" {
envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] = "30m"
}
}
if envData.StringEnv[constants.EnvKeyAdminSecret] == "" {
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
}
if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv(constants.EnvKeySmtpHost)
}
if envData.StringEnv[constants.EnvKeySmtpPort] == "" {
envData.StringEnv[constants.EnvKeySmtpPort] = os.Getenv(constants.EnvKeySmtpPort)
}
if envData.StringEnv[constants.EnvKeySmtpUsername] == "" {
envData.StringEnv[constants.EnvKeySmtpUsername] = os.Getenv(constants.EnvKeySmtpUsername)
}
if envData.StringEnv[constants.EnvKeySmtpPassword] == "" {
envData.StringEnv[constants.EnvKeySmtpPassword] = os.Getenv(constants.EnvKeySmtpPassword)
}
if envData.StringEnv[constants.EnvKeySenderEmail] == "" {
envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail)
}
algo := envData.StringEnv[constants.EnvKeyJwtType]
if algo == "" {
envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType)
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
envData.StringEnv[constants.EnvKeyJwtType] = "RS256"
algo = envData.StringEnv[constants.EnvKeyJwtType]
if osEnv != "" && osEnv != envData[constants.EnvKeyEnv] {
envData[constants.EnvKeyEnv] = osEnv
if envData[constants.EnvKeyEnv] == "production" {
envData[constants.EnvKeyIsProd] = true
} else {
algo = envData.StringEnv[constants.EnvKeyJwtType]
if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) {
log.Debug("Invalid JWT Algorithm")
return errors.New("invalid JWT_TYPE")
}
envData[constants.EnvKeyIsProd] = false
}
}
if val, ok := envData[constants.EnvKeyAppURL]; !ok || val == "" {
envData[constants.EnvKeyAppURL] = osAppURL
}
if osAppURL != "" && envData[constants.EnvKeyAppURL] != osAppURL {
envData[constants.EnvKeyAppURL] = osAppURL
}
if val, ok := envData[constants.EnvKeyAuthorizerURL]; !ok || val == "" {
envData[constants.EnvKeyAuthorizerURL] = osAuthorizerURL
}
if osAuthorizerURL != "" && envData[constants.EnvKeyAuthorizerURL] != osAuthorizerURL {
envData[constants.EnvKeyAuthorizerURL] = osAuthorizerURL
}
if val, ok := envData[constants.EnvKeyPort]; !ok || val == "" {
envData[constants.EnvKeyPort] = osPort
if envData[constants.EnvKeyPort] == "" {
envData[constants.EnvKeyPort] = "8080"
}
}
if osPort != "" && envData[constants.EnvKeyPort] != osPort {
envData[constants.EnvKeyPort] = osPort
}
if val, ok := envData[constants.EnvKeyAccessTokenExpiryTime]; !ok || val == "" {
envData[constants.EnvKeyAccessTokenExpiryTime] = osAccessTokenExpiryTime
if envData[constants.EnvKeyAccessTokenExpiryTime] == "" {
envData[constants.EnvKeyAccessTokenExpiryTime] = "30m"
}
}
if osAccessTokenExpiryTime != "" && envData[constants.EnvKeyAccessTokenExpiryTime] != osAccessTokenExpiryTime {
envData[constants.EnvKeyAccessTokenExpiryTime] = osAccessTokenExpiryTime
}
if val, ok := envData[constants.EnvKeyAdminSecret]; !ok || val == "" {
envData[constants.EnvKeyAdminSecret] = osAdminSecret
}
if osAdminSecret != "" && envData[constants.EnvKeyAdminSecret] != osAdminSecret {
envData[constants.EnvKeyAdminSecret] = osAdminSecret
}
if val, ok := envData[constants.EnvKeySmtpHost]; !ok || val == "" {
envData[constants.EnvKeySmtpHost] = osSmtpHost
}
if osSmtpHost != "" && envData[constants.EnvKeySmtpHost] != osSmtpHost {
envData[constants.EnvKeySmtpHost] = osSmtpHost
}
if val, ok := envData[constants.EnvKeySmtpPort]; !ok || val == "" {
envData[constants.EnvKeySmtpPort] = osSmtpPort
}
if osSmtpPort != "" && envData[constants.EnvKeySmtpPort] != osSmtpPort {
envData[constants.EnvKeySmtpPort] = osSmtpPort
}
if val, ok := envData[constants.EnvKeySmtpUsername]; !ok || val == "" {
envData[constants.EnvKeySmtpUsername] = osSmtpUsername
}
if osSmtpUsername != "" && envData[constants.EnvKeySmtpUsername] != osSmtpUsername {
envData[constants.EnvKeySmtpUsername] = osSmtpUsername
}
if val, ok := envData[constants.EnvKeySmtpPassword]; !ok || val == "" {
envData[constants.EnvKeySmtpPassword] = osSmtpPassword
}
if osSmtpPassword != "" && envData[constants.EnvKeySmtpPassword] != osSmtpPassword {
envData[constants.EnvKeySmtpPassword] = osSmtpPassword
}
if val, ok := envData[constants.EnvKeySenderEmail]; !ok || val == "" {
envData[constants.EnvKeySenderEmail] = osSenderEmail
}
if osSenderEmail != "" && envData[constants.EnvKeySenderEmail] != osSenderEmail {
envData[constants.EnvKeySenderEmail] = osSenderEmail
}
algoVal, ok := envData[constants.EnvKeyJwtType]
algo := ""
if !ok || algoVal == "" {
envData[constants.EnvKeyJwtType] = osJwtType
if envData[constants.EnvKeyJwtType] == "" {
envData[constants.EnvKeyJwtType] = "RS256"
algo = envData[constants.EnvKeyJwtType].(string)
}
} else {
algo = algoVal.(string)
if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) {
log.Debug("Invalid JWT Algorithm")
return errors.New("invalid JWT_TYPE")
}
}
if osJwtType != "" && osJwtType != algo {
if !crypto.IsHMACA(osJwtType) && !crypto.IsRSA(osJwtType) && !crypto.IsECDSA(osJwtType) {
log.Debug("Invalid JWT Algorithm")
return errors.New("invalid JWT_TYPE")
}
algo = osJwtType
envData[constants.EnvKeyJwtType] = osJwtType
}
if crypto.IsHMACA(algo) {
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret], _, err = crypto.NewHMACKey(algo, clientID)
if val, ok := envData[constants.EnvKeyJwtSecret]; !ok || val == "" {
envData[constants.EnvKeyJwtSecret] = osJwtSecret
if envData[constants.EnvKeyJwtSecret] == "" {
envData[constants.EnvKeyJwtSecret], _, err = crypto.NewHMACKey(algo, clientID)
if err != nil {
return err
}
}
}
if osJwtSecret != "" && envData[constants.EnvKeyJwtSecret] != osJwtSecret {
envData[constants.EnvKeyJwtSecret] = osJwtSecret
}
}
if crypto.IsRSA(algo) || crypto.IsECDSA(algo) {
privateKey, publicKey := "", ""
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey)
if val, ok := envData[constants.EnvKeyJwtPrivateKey]; !ok || val == "" {
privateKey = osJwtPrivateKey
}
if osJwtPrivateKey != "" && privateKey != osJwtPrivateKey {
privateKey = osJwtPrivateKey
}
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
publicKey = os.Getenv(constants.EnvKeyJwtPublicKey)
if val, ok := envData[constants.EnvKeyJwtPublicKey]; !ok || val == "" {
publicKey = osJwtPublicKey
}
if osJwtPublicKey != "" && publicKey != osJwtPublicKey {
publicKey = osJwtPublicKey
}
// if algo is RSA / ECDSA, then we need to have both private and public key
@@ -250,159 +285,273 @@ func InitAllEnv() error {
}
}
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey
envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey
envData[constants.EnvKeyJwtPrivateKey] = privateKey
envData[constants.EnvKeyJwtPublicKey] = publicKey
}
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv(constants.EnvKeyJwtRoleClaim)
if val, ok := envData[constants.EnvKeyJwtRoleClaim]; !ok || val == "" {
envData[constants.EnvKeyJwtRoleClaim] = osJwtRoleClaim
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = "role"
if envData[constants.EnvKeyJwtRoleClaim] == "" {
envData[constants.EnvKeyJwtRoleClaim] = "role"
}
}
if osJwtRoleClaim != "" && envData[constants.EnvKeyJwtRoleClaim] != osJwtRoleClaim {
envData[constants.EnvKeyJwtRoleClaim] = osJwtRoleClaim
}
if val, ok := envData[constants.EnvKeyCustomAccessTokenScript]; !ok || val == "" {
envData[constants.EnvKeyCustomAccessTokenScript] = osCustomAccessTokenScript
}
if osCustomAccessTokenScript != "" && envData[constants.EnvKeyCustomAccessTokenScript] != osCustomAccessTokenScript {
envData[constants.EnvKeyCustomAccessTokenScript] = osCustomAccessTokenScript
}
if val, ok := envData[constants.EnvKeyGoogleClientID]; !ok || val == "" {
envData[constants.EnvKeyGoogleClientID] = osGoogleClientID
}
if osGoogleClientID != "" && envData[constants.EnvKeyGoogleClientID] != osGoogleClientID {
envData[constants.EnvKeyGoogleClientID] = osGoogleClientID
}
if val, ok := envData[constants.EnvKeyGoogleClientSecret]; !ok || val == "" {
envData[constants.EnvKeyGoogleClientSecret] = osGoogleClientSecret
}
if osGoogleClientSecret != "" && envData[constants.EnvKeyGoogleClientSecret] != osGoogleClientSecret {
envData[constants.EnvKeyGoogleClientSecret] = osGoogleClientSecret
}
if val, ok := envData[constants.EnvKeyGithubClientID]; !ok || val == "" {
envData[constants.EnvKeyGithubClientID] = osGithubClientID
}
if osGithubClientID != "" && envData[constants.EnvKeyGithubClientID] != osGithubClientID {
envData[constants.EnvKeyGithubClientID] = osGithubClientID
}
if val, ok := envData[constants.EnvKeyGithubClientSecret]; !ok || val == "" {
envData[constants.EnvKeyGithubClientSecret] = osGithubClientSecret
}
if osGithubClientSecret != "" && envData[constants.EnvKeyGithubClientSecret] != osGithubClientSecret {
envData[constants.EnvKeyGithubClientSecret] = osGithubClientSecret
}
if val, ok := envData[constants.EnvKeyFacebookClientID]; !ok || val == "" {
envData[constants.EnvKeyFacebookClientID] = osFacebookClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyFacebookClientID] != osFacebookClientID {
envData[constants.EnvKeyFacebookClientID] = osFacebookClientID
}
if val, ok := envData[constants.EnvKeyFacebookClientSecret]; !ok || val == "" {
envData[constants.EnvKeyFacebookClientSecret] = osFacebookClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyFacebookClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyFacebookClientSecret] = osFacebookClientSecret
}
if val, ok := envData[constants.EnvKeyLinkedInClientID]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osFacebookClientID {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if val, ok := envData[constants.EnvKeyLinkedInClientSecret]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if val, ok := envData[constants.EnvKeyAppleClientID]; !ok || val == "" {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyAppleClientID] != osFacebookClientID {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if val, ok := envData[constants.EnvKeyAppleClientSecret]; !ok || val == "" {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyAppleClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
}
if osResetPasswordURL != "" && envData[constants.EnvKeyResetPasswordURL] != osResetPasswordURL {
envData[constants.EnvKeyResetPasswordURL] = osResetPasswordURL
}
if val, ok := envData[constants.EnvKeyOrganizationName]; !ok || val == "" {
envData[constants.EnvKeyOrganizationName] = osOrganizationName
}
if osOrganizationName != "" && envData[constants.EnvKeyOrganizationName] != osOrganizationName {
envData[constants.EnvKeyOrganizationName] = osOrganizationName
}
if val, ok := envData[constants.EnvKeyOrganizationLogo]; !ok || val == "" {
envData[constants.EnvKeyOrganizationLogo] = osOrganizationLogo
}
if osOrganizationLogo != "" && envData[constants.EnvKeyOrganizationLogo] != osOrganizationLogo {
envData[constants.EnvKeyOrganizationLogo] = osOrganizationLogo
}
if _, ok := envData[constants.EnvKeyDisableBasicAuthentication]; !ok {
envData[constants.EnvKeyDisableBasicAuthentication] = osDisableBasicAuthentication == "true"
}
if osDisableBasicAuthentication != "" {
boolValue, err := strconv.ParseBool(osDisableBasicAuthentication)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableBasicAuthentication].(bool) {
envData[constants.EnvKeyDisableBasicAuthentication] = boolValue
}
}
if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" {
envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript)
if _, ok := envData[constants.EnvKeyDisableEmailVerification]; !ok {
envData[constants.EnvKeyDisableEmailVerification] = osDisableEmailVerification == "true"
}
if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv(constants.EnvKeyRedisURL)
}
if envData.StringEnv[constants.EnvKeyCookieName] == "" {
envData.StringEnv[constants.EnvKeyCookieName] = os.Getenv(constants.EnvKeyCookieName)
if envData.StringEnv[constants.EnvKeyCookieName] == "" {
envData.StringEnv[constants.EnvKeyCookieName] = "authorizer"
if osDisableEmailVerification != "" {
boolValue, err := strconv.ParseBool(osDisableEmailVerification)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableEmailVerification].(bool) {
envData[constants.EnvKeyDisableEmailVerification] = boolValue
}
}
if envData.StringEnv[constants.EnvKeyGoogleClientID] == "" {
envData.StringEnv[constants.EnvKeyGoogleClientID] = os.Getenv(constants.EnvKeyGoogleClientID)
if _, ok := envData[constants.EnvKeyDisableMagicLinkLogin]; !ok {
envData[constants.EnvKeyDisableMagicLinkLogin] = osDisableMagicLinkLogin == "true"
}
if osDisableMagicLinkLogin != "" {
boolValue, err := strconv.ParseBool(osDisableMagicLinkLogin)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableMagicLinkLogin].(bool) {
envData[constants.EnvKeyDisableMagicLinkLogin] = boolValue
}
}
if envData.StringEnv[constants.EnvKeyGoogleClientSecret] == "" {
envData.StringEnv[constants.EnvKeyGoogleClientSecret] = os.Getenv(constants.EnvKeyGoogleClientSecret)
if _, ok := envData[constants.EnvKeyDisableLoginPage]; !ok {
envData[constants.EnvKeyDisableLoginPage] = osDisableLoginPage == "true"
}
if osDisableLoginPage != "" {
boolValue, err := strconv.ParseBool(osDisableLoginPage)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableLoginPage].(bool) {
envData[constants.EnvKeyDisableLoginPage] = boolValue
}
}
if envData.StringEnv[constants.EnvKeyGithubClientID] == "" {
envData.StringEnv[constants.EnvKeyGithubClientID] = os.Getenv(constants.EnvKeyGithubClientID)
if _, ok := envData[constants.EnvKeyDisableSignUp]; !ok {
envData[constants.EnvKeyDisableSignUp] = osDisableSignUp == "true"
}
if osDisableSignUp != "" {
boolValue, err := strconv.ParseBool(osDisableSignUp)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableSignUp].(bool) {
envData[constants.EnvKeyDisableSignUp] = boolValue
}
}
if envData.StringEnv[constants.EnvKeyGithubClientSecret] == "" {
envData.StringEnv[constants.EnvKeyGithubClientSecret] = os.Getenv(constants.EnvKeyGithubClientSecret)
if _, ok := envData[constants.EnvKeyDisableRedisForEnv]; !ok {
envData[constants.EnvKeyDisableRedisForEnv] = osDisableRedisForEnv == "true"
}
if osDisableRedisForEnv != "" {
boolValue, err := strconv.ParseBool(osDisableRedisForEnv)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableRedisForEnv].(bool) {
envData[constants.EnvKeyDisableRedisForEnv] = boolValue
}
}
if envData.StringEnv[constants.EnvKeyFacebookClientID] == "" {
envData.StringEnv[constants.EnvKeyFacebookClientID] = os.Getenv(constants.EnvKeyFacebookClientID)
if _, ok := envData[constants.EnvKeyDisableStrongPassword]; !ok {
envData[constants.EnvKeyDisableStrongPassword] = osDisableStrongPassword == "true"
}
if envData.StringEnv[constants.EnvKeyFacebookClientSecret] == "" {
envData.StringEnv[constants.EnvKeyFacebookClientSecret] = os.Getenv(constants.EnvKeyFacebookClientSecret)
if osDisableStrongPassword != "" {
boolValue, err := strconv.ParseBool(osDisableStrongPassword)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableStrongPassword].(bool) {
envData[constants.EnvKeyDisableStrongPassword] = boolValue
}
}
if envData.StringEnv[constants.EnvKeyResetPasswordURL] == "" {
envData.StringEnv[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(os.Getenv(constants.EnvKeyResetPasswordURL), "/")
}
envData.BoolEnv[constants.EnvKeyDisableBasicAuthentication] = os.Getenv(constants.EnvKeyDisableBasicAuthentication) == "true"
envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = os.Getenv(constants.EnvKeyDisableEmailVerification) == "true"
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv(constants.EnvKeyDisableMagicLinkLogin) == "true"
envData.BoolEnv[constants.EnvKeyDisableLoginPage] = os.Getenv(constants.EnvKeyDisableLoginPage) == "true"
envData.BoolEnv[constants.EnvKeyDisableSignUp] = os.Getenv(constants.EnvKeyDisableSignUp) == "true"
// no need to add nil check as its already done above
if envData.StringEnv[constants.EnvKeySmtpHost] == "" || envData.StringEnv[constants.EnvKeySmtpUsername] == "" || envData.StringEnv[constants.EnvKeySmtpPassword] == "" || envData.StringEnv[constants.EnvKeySenderEmail] == "" && envData.StringEnv[constants.EnvKeySmtpPort] == "" {
envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = true
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
if envData[constants.EnvKeySmtpHost] == "" || envData[constants.EnvKeySmtpUsername] == "" || envData[constants.EnvKeySmtpPassword] == "" || envData[constants.EnvKeySenderEmail] == "" && envData[constants.EnvKeySmtpPort] == "" {
envData[constants.EnvKeyDisableEmailVerification] = true
envData[constants.EnvKeyDisableMagicLinkLogin] = true
}
if envData.BoolEnv[constants.EnvKeyDisableEmailVerification] {
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
if envData[constants.EnvKeyDisableEmailVerification].(bool) {
envData[constants.EnvKeyDisableMagicLinkLogin] = true
}
allowedOriginsSplit := strings.Split(os.Getenv(constants.EnvKeyAllowedOrigins), ",")
allowedOrigins := []string{}
hasWildCard := false
if val, ok := envData[constants.EnvKeyAllowedOrigins]; !ok || val == "" {
envData[constants.EnvKeyAllowedOrigins] = osAllowedOrigins
if envData[constants.EnvKeyAllowedOrigins] == "" {
envData[constants.EnvKeyAllowedOrigins] = "*"
}
}
if osAllowedOrigins != "" && envData[constants.EnvKeyAllowedOrigins] != osAllowedOrigins {
envData[constants.EnvKeyAllowedOrigins] = osAllowedOrigins
}
for _, val := range allowedOriginsSplit {
trimVal := strings.TrimSpace(val)
if trimVal != "" {
if trimVal != "*" {
host, port := utils.GetHostParts(trimVal)
allowedOrigins = append(allowedOrigins, host+":"+port)
} else {
hasWildCard = true
allowedOrigins = append(allowedOrigins, trimVal)
break
}
if val, ok := envData[constants.EnvKeyRoles]; !ok || val == "" {
envData[constants.EnvKeyRoles] = osRoles
if envData[constants.EnvKeyRoles] == "" {
envData[constants.EnvKeyRoles] = "user"
}
}
if osRoles != "" && envData[constants.EnvKeyRoles] != osRoles {
envData[constants.EnvKeyRoles] = osRoles
}
roles := strings.Split(envData[constants.EnvKeyRoles].(string), ",")
if val, ok := envData[constants.EnvKeyDefaultRoles]; !ok || val == "" {
envData[constants.EnvKeyDefaultRoles] = osDefaultRoles
if envData[constants.EnvKeyDefaultRoles] == "" {
envData[constants.EnvKeyDefaultRoles] = "user"
}
}
if osDefaultRoles != "" && envData[constants.EnvKeyDefaultRoles] != osDefaultRoles {
envData[constants.EnvKeyDefaultRoles] = osDefaultRoles
}
defaultRoles := strings.Split(envData[constants.EnvKeyDefaultRoles].(string), ",")
if len(defaultRoles) == 0 {
defaultRoles = []string{roles[0]}
}
for _, role := range defaultRoles {
if !utils.StringSliceContains(roles, role) {
return fmt.Errorf("Default role %s is not defined in roles", role)
}
}
if len(allowedOrigins) > 1 && hasWildCard {
allowedOrigins = []string{"*"}
if val, ok := envData[constants.EnvKeyProtectedRoles]; !ok || val == "" {
envData[constants.EnvKeyProtectedRoles] = osProtectedRoles
}
if osProtectedRoles != "" && envData[constants.EnvKeyProtectedRoles] != osProtectedRoles {
envData[constants.EnvKeyProtectedRoles] = osProtectedRoles
}
if len(allowedOrigins) == 0 {
allowedOrigins = []string{"*"}
err = memorystore.Provider.UpdateEnvStore(envData)
if err != nil {
log.Debug("Error while updating env store: ", err)
return err
}
envData.SliceEnv[constants.EnvKeyAllowedOrigins] = allowedOrigins
rolesEnv := strings.TrimSpace(os.Getenv(constants.EnvKeyRoles))
rolesSplit := strings.Split(rolesEnv, ",")
roles := []string{}
if len(rolesEnv) == 0 {
roles = []string{"user"}
}
defaultRolesEnv := strings.TrimSpace(os.Getenv(constants.EnvKeyDefaultRoles))
defaultRoleSplit := strings.Split(defaultRolesEnv, ",")
defaultRoles := []string{}
if len(defaultRolesEnv) == 0 {
defaultRoles = []string{"user"}
}
protectedRolesEnv := strings.TrimSpace(os.Getenv(constants.EnvKeyProtectedRoles))
protectedRolesSplit := strings.Split(protectedRolesEnv, ",")
protectedRoles := []string{}
if len(protectedRolesEnv) > 0 {
for _, val := range protectedRolesSplit {
trimVal := strings.TrimSpace(val)
protectedRoles = append(protectedRoles, trimVal)
}
}
for _, val := range rolesSplit {
trimVal := strings.TrimSpace(val)
if trimVal != "" {
roles = append(roles, trimVal)
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
defaultRoles = append(defaultRoles, trimVal)
}
}
}
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
log.Debug("Default roles not found in roles list. It can be one from ROLES only")
return errors.New(`invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
}
envData.SliceEnv[constants.EnvKeyRoles] = roles
envData.SliceEnv[constants.EnvKeyDefaultRoles] = defaultRoles
envData.SliceEnv[constants.EnvKeyProtectedRoles] = protectedRoles
if os.Getenv(constants.EnvKeyOrganizationName) != "" {
envData.StringEnv[constants.EnvKeyOrganizationName] = os.Getenv(constants.EnvKeyOrganizationName)
}
if os.Getenv(constants.EnvKeyOrganizationLogo) != "" {
envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv(constants.EnvKeyOrganizationLogo)
}
envstore.EnvStoreObj.UpdateEnvStore(envData)
return nil
}

View File

@@ -1,8 +1,10 @@
package env
import (
"context"
"encoding/json"
"os"
"reflect"
"strconv"
"strings"
@@ -13,14 +15,52 @@ import (
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/utils"
)
func fixBackwardCompatibility(data map[string]interface{}) (bool, map[string]interface{}) {
result := data
// check if env data is stored in older format
hasOlderFormat := false
if _, ok := result["bool_env"]; ok {
for key, value := range result["bool_env"].(map[string]interface{}) {
result[key] = value
}
hasOlderFormat = true
delete(result, "bool_env")
}
if _, ok := result["string_env"]; ok {
for key, value := range result["string_env"].(map[string]interface{}) {
result[key] = value
}
hasOlderFormat = true
delete(result, "string_env")
}
if _, ok := result["slice_env"]; ok {
for key, value := range result["slice_env"].(map[string]interface{}) {
typeOfValue := reflect.TypeOf(value)
if strings.Contains(typeOfValue.String(), "[]string") {
result[key] = strings.Join(value.([]string), ",")
}
if strings.Contains(typeOfValue.String(), "[]interface") {
result[key] = strings.Join(utils.ConvertInterfaceToStringSlice(value), ",")
}
}
hasOlderFormat = true
delete(result, "slice_env")
}
return hasOlderFormat, result
}
// GetEnvData returns the env data from database
func GetEnvData() (envstore.Store, error) {
var result envstore.Store
env, err := db.Provider.GetEnv()
func GetEnvData() (map[string]interface{}, error) {
var result map[string]interface{}
ctx := context.Background()
env, err := db.Provider.GetEnv(ctx)
// config not found in db
if err != nil {
log.Debug("Error while getting env data from db: ", err)
@@ -34,7 +74,7 @@ func GetEnvData() (envstore.Store, error) {
return result, err
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
@@ -54,20 +94,42 @@ func GetEnvData() (envstore.Store, error) {
return result, err
}
hasOlderFormat, result := fixBackwardCompatibility(result)
if hasOlderFormat {
err = memorystore.Provider.UpdateEnvStore(result)
if err != nil {
log.Debug("Error while updating env store: ", err)
return result, err
}
}
return result, err
}
// PersistEnv persists the environment variables to the database
func PersistEnv() error {
env, err := db.Provider.GetEnv()
ctx := context.Background()
env, err := db.Provider.GetEnv(ctx)
// config not found in db
if err != nil {
if err != nil || env.EnvData == "" {
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
hash := uuid.New().String()[:36-4]
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, hash)
if err != nil {
log.Debug("Error while updating encryption env variable: ", err)
return err
}
encodedHash := crypto.EncryptB64(hash)
encryptedConfig, err := crypto.EncryptEnvData(envstore.EnvStoreObj.GetEnvStoreClone())
res, err := memorystore.Provider.GetEnvStore()
if err != nil {
log.Debug("Error while getting env store: ", err)
return err
}
encryptedConfig, err := crypto.EncryptEnvData(res)
if err != nil {
log.Debug("Error while encrypting env data: ", err)
return err
@@ -78,7 +140,7 @@ func PersistEnv() error {
EnvData: encryptedConfig,
}
env, err = db.Provider.AddEnv(env)
env, err = db.Provider.AddEnv(ctx, env)
if err != nil {
log.Debug("Error while persisting env data to db: ", err)
return err
@@ -93,7 +155,7 @@ func PersistEnv() error {
return err
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
@@ -108,83 +170,81 @@ func PersistEnv() error {
}
// temp store variable
var storeData envstore.Store
storeData := map[string]interface{}{}
err = json.Unmarshal(decryptedConfigs, &storeData)
if err != nil {
log.Debug("Error while unmarshalling env data: ", err)
log.Debug("Error while un-marshalling env data: ", err)
return err
}
hasOlderFormat, result := fixBackwardCompatibility(storeData)
if hasOlderFormat {
err = memorystore.Provider.UpdateEnvStore(result)
if err != nil {
log.Debug("Error while updating env store: ", err)
return err
}
}
// if env is changed via env file or OS env
// give that higher preference and update db, but we don't recommend it
hasChanged := false
for key, value := range storeData.StringEnv {
for key, value := range storeData {
// don't override unexposed envs
// check only for derivative keys
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
// as we have removed it from json
if key != constants.EnvKeyEncryptionKey {
// check only for derivative keys
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
// as we have removed it from json
envValue := strings.TrimSpace(os.Getenv(key))
// env is not empty
if envValue != "" {
if value != envValue {
storeData.StringEnv[key] = envValue
hasChanged = true
switch key {
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword:
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool {
storeData[key] = envValueBool
hasChanged = true
}
}
default:
if value != nil && value.(string) != envValue {
storeData[key] = envValue
hasChanged = true
}
}
}
}
}
for key, value := range storeData.BoolEnv {
envValue := strings.TrimSpace(os.Getenv(key))
// env is not empty
if envValue != "" {
envValueBool, _ := strconv.ParseBool(envValue)
if value != envValueBool {
storeData.BoolEnv[key] = envValueBool
hasChanged = true
}
}
}
for key, value := range storeData.SliceEnv {
envValue := strings.TrimSpace(os.Getenv(key))
// env is not empty
if envValue != "" {
envStringArr := strings.Split(envValue, ",")
if !utils.IsStringArrayEqual(value, envStringArr) {
storeData.SliceEnv[key] = envStringArr
hasChanged = true
}
}
}
// handle derivative cases like disabling email verification & magic login
// in case SMTP is off but env is set to true
if storeData.StringEnv[constants.EnvKeySmtpHost] == "" || storeData.StringEnv[constants.EnvKeySmtpUsername] == "" || storeData.StringEnv[constants.EnvKeySmtpPassword] == "" || storeData.StringEnv[constants.EnvKeySenderEmail] == "" && storeData.StringEnv[constants.EnvKeySmtpPort] == "" {
if !storeData.BoolEnv[constants.EnvKeyDisableEmailVerification] {
storeData.BoolEnv[constants.EnvKeyDisableEmailVerification] = true
if storeData[constants.EnvKeySmtpHost] == "" || storeData[constants.EnvKeySmtpUsername] == "" || storeData[constants.EnvKeySmtpPassword] == "" || storeData[constants.EnvKeySenderEmail] == "" && storeData[constants.EnvKeySmtpPort] == "" {
if !storeData[constants.EnvKeyDisableEmailVerification].(bool) {
storeData[constants.EnvKeyDisableEmailVerification] = true
hasChanged = true
}
if !storeData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] {
storeData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
if !storeData[constants.EnvKeyDisableMagicLinkLogin].(bool) {
storeData[constants.EnvKeyDisableMagicLinkLogin] = true
hasChanged = true
}
}
envstore.EnvStoreObj.UpdateEnvStore(storeData)
err = memorystore.Provider.UpdateEnvStore(storeData)
if err != nil {
log.Debug("Error while updating env store: ", err)
return err
}
jwk, err := crypto.GenerateJWKBasedOnEnv()
if err != nil {
log.Debug("Error while generating JWK: ", err)
return err
}
// updating jwk
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJWK, jwk)
if hasChanged {
encryptedConfig, err := crypto.EncryptEnvData(storeData)
@@ -194,7 +254,7 @@ func PersistEnv() error {
}
env.EnvData = encryptedConfig
_, err = db.Provider.UpdateEnv(env)
_, err = db.Provider.UpdateEnv(ctx, env)
if err != nil {
log.Debug("Failed to Update Config: ", err)
return err

View File

@@ -1,122 +0,0 @@
package envstore
import (
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
var (
// ARG_DB_URL is the cli arg variable for the database url
ARG_DB_URL *string
// ARG_DB_TYPE is the cli arg variable for the database type
ARG_DB_TYPE *string
// ARG_ENV_FILE is the cli arg variable for the env file
ARG_ENV_FILE *string
// ARG_LOG_LEVEL is the cli arg variable for the log level
ARG_LOG_LEVEL *string
)
// Store data structure
type Store struct {
StringEnv map[string]string `json:"string_env"`
BoolEnv map[string]bool `json:"bool_env"`
SliceEnv map[string][]string `json:"slice_env"`
}
// EnvStore struct
type EnvStore struct {
mutex sync.Mutex
store *Store
}
var defaultStore = &EnvStore{
store: &Store{
StringEnv: map[string]string{
constants.EnvKeyAdminCookieName: "authorizer-admin",
constants.EnvKeyJwtRoleClaim: "role",
constants.EnvKeyOrganizationName: "Authorizer",
constants.EnvKeyOrganizationLogo: "https://www.authorizer.dev/images/logo.png",
},
BoolEnv: map[string]bool{
constants.EnvKeyDisableBasicAuthentication: false,
constants.EnvKeyDisableMagicLinkLogin: false,
constants.EnvKeyDisableEmailVerification: false,
constants.EnvKeyDisableLoginPage: false,
constants.EnvKeyDisableSignUp: false,
},
SliceEnv: map[string][]string{},
},
}
// EnvStoreObj.GetBoolStoreEnvVariable global variable for EnvStore
var EnvStoreObj = defaultStore
// UpdateEnvStore to update the whole env store object
func (e *EnvStore) UpdateEnvStore(store Store) {
e.mutex.Lock()
defer e.mutex.Unlock()
// just override the keys + new keys
for key, value := range store.StringEnv {
e.store.StringEnv[key] = value
}
for key, value := range store.BoolEnv {
e.store.BoolEnv[key] = value
}
for key, value := range store.SliceEnv {
e.store.SliceEnv[key] = value
}
}
// UpdateEnvVariable to update the particular env variable
func (e *EnvStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) {
e.mutex.Lock()
defer e.mutex.Unlock()
switch storeIdentifier {
case constants.StringStoreIdentifier:
e.store.StringEnv[key] = value.(string)
case constants.BoolStoreIdentifier:
e.store.BoolEnv[key] = value.(bool)
case constants.SliceStoreIdentifier:
e.store.SliceEnv[key] = value.([]string)
}
}
// GetStringStoreEnvVariable to get the env variable from string store object
func (e *EnvStore) GetStringStoreEnvVariable(key string) string {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.StringEnv[key]
}
// GetBoolStoreEnvVariable to get the env variable from bool store object
func (e *EnvStore) GetBoolStoreEnvVariable(key string) bool {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.BoolEnv[key]
}
// GetSliceStoreEnvVariable to get the env variable from slice store object
func (e *EnvStore) GetSliceStoreEnvVariable(key string) []string {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.SliceEnv[key]
}
// GetEnvStoreClone to get clone of current env store object
func (e *EnvStore) GetEnvStoreClone() Store {
e.mutex.Lock()
defer e.mutex.Unlock()
result := *e.store
return result
}
func (e *EnvStore) ResetStore() {
e.mutex.Lock()
defer e.mutex.Unlock()
e.store = defaultStore.store
}

Some files were not shown because too many files have changed in this diff Show More