Compare commits

...

912 Commits

Author SHA1 Message Date
Stepan Vladovskiy
5758fe1e2c feat: Dockerfile with big ASSSsssss
All checks were successful
deploy / deploy (push) Successful in 36s
2025-05-14 15:35:07 -03:00
Stepan Vladovskiy
9c27d7fcab restart branch
All checks were successful
deploy / deploy (push) Successful in 36s
2025-05-14 15:28:50 -03:00
Stepan Vladovskiy
282c700431 restart branch 2025-05-14 15:27:33 -03:00
acc471e500 log-error-more
All checks were successful
deploy / deploy (push) Successful in 7s
2024-12-11 22:40:26 +03:00
586b2cc3af deploy
All checks were successful
deploy / deploy (push) Successful in 1m44s
2024-06-06 09:46:38 +03:00
6a4b3553af premerge 2024-06-06 09:44:19 +03:00
48e1dbf8aa get-id-debug
All checks were successful
deploy / deploy (push) Successful in 1m29s
2024-06-06 07:51:45 +03:00
55121656a8 get-id
All checks were successful
deploy / deploy (push) Successful in 1m28s
2024-06-05 23:47:41 +03:00
e9f54a74f0 get-id
All checks were successful
deploy / deploy (push) Successful in 1m26s
2024-06-05 23:40:09 +03:00
bfa192d21e log-author-id
All checks were successful
deploy / deploy (push) Successful in 1m27s
2024-06-05 23:20:37 +03:00
800fc4f8d8 fixed-redis-intfix4
All checks were successful
deploy / deploy (push) Successful in 1m27s
2024-06-05 22:17:18 +03:00
86453d483e fixed-redis-intfix2-вуигп
All checks were successful
deploy / deploy (push) Successful in 1m27s
2024-06-05 22:15:03 +03:00
7d5d31692f fixed-redis-intfix2
All checks were successful
deploy / deploy (push) Successful in 1m27s
2024-06-05 22:10:43 +03:00
5745a9b3c4 fixed-redis-intfix
Some checks failed
deploy / deploy (push) Failing after 1m4s
2024-06-05 22:07:06 +03:00
641c5bc248 fixed-redis-debug
All checks were successful
deploy / deploy (push) Successful in 1m31s
2024-06-05 22:01:24 +03:00
085faf73e7 fixed-redis-cache-5
All checks were successful
deploy / deploy (push) Successful in 1m27s
2024-06-05 21:53:59 +03:00
3197154fec fixed-redis-cache-3
All checks were successful
deploy / deploy (push) Successful in 1m27s
2024-06-05 21:32:10 +03:00
b8f5e9ebb9 fixed-redis-cache-2
Some checks failed
deploy / deploy (push) Failing after 1m3s
2024-06-05 21:27:51 +03:00
76f976937a fixed-redis-cache
Some checks failed
deploy / deploy (push) Failing after 1m3s
2024-06-05 21:20:36 +03:00
693f252ae9 redis-key 2024-05-30 18:34:25 +03:00
12af7fc617 reimplement-following-data-proxy-4
All checks were successful
deploy / deploy (push) Successful in 1m29s
2024-05-21 03:38:56 +03:00
32eaff5c5d reimplement-following-data-proxy-3
Some checks failed
deploy / deploy (push) Failing after 29s
2024-05-21 03:37:14 +03:00
0b823ffc33 reimplement-following-data-proxy-2
Some checks failed
deploy / deploy (push) Failing after 1m3s
2024-05-21 03:31:06 +03:00
42017035fc reimplement-following-data-proxy
Some checks failed
deploy / deploy (push) Failing after 1m7s
2024-05-21 03:25:15 +03:00
c18209418f postmerge
All checks were successful
deploy / deploy (push) Successful in 1m34s
2024-05-21 03:10:43 +03:00
c8ba8c1453 yml-fix
Some checks failed
deploy / deploy (push) Failing after 5s
2024-05-21 03:03:59 +03:00
04a8e292c0 Merge branch 'main' into discours
Some checks failed
deploy / deploy (push) Failing after 6s
2024-05-21 03:00:07 +03:00
Lakhan Samani
9336d3e003
Merge pull request #455 from testwill/ioutil
chore: remove refs to deprecated io/ioutil
2024-05-20 18:23:40 +05:30
guoguangwu
c6f19a82a4 chore: remove refs to deprecated io/ioutil
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
2024-05-18 15:02:45 +08:00
Lakhan Samani
28390ebbe4
fix: social login state 2024-05-08 09:56:06 +05:30
Lakhan Samani
5add964449
Merge pull request #453 from authorizerdev/feat-add-roblox-login
Add roblox login feature
2024-05-01 18:17:17 +05:30
Lakhan Samani
4eb8ca0d65
update roblox ui 2024-05-01 17:40:11 +05:30
Lakhan Samani
d924e36786
feat: fix roblox image 2024-05-01 17:04:33 +05:30
Lakhan Samani
d0e6392f1a
fix roblox oauth 2024-05-01 16:36:21 +05:30
Lakhan Samani
b931652769
Add roblox login feature 2024-04-30 13:13:27 +05:30
fb65fcbad5 deploy
Some checks failed
deploy / deploy (push) Failing after 6s
2024-04-15 14:12:12 +03:00
79d2d52fc0 deploy
Some checks failed
deploy / deploy (push) Failing after 5s
2024-04-15 09:33:44 +03:00
2a766d587f branch 2024-04-15 09:32:16 +03:00
5c33b159dc Merge remote-tracking branch 'hub/main' into discours 2024-04-15 09:31:44 +03:00
Stepan Vladovskiy
ca0118df5e debug: no force in push to staging
All checks were successful
deploy / deploy (push) Successful in 29s
2024-04-11 17:16:39 -03:00
Stepan Vladovskiy
8eb9650142 feat: merge with mailgun
All checks were successful
deploy / deploy (push) Successful in 1m32s
2024-04-11 17:14:25 -03:00
Stepan Vladovskiy
cfaf6c49b4 feat: CI mailgun->v2 and dev->staging
All checks were successful
deploy / deploy (push) Successful in 50s
2024-04-11 17:10:55 -03:00
Stepan Vladovskiy
4990a77786 feat: CI mailgun->v2 and dev->staging
Some checks failed
deploy / deploy (push) Failing after 12s
2024-04-11 17:09:38 -03:00
Lakhan Samani
82f639757f
Merge pull request #450 from authorizerdev/fix-user-verification
fix: user verification
2024-04-02 15:39:48 +05:30
Lakhan Samani
28b574c827
Update tests 2024-04-02 15:25:11 +05:30
Lakhan Samani
67f866a787
fix: user verification 2024-03-28 13:57:47 +05:30
Stepan Vladovskiy
2420297c20 feat: branch dev with CI to deploy dev in staging.discour.io
Some checks failed
deploy / deploy (push) Failing after 1m39s
2024-03-18 00:12:17 -03:00
5616186be7 cache-fix 2024-03-12 15:51:49 +03:00
4351f5cd32 type-fix-8 2024-03-02 14:51:37 +03:00
29026ed6fb type-fix-7 2024-03-02 14:44:34 +03:00
f244330401 type-fix-6 2024-03-02 14:40:40 +03:00
132f965c8b type-fix-5 2024-03-02 14:35:40 +03:00
8deba4849d type-fix-4 2024-03-02 14:22:27 +03:00
3a3407f85e type-fix-3 2024-03-02 14:14:54 +03:00
7a1df30325 type-fix 2024-03-02 14:06:20 +03:00
d9fd6c2b36 type-fix 2024-03-02 13:56:57 +03:00
11472d5b06 logs-fix-14 2024-03-02 13:48:10 +03:00
3502d8b3d1 logs-fix-14 2024-03-02 13:46:58 +03:00
7c949417c1 logs-fix-13 2024-03-02 13:41:23 +03:00
09e4daf66e logs-fix-12 2024-03-02 13:34:00 +03:00
32f903f40a logs-fix-11 2024-03-02 13:28:20 +03:00
348a9426cc logs-fix-10 2024-03-02 13:22:19 +03:00
9ddbfb5df7 logs-fix-9 2024-03-02 13:18:22 +03:00
9fa248ca41 logs-fix-9 2024-03-02 13:17:57 +03:00
0b0ab12bf0 logs-fix-8 2024-03-02 13:13:20 +03:00
be58bb8c6d logs-fix-7 2024-03-02 13:08:48 +03:00
cdad58bd02 logs-fix-6 2024-03-02 12:58:39 +03:00
934f433dd2 logs-fix-5 2024-03-02 12:55:46 +03:00
d6fec9121c logs-fix-4 2024-03-02 12:51:26 +03:00
f9e1316145 logs-fix-3 2024-03-02 12:47:06 +03:00
362d410e37 logs-fix-2 2024-03-02 12:35:12 +03:00
78930dc8f3 logs-fix 2024-03-02 11:56:24 +03:00
baeb5a68ea appdata-debug-4 2024-03-02 10:19:56 +03:00
040f7ead7e appdata-debug-3 2024-03-02 10:12:05 +03:00
3246d32a23 appdata-debug-2 2024-03-02 10:08:38 +03:00
ce5b09953e appdata-debug 2024-03-02 09:56:48 +03:00
31079f2628 redis-provider-fix
Some checks failed
deploy / deploy (push) Failing after 5s
2024-02-22 11:51:20 +03:00
8fca4cf4c0 appdata-patch
Some checks failed
deploy / deploy (push) Failing after 5s
2024-02-22 10:40:39 +03:00
94917e7735 login-patched
Some checks failed
deploy / deploy (push) Failing after 5s
2024-02-22 09:13:56 +03:00
0f9b95cafe core-api-redis
Some checks failed
deploy / deploy (push) Failing after 5s
2024-02-22 00:33:24 +03:00
871c65b9dd app-data-patch
Some checks failed
deploy / deploy (push) Failing after 5s
2024-02-22 00:29:46 +03:00
2487077adc Merge branch 'feature/discoursio-auth' into mailgun
All checks were successful
deploy / deploy (push) Successful in 5s
2024-02-17 14:23:28 +03:00
7c03b383f1 Merge branch 'mailgun' of https://dev.discours.io/discours.io/authorizer into mailgun 2024-02-17 14:23:23 +03:00
e0db7c1051 ci 2024-02-17 14:21:45 +03:00
19f5e7e174 forgot-resolver-patch
Some checks failed
deploy / deploy (push) Failing after 5s
2024-02-08 19:18:31 +03:00
02deafa879 magic-lnk-login-only
All checks were successful
deploy / deploy (push) Successful in 1m35s
2024-02-08 19:04:24 +03:00
42efbc232b check-not-basic
All checks were successful
deploy / deploy (push) Successful in 1m35s
2024-02-08 18:54:44 +03:00
7bc9fca4c4 magic
All checks were successful
deploy / deploy (push) Successful in 1m37s
2024-02-08 18:05:08 +03:00
b20f5986c1 is-registered-debug
All checks were successful
deploy / deploy (push) Successful in 1m33s
2024-02-06 12:25:40 +03:00
a66393e754 resolver-fix
All checks were successful
deploy / deploy (push) Successful in 1m32s
2024-02-06 12:20:01 +03:00
e963f325f8 is-registered-hotfix-4
All checks were successful
deploy / deploy (push) Successful in 1m31s
2024-02-06 12:12:34 +03:00
570436e6e8 schema-regen
All checks were successful
deploy / deploy (push) Successful in 1m25s
2024-02-06 12:03:42 +03:00
c4472340ef is-registered-hotfix-3
All checks were successful
deploy / deploy (push) Successful in 32s
2024-02-06 11:38:45 +03:00
7a6ae24aaa is-registered-hotfix-2
All checks were successful
deploy / deploy (push) Successful in 1m29s
2024-02-06 11:35:48 +03:00
db25ceb0b5 is-registered-hotfix
All checks were successful
deploy / deploy (push) Successful in 1m29s
2024-02-06 11:34:00 +03:00
40b8e020a3 fixfix
All checks were successful
deploy / deploy (push) Successful in 1m30s
2024-02-02 16:06:57 +03:00
4b249d643f is-registered-fix-2
Some checks failed
deploy / deploy (push) Failing after 1m2s
2024-02-01 16:37:49 +03:00
41f99d1199 Merge branch 'mailgun' of https://dev.discours.io/discours.io/authorizer into mailgun
All checks were successful
deploy / deploy (push) Successful in 1m30s
2024-02-01 16:22:28 +03:00
83bb18ae79 is-registered-fix 2024-02-01 16:21:53 +03:00
to
416640ac13 Update server/graph/schema.graphqls
All checks were successful
deploy / deploy (push) Successful in 1m28s
2024-02-01 10:35:11 +00:00
Stepan Vladovskii
853f8c44bb feat: add CI pipline to mailgun brunch with check is this branch is mailgun
All checks were successful
deploy / deploy (push) Successful in 1m33s
2024-01-28 19:33:27 -03:00
bdcf2c39f7 logs-fix-4 2024-01-22 15:02:22 +03:00
c53ada95eb logs-fix-3 2024-01-22 14:49:58 +03:00
07a26991e9 logs-fix-2 2024-01-22 14:32:27 +03:00
c8413665ae logs-fix 2024-01-22 14:29:32 +03:00
69a87896e9 Merge branch 'main' of https://github.com/authorizerdev/authorizer into mailgun 2024-01-22 13:48:09 +03:00
Lakhan Samani
27b51ad369
Merge pull request #436 from team-scaletech/feat/totp_for_signup
Feat/totp for signup
2024-01-19 21:26:35 +05:30
Lakhan Samani
747c82f1b9
Merge pull request #439 from team-scaletech/fix/role-deletion
role deletion
2024-01-19 21:25:36 +05:30
Lakhan Samani
b1bfaf6688
Merge pull request #443 from cosark/patch-1
Added one-click deployment option to readme table
2024-01-19 21:23:41 +05:30
034d80303f Merge remote-tracking branch 'hub/main' into mailgun 2024-01-18 18:50:16 +03:00
cosark
a328121aa3
Added one-click deployment option to readme table
Adding RepoCloud.io as a 1-click deployment options for Authorizer instances. Provides diversity and a user-friendly deployment option.
2024-01-12 02:15:04 -07:00
lemonScaletech
40a0a2fbcc feat:
* added chunks of 1000 for deletion of role
2024-01-10 12:56:30 +05:30
lemonScaletech
e52164665f Merge branch 'main' into fix/role-deletion 2024-01-10 12:30:47 +05:30
Anand Kumar Panigrahi
a63d00b0c8
Merge branch 'authorizerdev:main' into main 2024-01-10 12:23:03 +05:30
Lakhan Samani
0bce901749 remove debug logs 2024-01-08 14:28:23 +05:30
Lakhan Samani
e5fbaa26e1 fix: pkce flow for oauth login 2024-01-08 14:21:24 +05:30
bb62b4adfd templates-fix 2024-01-05 20:35:58 +03:00
d47b39bb1d link-fix 2024-01-05 19:43:45 +03:00
0aa1c2532b debug-link-2 2024-01-05 19:29:50 +03:00
0ab26c19b6 debug-link 2024-01-05 19:27:59 +03:00
b8aade7dc0 sdk-fix 2024-01-05 19:17:55 +03:00
6e0ab799b8 bytes-fix 2024-01-05 18:56:13 +03:00
2ccc2ae4eb mailgun-debug 2024-01-05 18:51:39 +03:00
95807ae319 monkey-fix 2024-01-05 18:45:13 +03:00
e62f356a79 debug-mailgun 2024-01-05 18:26:58 +03:00
0331d34afc mailgun-response-debug 2024-01-05 18:04:19 +03:00
5af71dfc94 template-names-fix 2024-01-05 16:44:37 +03:00
dd4b41674a is-registered-1 2024-01-05 14:58:16 +03:00
58a91814ae minor-fix 2024-01-05 14:17:56 +03:00
2fba6af769 t 2024-01-05 14:09:02 +03:00
ee454aeabe first-only 2024-01-05 14:00:39 +03:00
896e890421 less-bloat-2 2024-01-05 13:19:46 +03:00
c5aaad0662 less-bloat 2024-01-05 13:15:22 +03:00
68c761a181 fixed-imports 2024-01-05 13:12:13 +03:00
f30881de3d mailgun-rest 2024-01-05 13:06:43 +03:00
534b5624af sha256-fix 2024-01-05 01:41:52 +03:00
be88e231e0 imports-fix 2024-01-04 23:53:20 +03:00
1f3cb1aab9 universal-hashing-sha256 2024-01-04 22:15:22 +03:00
scaletech-milan
cb01dea902 Refactor:
- Remove redundant mail otp check
2024-01-02 12:18:51 +05:30
lemonScaletech
7bcd5a70c3 feat:
* PR suggested changes
2024-01-02 11:50:26 +05:30
Lakhan Samani
3bd3a52d3b Merge branch 'main' of https://github.com/authorizerdev/authorizer 2023-12-30 21:19:54 +05:30
Lakhan Samani
ade676f92c fix: remove access_token & id_token from query string 2023-12-30 21:19:44 +05:30
Lakhan Samani
ca71aba96d
Merge pull request #440 from foestauf/feat/discord-provider
feat: Add Discord as Identity Provider
2023-12-30 11:16:14 +05:30
Lakhan Samani
ef2a590608 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2023-12-26 21:10:58 +05:30
Lakhan Samani
59ed4e273f Update react app 2023-12-26 21:10:10 +05:30
Robert McKee
751933d40e
feat: Add Discord as Identity Provider 2023-12-23 17:05:02 -05:00
Lakhan Samani
5fac440205
Merge pull request #432 from authorizerdev/fix/forgot-password
feat: add forgot password for mobile login
2023-12-22 01:32:49 +05:30
Lakhan Samani
5ba30ccd12 remove todo 2023-12-22 01:32:12 +05:30
Lakhan Samani
02f7a62918 feat: add testing & ui for forgot password with mobile 2023-12-22 01:26:14 +05:30
scaletech-milan
1172c95a23
Merge branch 'authorizerdev:main' into main 2023-12-15 10:28:20 +05:30
Lakhan Samani
c9c2789097 Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix/forgot-password 2023-12-14 22:45:36 +05:30
Lakhan Samani
ed3100c179 fix: allow logout using access token 2023-12-14 22:12:03 +05:30
Anand Kumar Panigrahi
b8c6ee0a6d
Merge branch 'authorizerdev:main' into main 2023-12-11 15:12:23 +05:30
Lakhan Samani
0a5357c948 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2023-12-09 02:03:36 +05:30
Lakhan Samani
a002e2faf7 fix: remove forgot password for magic link login 2023-12-09 02:03:27 +05:30
lemonScaletech
d8b9ffe9ce Merge remote-tracking branch 'origin/fix/role-deletion' into fix/role-deletion 2023-12-08 18:22:57 +05:30
lemonScaletech
47f26103b0 test:
* added integration test for role deletion functionality
2023-12-08 18:22:24 +05:30
Anand Kumar Panigrahi
48ada9ab26
Merge branch 'authorizerdev:main' into fix/role-deletion 2023-12-08 17:31:01 +05:30
lemonScaletech
b8c2ab4cf8 refactoring:
* removed extra for loop
* commenting on functions
2023-12-08 10:38:09 +05:30
lemonScaletech
5cb94a7820 fix:
* added logic if role is deleted then also be deleted from user side if role is assigned to that user.
* default role should be subset of roles
2023-12-07 19:33:59 +05:30
scaletech-milan
7e9fac335b Feat:
- Add TOTP MFA for signup
- Test cases for totp signup and verify_email
2023-12-06 15:53:01 +05:30
scaletech-milan
febf4f9b15
Merge branch 'authorizerdev:main' into main 2023-12-06 15:35:25 +05:30
Lakhan Samani
b0f54b181d
Merge pull request #435 from siimsams/bugfix-appdata-validation 2023-12-06 07:42:09 +05:30
Siim Sams
df5978fb5a
fix unable to update app_data for user 2023-12-05 20:41:39 +02:00
Lakhan Samani
2a0e0da436 Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix/forgot-password 2023-12-04 23:35:13 +05:30
Lakhan Samani
e4a8eb3542 fix: signup field 2023-12-04 13:44:39 +05:30
Lakhan Samani
06214f0b1d feat: add switch to enable totp 2023-12-04 13:37:58 +05:30
scaletech-milan
a203b853f2
Merge branch 'authorizerdev:main' into main 2023-12-04 11:48:25 +05:30
Lakhan Samani
109b38e588
Merge pull request #433 from authorizerdev/fix/phone_number_verification
fix: phone_number_verified_at set during signup
2023-12-04 11:30:24 +05:30
Lakhan Samani
e8b99f73c3 fix: phone number verification 2023-12-04 11:28:27 +05:30
Lakhan Samani
3fa892431e fix: phone_number_verified_at set during signup 2023-12-04 09:26:59 +05:30
Lakhan Samani
4b341c0a5d Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix/forgot-password 2023-12-03 23:05:39 +05:30
Lakhan Samani
c95db8b07b feat: add forgot password for mobile login 2023-12-03 22:49:40 +05:30
Lakhan Samani
32fcba0f8d
Fix/forgot password (#430)
* fix: forgot password shown with magic link login

* fix: forgot password shown with magic link login

* fix is basic auth enabled
2023-12-03 22:27:56 +05:30
Lakhan Samani
5b75521490 fix is basic auth enabled 2023-12-03 22:27:37 +05:30
Lakhan Samani
f70310f04f fix: forgot password shown with magic link login 2023-12-03 11:27:39 +05:30
Lakhan Samani
bea6eb8342 fix: forgot password shown with magic link login 2023-12-03 11:17:34 +05:30
Lakhan Samani
cac67b7915
feat: add totp UI & recovery code (#429) 2023-12-03 09:03:22 +05:30
Lakhan Samani
d7da81d308 fix comment for twitch login 2023-12-02 12:22:27 +05:30
scaletech-milan
e49e315967
Feat: Add oauth2 for twitch (#426)
* fix:
* removed fmt.Println

* Feat:
- Add OAuth for twitch

---------

Co-authored-by: lemonScaletech <anand.panigrahi@scaletech.xyz>
Co-authored-by: Anand Kumar Panigrahi <70533637+lemonScaletech@users.noreply.github.com>
2023-12-02 12:21:53 +05:30
scaletech-milan
fbb4975c02
Merge branch 'authorizerdev:main' into main 2023-12-01 14:11:20 +05:30
Lakhan Samani
7f6ddca3fc fix: totp login 2023-12-01 14:00:01 +05:30
Anand Kumar Panigrahi
e71da3def6
Merge branch 'authorizerdev:main' into main 2023-11-28 11:34:24 +05:30
Lakhan Samani
46d6f86ab0 Remove logs 2023-11-25 11:44:52 +05:30
Lakhan Samani
1890db8f03 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2023-11-25 11:34:15 +05:30
Lakhan Samani
0e96e0b6f0 fix: nil check 2023-11-25 11:34:01 +05:30
Aris Ripandi
bbddf484ed
feat: add support for libsql / Turso database (#421) 2023-11-25 09:35:39 +05:30
lemonScaletech
7d4c641297 fix:
* removed fmt.Println
2023-11-24 13:52:17 +05:30
Lakhan Samani
bd343f0b27 fix: disable totp by default 2023-11-23 20:54:03 +05:30
Lakhan Samani
ad8bd64987 fix: remove width from logo for vertical logos 2023-11-23 15:42:17 +05:30
scaletech-milan
de5c18b60f
Feat: add screen_hint param in /authorize api for explicit signup redirection (#420)
* Feat:
- Introduce screen_hint param in /authorize for explicit signup redirection

* Feat:
- Declare variable for base path and signup path
- Add social login on signup page

* Refactor:
- Update variable name for screen hint param
2023-11-21 13:08:32 +05:30
Lakhan Samani
fe4c693324
feat: add totp login API (#416)
* fix:
* removed hasReversedValue in playground

* feat:
* added totp methods in db's providers
* adding totp in login method

* feat:
* added toggle in dashboard
* fixing issue with env set

* feat:
* integrated totp

* feat:
* encrypted userid
* added totp_verified column in user table
* started test for totp

* feat:
* test cases totp

* test-cases:
* completed test cases
* tested for all dbs

* fixes:
* return variable to snake case
* import refactoring

* feat:
* created seperate folder for authenticator with totp subfolder
* refactored code
* created new table for authenticators
* added recovery code for totp

* feat:
* adding functions to different db providers

* feat:
* added authenticators method for all db

* feat:
* added logic for updating mfa in user_profile update

* fix:
* merge conflict

* fix:
* resolved mongodb, dynamodb and arangodb test case bug
* added new condition for checking first time totp user or not

* feat:
* changes in all respective db with authenticator

* fix:
* PR suggested changes

* fix(cassandra): list users

* Update verify otp

* fix totp login api

---------

Co-authored-by: lemonScaletech <anand.panigrahi@scaletech.xyz>
2023-11-16 18:30:54 +05:30
Lakhan Samani
d8cd965004
Merge pull request #415 from testwill/typo
fix: typo
2023-11-16 10:44:12 +05:30
guangwu
fdd41721a8
fix: typo 2023-11-16 11:25:57 +08:00
Lakhan Samani
281714b86d
Merge pull request #414 from authorizerdev/fix/apple-login
Fix/apple login
2023-11-09 13:46:38 +05:30
Lakhan Samani
a05f5ce063 fix apple login nil pointer exception 2023-11-09 13:45:29 +05:30
Lakhan Samani
b7627a36a6 Add debug point 2023-11-09 10:49:17 +05:30
Lakhan Samani
85bbd1223e Fix validating session 2023-11-01 18:12:56 +05:30
Lakhan Samani
885a147463
Merge pull request #409 from authorizerdev/fix/use-login-signup-for-mobile
feat: unify email & mobile singup + login
2023-10-26 10:08:30 +05:30
Lakhan Samani
9a6f1a659a Allow empty email 2023-10-26 00:55:10 +05:30
Lakhan Samani
4bddbde280 Update comments 2023-10-22 02:36:10 +05:30
Lakhan Samani
3ed31b0557 feat: unify email & mobile singup + login 2023-10-22 02:33:36 +05:30
Lakhan Samani
4d1fcc3004
Merge pull request #408 from Olatunji-Longe/olatunji-longe/OIDC-config-fix
changed subject_types_supported to array of strings according to OIDC…
2023-10-22 01:01:07 +05:30
Olatunji Longe
99441964b5 Clean up ignored files 2023-10-20 20:19:46 -04:00
Olatunji Longe
be3d8cf69b changed subject_types_supported to array of strings according to OIDC specs 2023-10-20 20:10:10 -04:00
Lakhan Samani
734e54db69
Merge pull request #403 from authorizerdev/fix/upgrade-packages
fix: upgrade packages
2023-10-14 18:08:40 +05:30
Lakhan Samani
a3bda429d0 fix: upgrade packages
- fix app_data for cassandra & scylladb
2023-10-14 18:06:29 +05:30
Lakhan Samani
3fba4e4c28 fix: upgrade packages 2023-10-13 10:49:26 +05:30
Lakhan Samani
2a759de311 fix: upgrade packages 2023-10-13 10:48:51 +05:30
Lakhan Samani
e7c4ee5630
Merge pull request #402 from authorizerdev/fix/profile-access
fix: use session / access_token for profile related queries or mutation
2023-10-13 09:09:46 +05:30
Lakhan Samani
6a4568dcf3 fix: use session / access_token for profile related queries or mutation 2023-10-13 08:11:55 +05:30
Lakhan Samani
e941e4834a
Merge pull request #401 from authorizerdev/fix/deactivate-acc
Fix calling deactivate acc
2023-10-12 11:49:11 +05:30
Lakhan Samani
f906fb74af Fix calling deactivate acc 2023-10-12 11:48:37 +05:30
Lakhan Samani
7ced811e6e
Merge pull request #399 from authorizerdev/feat/deativate-account
Add api to deactivate user account
2023-10-11 00:22:15 +05:30
Lakhan Samani
c1e1ee13f2 Add webhook event to fe 2023-10-11 00:21:58 +05:30
Lakhan Samani
60de61a74e fix webhook for deactiavtion 2023-10-11 00:20:15 +05:30
Lakhan Samani
843bc022fe Add api to deactivate user account 2023-10-11 00:16:53 +05:30
Lakhan Samani
ad41bcf792
Merge pull request #397 from VishwasShashidhar/main
Support macOS arm64 release binaries
2023-09-30 23:54:33 +05:30
Vishwas Shashidhar
ea2596b9ae support darwin-arm64 builds 2023-09-30 23:23:10 +05:30
Lakhan Samani
c8ccb89a67
Merge pull request #391 from team-scaletech/fix/webhook_bug_389
Fix/webhook bug 389
2023-09-13 17:46:55 +05:30
lemonScaletech
9519b53d4e Merge branch 'main' into fix/webhook_bug_389 2023-09-11 11:57:19 +05:30
Anand Kumar Panigrahi
e7cfaf4fbe
Merge branch 'authorizerdev:main' into main 2023-09-11 11:55:27 +05:30
Lakhan Samani
0428488dab
Merge pull request #393 from Juneezee/refactor/redundant-nil-check
refactor(server/utils): remove redundant nil check
2023-09-11 11:42:58 +05:30
Eng Zer Jun
f3b672a4cf
refactor(server/utils): remove redundant nil check
From the Go specification:

  "3. If the map is nil, the number of iterations is 0." [1]

Therefore, an additional nil check for before the loop is unnecessary.

[1]: https://go.dev/ref/spec#For_range

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-09-09 12:57:20 +08:00
Lakhan Samani
1d6f569f92 fix: default value for disable playground 2023-09-09 07:11:13 +05:30
Lakhan Samani
fbdc24f314
Merge pull request #390 from team-scaletech/feat/disable_playground
feat: disable playground for non admin
2023-09-08 21:57:57 +05:30
Anand Kumar Panigrahi
1275644abe
Merge branch 'authorizerdev:main' into main 2023-09-06 11:30:06 +05:30
Lakhan Samani
ac6b08c093
Merge pull request #392 from imchairmanm/feat/add_koyeb_button
Add button to deploy to Koyeb
2023-09-03 21:11:46 +05:30
Justin Ellingwood
fe461b675b Add button to deploy to Koyeb 2023-09-01 13:30:44 +01:00
lemonScaletech
77e219d990 fix:
* #389 resolved
* typo fixed
2023-08-29 18:14:48 +05:30
lemonScaletech
3ea13d7e93 refactor:
* added blank line eof .env.sample
2023-08-29 12:19:16 +05:30
lemonScaletech
940a09d172 Merge remote-tracking branch 'origin/feat/disable_playground' into feat/disable_playground
# Conflicts:
#	app/yarn.lock
#	server/constants/env.go
#	server/env/env.go
#	server/env/persist_env.go
#	server/memorystore/providers/redis/store.go
2023-08-29 12:00:24 +05:30
lemonScaletech
3ac6875f87 feat:
* resolved conflicts
2023-08-29 11:55:27 +05:30
lemonScaletech
937506ff64 Merge branch 'main' into feat/disable_playground
# Conflicts:
#	app/yarn.lock
#	dashboard/yarn.lock
#	server/constants/env.go
#	server/env/env.go
#	server/env/persist_env.go
#	server/graph/model/models_gen.go
#	server/memorystore/providers/redis/store.go
2023-08-29 11:44:07 +05:30
lemonScaletech
9795ba9097 feat:
* added disable playground functionality
 * added toggle button for playground in dashboard
2023-08-29 11:36:18 +05:30
vipul patel
11dbe9d97a
Merge pull request #1 from authorizerdev/main
Authorizer to foke authorizer
2023-08-29 09:15:53 +05:30
lemonScaletech
c9b8bbc3e1 feat:
* added disable playground functionality
 * added toggle button for playground in dashboard
2023-08-28 19:51:42 +05:30
Lakhan Samani
a124edfaee Add user to validate_session
Resolves #379
2023-08-19 20:45:20 +05:30
Lakhan Samani
5e6b033024 fix microsoft active directory config 2023-08-17 14:20:31 +05:30
Lakhan Samani
171d4e3fff remove unused code 2023-08-14 14:16:54 +05:30
Lakhan Samani
cf96a0087f Fix tests for verifying otp using mfa session 2023-08-14 14:15:52 +05:30
Lakhan Samani
09cfad9c27
Merge pull request #382 from authorizerdev/feat-add-field-for-app-data
Add app_data
2023-08-14 12:05:58 +05:30
Lakhan Samani
35e563ab3b Add app_data 2023-08-14 12:01:37 +05:30
Lakhan Samani
e625ed9633 allow common tenant for microsoft 2023-08-03 14:43:27 +05:30
Lakhan Samani
a042c202a0 fix microsoft active directory config 2023-08-03 13:29:07 +05:30
Lakhan Samani
7a76b783b1
Merge pull request #372 from catusax/main
feat: add mfa session to secure otp login
2023-08-03 12:34:39 +05:30
Lakhan Samani
e5400bc7bd fix microsoft active directory config 2023-08-03 12:33:20 +05:30
Lakhan Samani
a8503666e3 fix: add events for signup 2023-08-02 10:02:41 +05:30
Lakhan Samani
b028be3cbc
Merge pull request #377 from authorizerdev/fix-webhook-test-endpoint
fix: test webhook endpoint mutation
2023-08-02 00:04:55 +05:30
Lakhan Samani
9a8d20b698 fix: test webhook endpoint mutation
Resolves #376
2023-08-02 00:04:07 +05:30
Lakhan Samani
fab3c2f87e
Merge pull request #375 from authorizerdev/fix-db-refs
Fix db refs
2023-08-01 23:38:00 +05:30
Lakhan Samani
3fefcfcd9a fix: refs for dynamo db 2023-08-01 17:37:34 +05:30
Lakhan Samani
c2defdbaac fix: refs for cassandra db 2023-08-01 16:09:17 +05:30
Lakhan Samani
15a4be5431 fix: refs for db provider and few utils 2023-07-31 16:42:11 +05:30
Lakhan Samani
146561bacb fix: allow multi tenant for microsooft 2023-07-25 11:45:08 +05:30
catusax
0c334856bc
Merge branch 'main' into main 2023-07-24 14:04:26 +08:00
catusax
ba0cf189de
userid ass mfa session key 2023-07-24 12:00:30 +08:00
Lakhan Samani
9f52c08883
[app] bump authorizer-react 1.1.13 2023-07-24 11:56:56 +08:00
Lakhan Samani
80f3698f06
[app] bump authorizer-react 1.1.12 2023-07-24 11:56:56 +08:00
Lakhan Samani
2a2b7abc08
Add optional show_mobile_otp_screen 2023-07-24 11:56:56 +08:00
Lakhan Samani
27e3ed82e4
Update resend otp 2023-07-24 11:56:55 +08:00
Lakhan Samani
6077702626
fix: tests for otp refactor 2023-07-24 11:56:55 +08:00
Lakhan Samani
cf54fcef03
Fix tests 2023-07-24 11:56:55 +08:00
Lakhan Samani
2f849b8f0c
Refactor code for otp 2023-07-24 11:56:55 +08:00
Lakhan Samani
85ca0f09bf
[draft] Move sms verificaiton to otp models 2023-07-24 11:55:26 +08:00
Lakhan Samani
2fc438d810 [app] bump authorizer-react 1.1.13 2023-07-23 16:13:40 +05:30
Lakhan Samani
1796cace15 [app] bump authorizer-react 1.1.12 2023-07-23 11:30:43 +05:30
Lakhan Samani
43fdc826c4 Add optional show_mobile_otp_screen 2023-07-23 11:23:24 +05:30
catusax
e7652db89c
add comments 2023-07-23 13:02:14 +08:00
Lakhan Samani
c80b0d7028
Merge pull request #368 from authorizerdev/fix-sms-verification-for-alldb
Move sms verificaiton to otp models
2023-07-23 10:04:18 +05:30
Lakhan Samani
55fc4b2608 Update resend otp 2023-07-23 10:03:37 +05:30
Lakhan Samani
fac333e195 fix: tests for otp refactor 2023-07-23 07:29:29 +05:30
catusax
5018462559
feat: add mfa session to secure otp login 2023-07-20 15:11:39 +08:00
Lakhan Samani
edb5412c17 Fix tests 2023-07-18 22:50:23 +05:30
Lakhan Samani
87a962504f Increase timeout for redis 2023-07-16 22:57:56 +05:30
Lakhan Samani
d04f79557a Refactor code for otp 2023-07-13 11:39:22 +05:30
Lakhan Samani
c20e9b810a Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix-sms-verification-for-alldb 2023-07-12 22:16:07 +05:30
Lakhan Samani
8d145bd5fe
Merge pull request #369 from authorizerdev/feat-add-validate-cookie-api
feat: add resolver to validate browser session
2023-07-12 22:13:47 +05:30
Lakhan Samani
6fa0ad1809 feat: add resolver to validate browser session 2023-07-12 22:12:17 +05:30
Lakhan Samani
abe809ca68 [draft] Move sms verificaiton to otp models 2023-07-12 11:24:13 +05:30
Lakhan Samani
07f71e883b Add comments for twillio 2023-07-11 14:49:16 +05:30
Lakhan Samani
6cef9064c3 Update provider template for sms verification 2023-07-11 14:48:37 +05:30
Lakhan Samani
9ae616b6b5
Merge pull request #365 from JokerQyou/patch-1
Fix wrong response_type parsed when missing response_mode
2023-06-30 18:10:31 +05:30
Joker_
356428ea02
Fix wrong response_type parsed when missing response_mode 2023-06-29 23:10:44 +08:00
Lakhan Samani
7f47177741
Merge pull request #359 from MussieT/feat/sms_confirmation
Feat/sms confirmation
2023-06-13 09:38:23 +05:30
Mussie Teshome
9fb00544cd removed unwanted comment 2023-06-11 20:44:09 +03:00
Mussie Teshome
2b022d1058 Fix typo 2023-06-11 16:23:31 +03:00
Mussie Teshome
1c84d9f4a8
Merge branch 'authorizerdev:main' into feat/sms_confirmation 2023-06-11 16:05:29 +03:00
Mussie Teshome
0838b60fae Added VerifyMobileTest to the resolver 2023-06-11 16:03:16 +03:00
Mussie Teshome
325134466d Testing verify_mobile resolver 2023-06-11 16:02:46 +03:00
Mussie Teshome
58d9978dd5 Updated to test verification 2023-06-11 16:01:49 +03:00
Mussie Teshome
801d64e2f5 Twilio configuration 2023-06-11 16:00:30 +03:00
Mussie Teshome
dd3cc9de3a Verify mobile resolver 2023-06-11 16:00:07 +03:00
Mussie Teshome
8dc7366182 Updated mobile signup to send sms when service enabled 2023-06-11 15:59:53 +03:00
Mussie Teshome
7749534087 generated 2023-06-11 15:59:18 +03:00
Mussie Teshome
510f16e7b0 New resolver - Verify Moblie 2023-06-11 15:59:03 +03:00
Mussie Teshome
d5e83ea14f Schema update for SMSVerificationRequest 2023-06-11 15:58:50 +03:00
Mussie Teshome
b4a90de1d4 Updated to support disable sms verification request 2023-06-11 15:58:04 +03:00
Mussie Teshome
c525ad92f2 SQL Related dbs CRUD implementation for SMS 2023-06-11 15:57:14 +03:00
Mussie Teshome
9028682e93 Added SMSVerificationRequests model to automigrate 2023-06-11 15:56:40 +03:00
Mussie Teshome
3d6bfe4480 mongo implementation for the sms crud 2023-06-11 15:56:02 +03:00
Mussie Teshome
043af08bf0 Mongo collection for SMSVerificationRequest model 2023-06-11 15:55:11 +03:00
Mussie Teshome
0af78479fc Different dbs fn skeleton which fn yet not written 2023-06-11 15:54:23 +03:00
Mussie Teshome
096f686495 Added delete sms request to the interface 2023-06-11 15:52:33 +03:00
Mussie Teshome
c574c6a679 configure twilio via environment variables 2023-06-11 15:52:07 +03:00
Mussie Teshome
6428b74e64 twilio - new package 2023-06-11 15:50:09 +03:00
Mussie Teshome
aa3892025d New resolvers for sms requests 2023-06-11 15:49:25 +03:00
Mussie Teshome
b2f3d6eb80 sms verification requests model 2023-06-08 11:53:06 +03:00
Mussie Teshome
348cbf8c38 Add sms verification to collection 2023-06-08 11:52:39 +03:00
Mussie Teshome
8ac33a085c commented out sms twilio sender 2023-06-01 15:29:22 +03:00
Lakhan Samani
6c9b359081
Merge pull request #355 from minilikmila/fix/facebook-login
Modify the Facebook login authentication callback to enable user email access through the response body.
2023-05-29 10:32:27 +05:30
Mila Shumete
0fde46d274 setting on facebook user email method --- change the parameter(key) passed to get the email from map 2023-05-28 17:10:29 +03:00
Lakhan Samani
1a5b446894
Merge pull request #353 from authorizerdev/add-get-user-by-email
[server] add ability to get user by email
2023-05-20 09:50:59 +05:30
Lakhan Samani
930c934fdb [server] add ability to get user by email 2023-05-20 09:49:18 +05:30
Lakhan Samani
4e7074d75b
Merge pull request #351 from miqe/feat/add_sender_name
Feat/add sender name
2023-05-16 14:07:18 +05:30
Michael Sahlu
bdfa045a43 add SENDER_NAME env 2023-05-16 00:59:45 +03:00
Michael Sahlu
a258399bde add Sender Name input 2023-05-16 00:59:01 +03:00
Michael Sahlu
55a25436a8 add SENDER_NAME in env variable query 2023-05-16 00:58:26 +03:00
Michael Sahlu
9fa402f5c8 add SENDER_NAME environment and types 2023-05-16 00:57:33 +03:00
Michael Sahlu
1111729ad4 add sender name / from name 2023-05-16 00:51:28 +03:00
Michael Sahlu
e56c2f58e5 add sender name on schema and resolver 2023-05-16 00:46:22 +03:00
Michael Sahlu
8dbd2556eb retrive sender name from env 2023-05-16 00:40:14 +03:00
Michael Sahlu
17bb077f3e add EnvKeySenderName for SENDER_NAME env variable 2023-05-16 00:39:25 +03:00
Lakhan Samani
f291417378
Merge pull request #350 from authorizerdev/fix/redirect-uri-error
[server]fix: error redirection for email verification
2023-05-12 16:43:38 +05:30
Lakhan Samani
f831379d27 revert change for forgot password 2023-05-12 16:39:02 +05:30
Lakhan Samani
a50f6becbd [server]fix: error redirection for email verification 2023-05-02 18:39:10 +05:30
Lakhan Samani
a6f6e0b18a
Merge pull request #349 from darshvaghela/main 2023-04-24 09:32:38 +05:30
darshvaghela
3868157e11 refactored code 2023-04-23 17:31:24 +05:30
darshvaghela
d693c05483 Features enhancement (Disable/Enable) 2023-04-22 15:21:47 +05:30
Lakhan Samani
6f1fbf886d
Merge pull request #346 from MussieT/feat/return-user-info-on-invitation
Feat/return user info on invitation
2023-04-20 13:59:49 +05:30
Mussie Teshome
b86487fda4 assert message and response is not null 2023-04-20 10:43:06 +03:00
Mussie Teshome
28d4ddeb50 Return the new emails only 2023-04-19 15:38:30 +03:00
Mussie Teshome
b9ab1d3761 return err on err 2023-04-19 15:31:57 +03:00
Mussie Teshome
a5b643e127 removed unnecessary comment 2023-04-19 15:19:17 +03:00
Mussie Teshome
691664e629 Invite members resolver updated to return user info 2023-04-19 14:46:27 +03:00
Mussie Teshome
efb67a9538 New response type for invite members 2023-04-19 14:45:22 +03:00
Mussie Teshome
a0f2eeba3e golang package updates 2023-04-19 14:44:50 +03:00
Mussie Teshome
d1a0ccd790 package updates while running make clean && make 2023-04-19 14:44:17 +03:00
Lakhan Samani
729c23f578 [chore]: update authorizer-react 2023-04-15 14:14:47 +05:30
Lakhan Samani
a074f85391 [chore]: update contributing guide 2023-04-15 08:33:33 +05:30
Lakhan Samani
4e7ec6cb7b [chore]: add command to generate db template 2023-04-15 08:13:10 +05:30
Lakhan Samani
6d541cbfb9 fix: use normal mutex for cache 2023-04-10 15:33:59 +05:30
Lakhan Samani
1ebba7f2b7
Merge pull request #343 from authorizerdev/fix/session-storage
fix: session storage
2023-04-08 18:07:52 +05:30
Lakhan Samani
428a0be3db feat: add cache clear 2023-04-08 18:02:53 +05:30
Lakhan Samani
02c0ebb9c4 fix: session storage 2023-04-08 13:06:15 +05:30
Lakhan Samani
9a284c03ca fix: redis session 2023-04-03 10:26:27 +05:30
Lakhan Samani
c8fe05eabc
Merge pull request #342 from authorizerdev/feat/default-oauth-configs
feat: add support for default response mode & type env
2023-04-01 17:42:02 +05:30
Lakhan Samani
48344ffd4c feat: add support for default response mode & type env
Resolves #341
2023-04-01 17:36:07 +05:30
Lakhan Samani
77f34e1149
Merge pull request #339 from authorizerdev/fix/webhooks
fix: allow multiple hooks for same event
2023-03-29 07:31:46 +05:30
Lakhan Samani
16136931a9 fix: add event description to webhook res 2023-03-29 07:31:07 +05:30
Lakhan Samani
c908ac94da fix: continue in case of error for register events 2023-03-29 07:29:44 +05:30
Lakhan Samani
6604b6bbdd fix: update dashboard ui for webhooks 2023-03-29 07:27:56 +05:30
Lakhan Samani
2c227b5518 chore: delete couchbase container after tests 2023-03-29 07:10:36 +05:30
Lakhan Samani
e822b6f31a fix: queries for webhooks + improve tests 2023-03-29 07:06:33 +05:30
Lakhan Samani
a38e9d4e6c fix: rename title -> event_description 2023-03-26 07:48:06 +05:30
Lakhan Samani
deaf1e2ff7 fix: allow multiple hooks for same event 2023-03-26 07:20:45 +05:30
Lakhan Samani
f324976801
Merge pull request #338 from authorizerdev/fix/accessibility
fix: accessibility
2023-03-25 10:48:49 +05:30
Lakhan Samani
fad90ce1a8 fix: accessibility
Resolves #337
2023-03-25 10:48:09 +05:30
Lakhan Samani
df406ba053
Merge pull request #332 from authorizerdev/fix/open-id
fix: add missing info for openid config
2023-03-07 08:44:26 +05:30
Lakhan Samani
4a7877a21b fix: remove duplicate files 2023-03-04 16:13:31 +05:30
Lakhan Samani
79089cc009
Merge pull request #330 from productdevbook/patch-1
feat: github sponsor
2023-03-04 16:12:52 +05:30
Lakhan Samani
149d0cac7a fix: add missing info for openid config
Resolves #304
2023-03-04 16:11:37 +05:30
Lakhan Samani
8863140e75
Create FUNDING.yaml 2023-03-03 08:11:38 +05:30
Mehmet
b8ffadd36c
Create FUNDING.yml 2023-03-02 13:07:05 +03:00
Lakhan Samani
7dd20128af
Merge pull request #329 from authorizerdev/fix/add-sub-user-info
[server][fix]: add sub to userinfo
2023-02-28 12:52:21 +05:30
Lakhan Samani
19f5ff61c0 [server][fix]: add sub to userinfo
Resolves: #327
2023-02-28 12:51:11 +05:30
Lakhan Samani
146707d062
Merge pull request #328 from authorizerdev/feat/add-microsoft-login
feat: add microsoft login
2023-02-26 06:05:42 +05:30
Lakhan Samani
0810c4a201 chore: update app authorizer-react 1.1.8 2023-02-26 06:05:15 +05:30
Lakhan Samani
3603af9f84 feat: add microsoft login 2023-02-26 05:23:02 +05:30
Lakhan Samani
1ac8ba4ce0
Merge pull request #324 from authorizerdev/fix/neon-db-support
[server]: fix support for neondb
2023-02-10 18:08:22 +05:30
Lakhan Samani
cdcdc444b2 [server]: fix support for neondb
Update gorm/postgres driver version 1.4.7
2023-02-10 10:39:53 +05:30
Lakhan Samani
330f35f2fc
Merge pull request #322 from authorizerdev/fix/use-scopes-as-string
[server] use scope string instead of string array in tokens
2023-02-08 09:41:17 +05:30
Lakhan Samani
70242debe1 [server] fix scope response type + add extra claims to access token 2023-02-08 09:39:08 +05:30
Lakhan Samani
4018da6697 [server] use scope string instead of string array in tokens 2023-02-07 01:13:03 +05:30
Lakhan Samani
a73c6ee49e
Merge pull request #319 from authorizerdev/fix/arangodb-connection 2023-02-06 21:32:32 +05:30
Lakhan Samani
c23fb1bb32 [server] update arangodb version for test 3.10.3 2023-02-06 20:39:22 +05:30
Lakhan Samani
270853a6a3 [server] add support for arangodb cert, username, password
Adding support for db username, password and ca cert will
enable users to connect arangodb cloud platform
2023-02-06 18:14:19 +05:30
Lakhan Samani
2d0346ff23 [server] remove unused error condition for couchbase 2023-02-05 11:03:26 +05:30
Lakhan Samani
4b26e1ce85 [server] fix bucket creation for couchbase
Run create bucket query only if bucket is not found.
Required while running couchbase cloud version
2023-02-05 11:01:20 +05:30
Lakhan Samani
8212e81023 [server] common util for couchbase syntax 2023-02-02 13:06:18 +05:30
Lakhan Samani
642581eefd [server] Add COUCHBASE_BUCKET_RAM_QUOTA
Resolves #317
2023-02-02 12:43:17 +05:30
Lakhan Samani
b7357dde21 [server] fix primary index creation for couchbase 2023-02-02 12:28:52 +05:30
Lakhan Samani
a1df2ce31f [server] use encryption_key for couchbase env as hash is reserved keyword 2023-01-31 11:18:20 +05:30
Lakhan Samani
748761926d [server] fix make command 2023-01-25 05:19:01 +05:30
Lakhan Samani
d632195ba5
Merge pull request #315 from authorizerdev/feat/couchbase
feat: add couchbase provider
2023-01-19 23:32:20 +05:30
Lakhan Samani
25970f80e1 [app] update authorizer-react 2023-01-19 23:30:54 +05:30
Lakhan Samani
a52b7c86e7 [server] add couchbase to all db test 2023-01-19 23:29:48 +05:30
Lakhan Samani
504d0f34d7 [server] add couchbase envs 2023-01-19 23:28:21 +05:30
Lakhan Samani
44879f1a8f feat: add couchbase provider 2023-01-18 01:38:00 +05:30
Lakhan Samani
b39f0b87fd
Merge pull request #313 from authorizerdev/feat/add-get-user
Add get user api for admin
2023-01-05 20:17:48 +05:30
Lakhan Samani
b2423140e2 Add get user api for admin 2023-01-05 20:16:41 +05:30
Lakhan Samani
4f810d2f8b
Merge pull request #307 from authorizerdev/feat/mobile-basic-auth
feat: add mobile based basic auth
2022-12-25 03:28:55 +05:30
Lakhan Samani
313b510ba1 feat: add signup + login using mobile 2022-12-25 03:22:42 +05:30
Lakhan Samani
105d9be685
Merge pull request #310 from Lentech-AS/main
Rnd warning fixes
2022-12-24 09:18:07 +05:30
Leander Gangsø
bc68b61879
fix: oauth2.NoContext is deprecated
using context.TODO instead
2022-12-23 21:12:11 +01:00
Leander Gangsø
2847300bf6
fix: update deprecated func since go 1.16
might want to update the docs if you accept this, as it states go > 1.15 is required
2022-12-23 21:12:11 +01:00
Leander Gangsø
d438480f37
fix: remove unused formatting directive 2022-12-23 21:12:11 +01:00
Lakhan Samani
da29f9d055
Merge pull request #308 from Lentech-AS/main
change toast position to top-right
2022-12-23 07:10:11 +05:30
Leander Gangsø
f29256a8f5
change toast position to top-right 2022-12-22 19:53:18 +01:00
Lakhan Samani
1eb8965f98 feat: add mobile based basic auth 2022-12-21 23:14:24 +05:30
Lakhan Samani
1c4e29fa7c fix: access_token renew + web_message redirect 2022-11-29 05:27:29 +05:30
Lakhan Samani
7a28795fa0
Merge pull request #302 from authorizerdev/fix/sql-user-deletion
fix(sql): user deletion
2022-11-25 22:46:57 +05:30
Lakhan Samani
f5db00beb0 fix(sql): user deletion
Resolves: https://github.com/authorizerdev/docs/issues/18
2022-11-25 22:45:23 +05:30
Lakhan Samani
d515a1f41d chore: update app 1.1.4 2022-11-25 22:40:14 +05:30
Lakhan Samani
c948c98e94
Merge pull request #298 from authorizerdev/fix/url-parsing
fix: remove extra slash from host
2022-11-24 19:13:50 +05:30
Lakhan Samani
e985e096bc fix: remove extra slash from host 2022-11-24 12:58:04 +05:30
Lakhan Samani
70bab70ead fix: validating id_token 2022-11-23 22:03:08 +05:30
Lakhan Samani
6ddaf88e3f
Merge pull request #294 from authorizerdev/fix/sql-unique-phone-constraint
fix(server): unique constraint for phone_number on mssql
2022-11-17 23:10:55 +05:30
Lakhan Samani
f8bcd0fe51 fix(server): unique constraint for phone_number on mssql 2022-11-17 23:08:17 +05:30
Lakhan Samani
16c4b8ab76 fix(server): global logging 2022-11-17 10:35:38 +05:30
Lakhan Samani
0788c5ff5e fix(server): default loglevel via cli arg 2022-11-17 10:27:55 +05:30
Lakhan Samani
2d5d38de02 fix(server): gorm logging
- add support for LOG_LEVEL env var (Resolves #271)
2022-11-17 10:25:00 +05:30
Lakhan Samani
0dd06d9afd fix(server): basic auth check 2022-11-17 05:44:40 +05:30
Lakhan Samani
d2f472a9cf
Merge pull request #293 from luclu7/main
fix: codeVerifier shouldn't be empty for basic auth
2022-11-17 05:44:01 +05:30
Luclu7
4678193300
fix: codeVerifier shouldn't be empty for basic auth 2022-11-16 23:31:39 +01:00
Lakhan Samani
67be8ae285 feat(server): allow using client_id & secret from basic auth header in token endpoint 2022-11-16 22:40:45 +05:30
Lakhan Samani
f9d2130c65
Merge pull request #270 from authorizerdev/fix/oauth-provider
fix(server): authorizer openid flow
2022-11-16 12:27:30 +05:30
Lakhan Samani
bb2a42a1db fix: update app package 2022-11-16 12:20:32 +05:30
Lakhan Samani
f857c993c8 Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix/oauth-provider 2022-11-16 11:47:47 +05:30
Lakhan Samani
824f286b9b
Merge pull request #290 from authorizerdev/feat/plain-html-editor
feat: add plain html editor for email templates
2022-11-16 11:31:42 +05:30
anik-ghosh-au7
ecefe12355 chore: format graphql schema 2022-11-15 22:39:04 +05:30
anik-ghosh-au7
5c8f9406f6 fix: email template resolver and for matting changes 2022-11-15 22:12:14 +05:30
Lakhan Samani
75a547cfe2 fix: other auth recipes for oidc idp + remove logs 2022-11-15 21:45:08 +05:30
Lakhan Samani
579899c397 fix(server): creepy @@ string split logic for auth_token 2022-11-13 01:22:21 +05:30
Lakhan Samani
9320f1cb07 fix(server): add flow comment 2022-11-13 00:40:28 +05:30
Lakhan Samani
c09558043e fix(server): spacing 2022-11-13 00:16:22 +05:30
Lakhan Samani
49556b1709 fix: openid flow 2022-11-12 23:54:37 +05:30
Lakhan Samani
4775641431 Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix/oauth-provider 2022-11-10 22:51:13 +05:30
anik-ghosh-au7
ae84213e34 fix: allow design variable empty value for email templates 2022-11-09 22:54:05 +05:30
anik-ghosh-au7
87bf1c3045 fix: allow design variable empty value for email templates 2022-11-09 22:48:12 +05:30
Lakhan Samani
e525877467 fix: latest tag 2022-11-09 22:27:52 +05:30
anik-ghosh-au7
b467e7002d feat: add plain html email template editor 2022-11-09 22:17:41 +05:30
Lakhan Samani
78bdd10a15
Merge pull request #289 from authorizerdev/fix/build
Fix/build
2022-11-09 22:08:13 +05:30
Lakhan Samani
512fd4f1f7 chore: add multi arch setup 2022-11-09 17:36:53 +05:30
Lakhan Samani
67da4a49e4 chore: use zip for windows assets 2022-11-09 16:07:27 +05:30
Lakhan Samani
48deae1d11 chore: fix multi arch build 2022-11-09 15:02:37 +05:30
Lakhan Samani
1f2ded4219 chore: fix tags 2022-11-09 14:15:09 +05:30
Lakhan Samani
cb5af1e679 use non c binding for sqlite 2022-11-09 13:07:20 +05:30
Lakhan Samani
27160ecbd5 chore: fix arm dependencies 2022-11-09 09:46:27 +05:30
Lakhan Samani
c6c3af1114 chore: update dependencies for cross compile 2022-11-09 04:31:04 +05:30
Lakhan Samani
e54b7f18f0 chore: add dependencies for cross compile 2022-11-09 04:25:15 +05:30
Lakhan Samani
a18046748b chore: add cgo param to gox 2022-11-09 04:09:55 +05:30
Lakhan Samani
1bff6720fc chore: install gox 2022-11-09 03:59:20 +05:30
Lakhan Samani
024ffd85f3 chore: fix yaml indentation 2022-11-09 03:52:47 +05:30
Lakhan Samani
e171820614 chore: use checkout@v3 2022-11-09 03:51:08 +05:30
Lakhan Samani
19f9caf478 chore: test gox + buildx 2022-11-09 03:47:28 +05:30
Lakhan Samani
4afd544c41 feat(server): add allowed_roles in access_token + refresh_token 2022-11-07 07:11:23 +05:30
Lakhan Samani
307c6f7d15 fix: refresh token login method claim 2022-11-04 01:40:18 +05:30
Lakhan Samani
bbc6394cf3
Merge pull request #286 from Pjort/main
fixed validation of refresh token
2022-11-03 20:10:40 +05:30
Pjort Kat
63c8e2e55f fixed validation of refresh token 2022-11-03 11:51:59 +01:00
Lakhan Samani
b224892a39 fix: minor space 2022-11-02 08:48:56 +05:30
Lakhan Samani
13edf1965c
Merge pull request #285 from authorizerdev/development
feat(server): add jwt claims as part of validation endpoint
2022-11-01 10:15:08 +05:30
Lakhan Samani
1f220a5205 add jwt claims as part of validation endpoint 2022-11-01 10:12:01 +05:30
Lakhan Samani
32fb954a1c
Merge pull request #281 from authorizerdev/development
chore: 1.1.22-rc.0
2022-10-25 08:27:02 +05:30
Lakhan Samani
65eadb66fa chore: format 2022-10-25 08:25:23 +05:30
Lakhan Samani
9ce53eb8e8 Merge branch 'development' of https://github.com/authorizerdev/authorizer into development 2022-10-25 08:21:32 +05:30
Lakhan Samani
3b196f074b
Merge pull request #280 from authorizerdev/feat/user-roles-multi-select
feat: add user roles multi select input
2022-10-25 08:19:08 +05:30
Lakhan Samani
f2fe584793 feat: add support for SMTP LocalName
Resolves #274
2022-10-25 08:18:29 +05:30
Lakhan Samani
287b952dad fix: forgot password redirect from app 2022-10-24 11:37:42 +05:30
Lakhan Samani
e690066652 fix(server):give higher preference to redirect_uri
While using forgot_password redirect URI was ignored if not present

Resolves #275
2022-10-24 11:15:36 +05:30
anik-ghosh-au7
0f67d74657 feat: add user roles multi select input 2022-10-23 22:59:17 +05:30
Lakhan Samani
274909b7c9 feat: add nonce variable to create auth token 2022-10-23 21:08:08 +05:30
Lakhan Samani
549385e5df Merge branch 'development' of https://github.com/authorizerdev/authorizer into fix/oauth-provider 2022-10-23 16:56:25 +05:30
Lakhan Samani
6e09307c22
Merge pull request #279 from authorizerdev/fix/sql-server-unique-index
fix(server): sql server not allow multiple null
2022-10-21 22:00:55 +05:30
Lakhan Samani
7fc69dcc55 fix(server): sql server not allow multiple null
multiple null values for unique constrained column
is not allowed on sqlserver

Resolves #278
2022-10-21 22:00:16 +05:30
Lakhan Samani
8449821d1b fix(server): dynamodb tests + provider config 2022-10-21 15:55:54 +05:30
Lakhan Samani
476bdf00fc fix(server): open_id config 2022-10-21 11:21:21 +05:30
Lakhan Samani
e41f123866 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-10-21 11:19:37 +05:30
Lakhan Samani
094782aeca fix(server): linting issues 2022-10-21 11:19:32 +05:30
Lakhan Samani
9630cbbc3e
Merge pull request #277 from authorizerdev/development
chore(1.1.21.rc)
2022-10-21 11:17:14 +05:30
Lakhan Samani
1ac060136a fix: make env vars name more persistent 2022-10-20 16:27:00 +05:30
Lakhan Samani
c6019e650b fix: add manual code generation 2022-10-20 15:35:26 +05:30
Lakhan Samani
b2e0a3371f fix: revert nonce 2022-10-20 00:14:06 +05:30
Lakhan Samani
a68876a6f4 fix: comment 2022-10-19 23:55:47 +05:30
Lakhan Samani
2c867b0314 fix: issuer token endpoint 2022-10-19 23:41:08 +05:30
Lakhan Samani
74b858ac24 fix: binding 2022-10-19 23:39:48 +05:30
Lakhan Samani
fedc3173fe fix: get nonce from query request if possible 2022-10-19 23:36:33 +05:30
Lakhan Samani
de4381261e fix: add nonce to supported claims 2022-10-19 23:17:13 +05:30
Lakhan Samani
a916b8c32c fix: add nonce 2022-10-19 19:04:15 +05:30
Lakhan Samani
89f08b6d31 fix: redirect from app 2022-10-19 12:20:22 +05:30
Lakhan Samani
cc23784df8 fix: add code to login query params 2022-10-19 12:01:34 +05:30
Lakhan Samani
7ff3b3018a fix: add code to query params 2022-10-19 11:29:49 +05:30
Lakhan Samani
2b52932e98 fix: add code to other response methods 2022-10-19 09:03:00 +05:30
Lakhan Samani
c716638725 fix(server): revert the state & code_challenge validation 2022-10-18 23:24:19 +05:30
Lakhan Samani
252cd1fa2d fix: make code_challenge optional 2022-10-18 23:14:24 +05:30
Lakhan Samani
7c2693b086 fix: form post template 2022-10-18 23:03:55 +05:30
Lakhan Samani
eaa10ec5bc fix: error detection 2022-10-18 22:34:57 +05:30
Lakhan Samani
253128ca0c fix: query params for code response 2022-10-18 22:00:54 +05:30
Lakhan Samani
cddfe1e088 fix: response 2022-10-18 21:46:37 +05:30
Lakhan Samani
8e655bcb5b fix: authorize response 2022-10-18 21:29:09 +05:30
Lakhan Samani
9a411e673c fix: reponse 2022-10-18 21:08:53 +05:30
Lakhan Samani
346c8e5a47 fix: handle response 2022-10-16 22:16:37 +05:30
Lakhan Samani
3cd99fe5f6 fix: open id config 2022-10-16 21:03:37 +05:30
Lakhan Samani
2bd92d6028 feat: add form_post method 2022-10-16 20:46:54 +05:30
Lakhan Samani
844e867d96
Merge pull request #262 from manojown/feat/dynamo-db-support
Feat/dynamo db support
2022-10-14 09:42:08 +05:30
Lakhan Samani
ff805e3ef2 fix: add comments 2022-10-12 13:10:24 +05:30
Lakhan Samani
0115128ee7 fix(server): authorizer as oauth provider 2022-10-09 19:48:13 +05:30
manoj
820d294130 comments resolved for requireEnv, Provider and test env 2022-10-09 00:49:31 +05:30
manoj
e37472d498 update the local test url for dynamodb 2022-10-08 16:20:16 +05:30
manoj
589af96888 resolve conflict over the db models 2022-10-08 16:07:07 +05:30
manoj
dccc70e5c0 resolve conflict with main branch 2022-10-08 15:47:44 +05:30
Lakhan Samani
d8eceadd7f
Merge pull request #267 from authorizerdev/fix/sqlserver-text-type
fix(server): text type for sql server 2019
2022-10-08 15:38:32 +05:30
Lakhan Samani
e6c4fdff26 fix(server): text type for sql server 2019
Resolves #266
2022-10-07 10:13:20 +05:30
manoj
896d8e046d remove the white space 2022-10-05 16:11:31 +05:30
manoj
a6d5d4af24 require env condition needed to be added for dynamodb 2022-10-05 15:52:17 +05:30
Manoj
cc4eaa2847 dynamod db index changes added to the schema 2022-10-05 15:32:32 +05:30
Manoj
dba8944565 provider: dynamo-db support added 2022-10-03 01:08:12 +05:30
Lakhan Samani
e760a5598e
Merge pull request #260 from authorizerdev/chore/add-prettier-app
chore(app): add prettier
2022-10-02 22:40:16 +05:30
Lakhan Samani
f62a22619b chore(app): add prettier 2022-10-02 22:39:47 +05:30
Lakhan Samani
c32a7fa1e4
Merge pull request #259 from authorizerdev/chore/add-prettier-dashboard
chore(dashboard): add prettier
2022-10-02 22:38:06 +05:30
Lakhan Samani
399b97079d chore(dashboard): add prettier 2022-10-02 22:37:20 +05:30
Lakhan Samani
fe687cb0ca
Merge pull request #258 from authorizerdev/fix/cookie-security-features
feat(dashboard): allow setting admin / app cookie security
2022-10-02 22:03:43 +05:30
Lakhan Samani
9cb011e921 feat(dashboard): allow setting admin / app cookie security
Fixes #233
2022-10-02 22:01:22 +05:30
Lakhan Samani
4e1bba2ba8
Merge pull request #246 from jerebtw/main
feat: add app & admin cookie secure variable to dashboard
2022-10-02 21:36:36 +05:30
ruessej
f1509f90f0
feat: Update generated and models_gen 2022-10-01 18:12:49 +02:00
Jerebtw
bd4d48c7c5
fix: schema.graphqls 2022-10-01 17:57:23 +02:00
Jerebtw
0e3242372b
feat: add app & admin cookie secure variable to dashboard
Todo: Generate graphql (i don't work on my PC (Windows))
2022-10-01 17:48:05 +02:00
Lakhan Samani
89cea39c41
Merge pull request #257 from authorizerdev/chore/update-go-gin-1.18.1
chore: update go-gin server to 1.18.1
2022-10-01 17:42:00 +05:30
Lakhan Samani
570a0b9531 chore: update go-gin server to 1.18.1 2022-10-01 17:41:16 +05:30
Lakhan Samani
686b3a4666
Merge pull request #255 from authorizerdev/chore/update-go-1.19.1
chore: update golang to 1.19.1
2022-10-01 17:20:48 +05:30
Lakhan Samani
b266a14108 chore: update golang to 1.19.1 2022-10-01 15:17:11 +05:30
Lakhan Samani
e5972a0dee
Merge pull request #254 from authorizerdev/feat/update-gqlgen-0.17.20
chore: update gqlgen to 0.17.20
2022-10-01 15:15:13 +05:30
Lakhan Samani
6f46f1e6ef chore: update gqlgen to 0.17.20 2022-09-30 15:37:59 +05:30
Lakhan Samani
cfbce17ab8 fix: set same site cookie to none for cross site 2022-09-28 18:42:42 +05:30
Lakhan Samani
aa6601e62c fix: same site cookie 2022-09-28 18:30:30 +05:30
Lakhan Samani
d8ea0c656f
Merge pull request #247 from authorizerdev/fix/same-site-cookie
fix(server): use sameSite as lax by default for app cookie
2022-09-28 11:18:03 +05:30
Lakhan Samani
f5323e0eec fix(server): update comments for host & cookies 2022-09-28 10:36:56 +05:30
Lakhan Samani
b1bc7b5370 fix(server): set default app cookie to lax mode 2022-09-28 09:51:04 +05:30
Lakhan Samani
536fd87c3c fix: debug log 2022-09-27 06:45:38 +05:30
Lakhan Samani
f8c96a9fee
Merge pull request #244 from authorizerdev/fix/remove-user-verification-request-when-deleted
fix: remove entries from otp + verification when user is deleted
2022-09-27 06:44:22 +05:30
Lakhan Samani
837fc781de fix: remove entries from otp + verification when user is deleted
Resolves #234
2022-09-27 00:27:36 +05:30
Lakhan Samani
640bb8c9ed chore: bump app/authorizer-react 1.1.2 2022-09-27 00:07:52 +05:30
Lakhan Samani
d9bba0bbe7
Merge pull request #243 from authorizerdev/fix/bool-env-vars-secure-cookie
fix: app & admin cookie secure variable type while persisting info
2022-09-27 00:03:31 +05:30
Lakhan Samani
f91ec1880f fix: app & admin cookie secure variable type while persisting info
Resolves #241
2022-09-27 00:01:38 +05:30
Lakhan Samani
19e2153379
Update README.md 2022-09-15 12:24:47 +05:30
Lakhan Samani
221009bf0a
Merge pull request #229 from ruessej/main
feat: Add a option to disable httpOnly cookies
2022-09-15 11:22:27 +05:30
ruessej
6085c2d535 Fix incorrect type 2022-09-14 12:24:19 +02:00
Jerebtw
8e0c5e4380 Make the default value true 2022-09-14 11:56:48 +02:00
Lakhan Samani
21b70e4b26
Merge pull request #230 from authorizerdev/fix/github-oauth-scopes
fix: scope for github auth
2022-09-14 11:46:46 +05:30
Lakhan Samani
993693884d fix: scope for github auth 2022-09-14 11:45:38 +05:30
Lakhan Samani
ed849fa6f6 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-09-14 10:44:09 +05:30
Lakhan Samani
aec1f5df53 fix: github endpoint to get user emails 2022-09-14 10:44:01 +05:30
ruessej
195bd1bc6a Add a option to disable httpOnly cookies 2022-09-12 14:37:42 +02:00
Lakhan Samani
45b4c41bca
Merge pull request #228 from Deep-Codes/main 2022-09-10 11:40:11 +05:30
Deepankar
63d486821e
fix: lint 2022-09-10 11:39:01 +05:30
Deep-Codes
4b56afdc98 fix(type): __authorizer__ on window 2022-09-10 11:23:20 +05:30
Lakhan Samani
6455ff956a fix: remove varible log 2022-09-10 10:52:56 +05:30
Lakhan Samani
3898e43fff feat: add button to jwt config as json 2022-09-10 10:50:15 +05:30
Lakhan Samani
2c305e5bde
Update README.md 2022-09-09 10:24:30 +05:30
Lakhan Samani
b8fd08e576
Update README.md 2022-09-09 09:29:27 +05:30
Lakhan Samani
6dafa45051 fix: invalid login message
Resolves #224
2022-09-03 21:48:33 +05:30
Lakhan Samani
ead3514113 chore: update railway template 2022-08-31 13:09:00 +05:30
Lakhan Samani
75a413e5f2 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-08-31 11:02:50 +05:30
Lakhan Samani
91bf0e2478 fix: use replace all 2022-08-31 11:02:46 +05:30
Lakhan Samani
7a1305cf96
Merge pull request #222 from Deep-Codes/main 2022-08-31 07:04:20 +05:30
Deep-Codes
ff5a6ec301 feat(server): add log to show PORT 2022-08-30 23:35:43 +05:30
Lakhan Samani
b7b97b4f8d
Merge pull request #221 from Deep-Codes/main
fix(dashboard): users table overflow
2022-08-30 22:38:49 +05:30
Deep-Codes
d9bc989c74 fix(dashboard): users table overflow 2022-08-30 21:56:28 +05:30
Lakhan Samani
d1f80d4088 feat: add support for twitter login 2022-08-29 08:37:53 +05:30
Lakhan Samani
4b299f0da2 fix: log 2022-08-29 08:19:11 +05:30
Lakhan Samani
ed8006db4c Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-08-29 08:18:42 +05:30
Lakhan Samani
97f6c7d50a fix: authorize endpoint setting user session 2022-08-29 08:18:20 +05:30
Lakhan Samani
5e3f68a180
Merge pull request #216 from szczepad/feat/twitter-login
Feat/twitter login
2022-08-24 08:53:52 +05:30
szczepad
f73d1fc588 feat: Adds login via twitter 2022-08-22 09:25:10 +02:00
szczepad
aa232de426 fix: Uses whitespace as seperator for oauth scopes in state-string
This is necessary, as the previous delimiter (,) was being redacted
after a redirect. This resulted in the scopes not being correctly
parseable and the state not being fetched correctly after the
oauth-callback
2022-08-22 09:25:10 +02:00
Lakhan Samani
34ce754ef6 feat: bootstrap twitter login config 2022-08-22 09:03:29 +02:00
Lakhan Samani
5f385b2016 fix: remove unused file 2022-08-18 07:21:50 +05:30
Lakhan Samani
da7c17271e
Merge pull request #215 from wabscale/main
fix: rootless container
2022-08-18 06:11:32 +05:30
John McCann Cunniff Jr
69fbd631ff
fix: rootless container 2022-08-17 20:33:05 -04:00
Lakhan Samani
deb209e358
Update README.md 2022-08-15 22:28:43 +05:30
Lakhan Samani
ea6b4cbc8d
Update README.md 2022-08-15 22:28:13 +05:30
Lakhan Samani
2f21a09b2e chore: bump app/authorizer-react 1.0.0 2022-08-15 21:06:57 +05:30
Lakhan Samani
4ab775f2c1 fix: apple & linkedin env config 2022-08-13 12:37:04 +05:30
Lakhan Samani
b6e8023104
Merge pull request #211 from authorizerdev/fix/email-template
fix email template
2022-08-13 11:58:07 +05:30
Lakhan Samani
4f1597e5d2 fix: update note on features 2022-08-13 11:57:03 +05:30
Lakhan Samani
4f81d1969e fix email template
- fix verification types
- add design to cassandra db provider for email_template
- fix default email verification types to include update_email
2022-08-13 11:34:24 +05:30
Lakhan Samani
ad3e615ac7
Merge pull request #210 from authorizerdev/fix/dashboard-ui
Fix/dashboard UI
2022-08-13 03:57:19 +05:30
anik-ghosh-au7
e9a2301d2b feat: [dashboard] add env options for multi factor auth 2022-08-11 17:50:45 +05:30
anik-ghosh-au7
48bbfa31af fix: template editor design 2022-08-11 17:08:23 +05:30
anik-ghosh-au7
d7f5f563cc fix: add design to email template 2022-08-11 16:45:59 +05:30
anik-ghosh-au7
6c29149fbe fix: email template editor 2022-08-11 15:08:50 +05:30
Lakhan Samani
bbd4d43317 fix: add padding to editor 2022-08-09 12:10:50 +05:30
Lakhan Samani
c4d2f62657 fix: clear form on close 2022-08-09 11:55:55 +05:30
Lakhan Samani
5d78bf178f fix: email template info 2022-08-09 11:41:51 +05:30
Lakhan Samani
58749497bd fix: payload example for webhook 2022-08-09 10:04:06 +05:30
Lakhan Samani
5c6e643efb
Merge pull request #209 from authorizerdev/feat/send-email-based-on-template
feat: send email based on template
2022-08-09 09:17:29 +05:30
Lakhan Samani
7792cdbc5e fix: template respone & ui 2022-08-09 09:07:47 +05:30
Lakhan Samani
65803c3763 fix: remove todos 2022-08-09 01:53:21 +05:30
Lakhan Samani
81fce1a471 feat: send email based on template 2022-08-09 01:43:37 +05:30
Lakhan Samani
0714b4360b
Merge pull request #206 from authorizerdev/feat/2fa
feat: add mutifactor authentication
2022-08-07 11:11:56 +05:30
Lakhan Samani
8f69d5746e
Merge pull request #207 from authorizerdev/feat/email-template-ui
feat: email template UI + subject
2022-08-07 11:10:44 +05:30
Lakhan Samani
ebc11906ef Merge branch 'feat/2fa' of https://github.com/authorizerdev/authorizer into feat/2fa 2022-08-03 23:20:37 +05:30
Lakhan Samani
465a92de22 feat: add managing mfa 2022-08-03 23:20:23 +05:30
Lakhan Samani
a890013317
Update generate_otp.go 2022-08-02 18:26:05 +05:30
Lakhan Samani
587828b59b feat: add helper for updating all users 2022-08-02 14:12:36 +05:30
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
236045ac54 feat: add resend otp test 2022-07-30 01:12:20 +05:30
Lakhan Samani
d89be44fe5 feat: add sending otp 2022-07-29 19:49:50 +05:30
Lakhan Samani
db4d711cba feat: add subject to email template 2022-07-29 16:15:57 +05:30
Lakhan Samani
0fc9e8ccaa feat: add EnvKeyIsEmailServiceEnabled 2022-07-29 16:00:12 +05:30
anik-ghosh-au7
4e3d73e767 feat: otp resolvers updated 2022-07-29 13:49:46 +05:30
anik-ghosh-au7
e3c58ffbb0 fix: login resolver multifactor auth 2022-07-28 11:18:06 +05:30
anik-ghosh-au7
f12491e42d fix: auth response schema updated 2022-07-27 15:28:12 +05:30
anik-ghosh-au7
d653fac340 Merge branch 'feat/2fa' of https://github.com/authorizerdev/authorizer into feat/2fa 2022-07-27 12:18:51 +05:30
anik-ghosh-au7
9fae8215d2 feat: dashboard - add actions to update is_multi_factor_auth_enabled 2022-07-27 12:18:32 +05:30
Lakhan Samani
4e23e49de4 fix: syntax 2022-07-25 18:08:07 +05:30
anik-ghosh-au7
ef22318d5c feat: add generate_otp util 2022-07-24 10:40:37 +05:30
anik-ghosh-au7
480438fb7a fix: remove duplicate code in verify otp resolver 2022-07-23 20:04:39 +05:30
Lakhan Samani
8db6649e5c
Merge pull request #205 from anik-ghosh-au7/feat/2fa
update: verify otp resolver and test added
2022-07-23 18:37:04 +05:30
anik-ghosh-au7
49cc6033ab update: verify otp resolver and test added 2022-07-23 18:32:31 +05:30
Lakhan Samani
5d903ca170 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/2fa 2022-07-23 16:52:30 +05:30
Lakhan Samani
44280be25a feat: add resolver for verify_otp 2022-07-23 16:44:39 +05:30
Lakhan Samani
f6029fb7bf feat: use upsert for otp + implement otp methods for cassandradb 2022-07-23 16:39:35 +05:30
Lakhan Samani
22ae3bca54 feat: add otp implementation for arangodb 2022-07-23 16:06:52 +05:30
Lakhan Samani
1a27d91957 feat: add otp implementation for mongodb 2022-07-23 16:01:46 +05:30
Lakhan Samani
f6c67243b9 feat: add otp model + implementation for sql 2022-07-23 15:55:06 +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
9ef5f33f7a feat: add is_multi_factor_auth_enabled 2022-07-23 15:26:44 +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
Lakhan Samani
7b13034081 fix: dashboard 2022-05-25 16:34:54 +05:30
Lakhan Samani
7c16900618
Merge pull request #168 from anik-ghosh-au7/fix/app-routes
update: separate routes for login and signup added
2022-05-25 16:07:15 +05:30
Lakhan Samani
d722fe258d
Merge pull request #173 from authorizerdev/feat/logging
feat: add logging system
2022-05-25 15:07:28 +05:30
Lakhan Samani
99dc5ee572 fix: remove unused code 2022-05-25 15:05:51 +05:30
Lakhan Samani
8bee421d0a feat: add flag for log level 2022-05-25 15:04:26 +05:30
Lakhan Samani
714b79e4ab fix: format logs 2022-05-25 12:30:22 +05:30
Lakhan Samani
d886d780b4 fix: replace all logs 2022-05-24 12:50:33 +05:30
Lakhan Samani
d7bb10fd21 feat: add loggging to all resolvers 2022-05-24 12:42:29 +05:30
Lakhan Samani
f5515bec28 fix: merge conflict 2022-05-23 11:54:46 +05:30
Lakhan Samani
b35d86fd40 feat: add logs for http handlers 2022-05-23 11:52:51 +05:30
anik-ghosh-au7
a638f02014 update: seperate routes for login and signup added 2022-05-18 20:04:29 +05:30
Lakhan Samani
2c4bc9adb6
Merge pull request #161 from akash-dutta-au7/fix/env-page-new
Update Dashboard UI
2022-05-15 17:11:13 +05:30
Lakhan Samani
5884802e60
Merge pull request #166 from Vicg853/fix/role-update
Fix/role update
2022-05-15 17:04:42 +05:30
Vicg853
241f977b2a Fixing related files 2022-05-14 23:21:45 -03:00
anik-ghosh-au7
ed855a274a fix: app style remove unnecessary code 2022-05-14 20:20:21 +05:30
Vicg853
049ea64475 Merge branch 'main' of github.com:Vicg853/authorizer into fix/role-update 2022-05-14 05:07:13 -03:00
Vicg853
5e4f34c889 Fixing login isValidRole usage 2022-05-14 04:37:36 -03:00
Lakhan Samani
ab717d956a fix: update role test 2022-05-13 07:49:45 +05:30
Lakhan Samani
6209c4d506
Merge pull request #165 from Vicg853/fix/role-update
Unable to update user role fix
2022-05-13 07:38:45 +05:30
Lakhan Samani
2bc4c74930 fix: remove old logs 2022-05-13 07:28:31 +05:30
Vicg853
1efa419cdf Clean up 2022-05-12 16:43:07 -03:00
Vicg853
4ceb6db4ba Adding possible test error cause comment 2022-05-12 16:40:49 -03:00
Vicg853
9edc8d0fb5 Inverted userRoles by role fix. Roles can now be updated 2022-05-12 16:40:19 -03:00
Lakhan Samani
da0fcb109b feat: setup logours for logging 2022-05-13 00:47:01 +05:30
akash.dutta
3e51a7bd01 login page vertically responsive 2022-05-12 17:06:18 +05:30
akash.dutta
28bed69b2e login page vertically responsive 2022-05-12 15:10:24 +05:30
anik-ghosh-au7
0433d64737 fix: dom nesting bugs 2022-05-12 12:27:25 +05:30
Lakhan Samani
773213e5a4 fix: clean test data 2022-05-11 20:25:57 +05:30
akash.dutta
de44c40de5 login page fixed 2022-05-09 15:46:42 +05:30
akash.dutta
a7fa988bf0 tooltiip added to Invite member button 2022-05-08 12:48:26 +05:30
akash.dutta
538a2d0b59 tooltiip added to Invite member button 2022-05-08 12:43:52 +05:30
akash.dutta
f519f0eb0e tooltiip added to Invite member button 2022-05-08 09:35:50 +05:30
akash.dutta
d5ad4a6e55 blue border on navlink removed 2022-05-07 22:34:26 +05:30
akash.dutta
d9b49ca932 login page responsive 2022-05-07 22:18:04 +05:30
akash.dutta
7c5aab7bf3 all components updated, uncontrolled input error handled 2022-05-07 22:10:29 +05:30
akash.dutta
c783e101d5 test-pull req 2022-05-03 19:53:21 +05:30
akash.dutta
ebccfb18cd test-pull req 2022-05-03 19:50:01 +05:30
Lakhan Samani
b7aeff57af fixes #160 2022-04-30 12:45:08 +05:30
Lakhan Samani
075c287f34 feat: add support for database cert, key, ca-cert 2022-04-23 17:52:02 +05:30
Lakhan Samani
4778827545
Merge pull request #144 from authorizerdev/feat/casandra-db
feat: add support for cassandra db
2022-04-22 21:26:10 +05:30
Lakhan Samani
39c2c364d9 feat: add support for db username, password, host, port 2022-04-22 21:24:39 +05:30
Lakhan Samani
961f2271c1 fix: tests 2022-04-22 19:56:55 +05:30
Lakhan Samani
aaf0831793 feat: add users queries 2022-04-22 16:45:49 +05:30
Lakhan Samani
27cb41c54c feat: add verification_request queries 2022-04-22 11:52:15 +05:30
Lakhan Samani
718b2d535f feat: add session queries 2022-04-21 18:11:15 +05:30
Lakhan Samani
ed6a1ceccc feat: add env queries 2022-04-21 17:54:33 +05:30
Lakhan Samani
fd52d6e5d3 feat: add casandradb provider 2022-04-21 12:36:22 +05:30
Lakhan Samani
325aa88368 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/casandra-db 2022-04-20 23:32:02 +05:30
Lakhan Samani
75e44ff698 fix: cors error for x-authorizer-url 2022-04-10 14:43:19 +05:30
Lakhan Samani
d5f1c5a5eb Resolves #156 2022-04-02 17:34:50 +05:30
Lakhan Samani
39947f1753
Merge pull request #155 from authorizerdev/fix/gateway-based-setup
fix: setting the cookie for proxy setup
2022-03-30 11:51:20 +05:30
Lakhan Samani
4fa9f79c3f fix: setting the cookie for proxy setup 2022-03-30 11:50:22 +05:30
Lakhan Samani
fe73c2f6f8
Update README.md 2022-03-26 07:00:01 +05:30
Lakhan Samani
4a3e3633ea fix: default access token expiry time 2022-03-25 20:29:00 +05:30
Lakhan Samani
dbbe36f6b5
Merge pull request #154 from MedvedewEM/enhancement/access_token_expiry_time
enhancement: add access_token_expiry_time env variable
2022-03-25 18:57:53 +05:30
egor.medvedev
819dd57377 Merge branch 'authorizerdev/authorizer:main' into main 2022-03-25 16:13:46 +03:00
egor.medvedev
044b025ba2 enhancement: add access_token_expiry_time env variable 2022-03-25 15:21:20 +03:00
Lakhan Samani
41b5f00b83 fix: client id make readonly 2022-03-25 00:32:21 +05:30
Lakhan Samani
3c31b7fdc7
Merge pull request #153 from anik-ghosh-au7/develop
fix: update jwt keys persisting old values
2022-03-24 23:34:20 +05:30
Anik Ghosh
7e91c6ca28 fix: update jwt keys persisting old values 2022-03-24 22:53:54 +05:30
Lakhan Samani
b1b43a41ca fix: resetting the keys 2022-03-24 22:19:30 +05:30
Lakhan Samani
f969495178 fix: generate new keys modal 2022-03-24 22:09:32 +05:30
Lakhan Samani
3c4c128931 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-03-24 21:51:00 +05:30
Lakhan Samani
003cec4f48 feat: add tests for revoke and enable access 2022-03-24 21:50:39 +05:30
Lakhan Samani
3e488155dc
Merge pull request #152 from anik-ghosh-au7/feat/generate-new-keys
Feat/generate new keys
2022-03-24 21:34:28 +05:30
Anik Ghosh
4f4a3a91e1 generate-keys-modal updated 2022-03-24 21:29:43 +05:30
Anik Ghosh
a3d9783aef mutation to update jwt vars added 2022-03-24 21:08:10 +05:30
Anik Ghosh
7d77396657 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/generate-new-keys 2022-03-24 19:30:32 +05:30
Lakhan Samani
7a18fc6312 fix: add test to resolvers test 2022-03-24 19:23:43 +05:30
Lakhan Samani
90e2709eeb feat: add mutation to generate new jwt secret & keys
Resolves: #150
2022-03-24 19:21:52 +05:30
Anik Ghosh
4c4743ac24 generate-keys-modal added in dashboard 2022-03-24 18:23:22 +05:30
Anik Ghosh
b2541c8e9a
feat: update user access (#151)
* feat: update user access

* revoked timestamp field updated

* updates

* updates

* updates
2022-03-24 14:13:55 +05:30
Lakhan Samani
1f3dec6ea6 feat: add validate_jwt_token query
Resolves #149
2022-03-24 13:32:30 +05:30
Lakhan Samani
a6b743465f feat: add provider template 2022-03-19 17:41:27 +05:30
Lakhan Samani
f356b4728d
Merge pull request #142 from authorizerdev/feat/add-password-validation
feat: add validation for strong password
2022-03-19 11:31:31 +05:30
Lakhan Samani
ec4ef97766 feat: add validation for strong password 2022-03-17 15:35:07 +05:30
Lakhan Samani
47d67bf3cd fix: update readme.md 2022-03-17 09:33:55 +05:30
Lakhan Samani
0c54da1168 fix: getting started 2022-03-17 09:31:40 +05:30
Lakhan Samani
d6f60ce464 chore: add workflow-dispatch 2022-03-17 00:44:55 +05:30
Lakhan Samani
3aa888b14e fix: use latest authorizer-react 2022-03-17 00:28:11 +05:30
Lakhan Samani
30be32a10b feat: add sample csv 2022-03-17 00:15:47 +05:30
Lakhan Samani
69d781d6cf fix: set password re-direct uri 2022-03-17 00:04:57 +05:30
Lakhan Samani
e4d9c60971
Merge pull request #139 from anik-ghosh-au7/feat/disable-signup
feat: disable user signup
2022-03-16 23:18:43 +05:30
Anik Ghosh
96edb43b67 feat: disable user signup 2022-03-16 22:49:18 +05:30
Lakhan Samani
21fef67c7d Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-03-16 21:52:51 +05:30
Lakhan Samani
9f09823c8b feat: add redirect_uri for signup 2022-03-16 21:52:45 +05:30
Lakhan Samani
1a64149da7
Merge pull request #138 from anik-ghosh-au7/feat/invite-emails
Feat/invite emails
2022-03-16 21:45:34 +05:30
Lakhan Samani
99b846811a fix: token + redirect 2022-03-16 21:44:57 +05:30
Anik Ghosh
df7837f44d updates 2022-03-16 20:22:24 +05:30
Anik Ghosh
d709f53c47 updates 2022-03-16 20:13:18 +05:30
Anik Ghosh
a257b77501 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/invite-emails 2022-03-16 18:07:16 +05:30
Anik Ghosh
2213619ed5 updates 2022-03-16 18:06:51 +05:30
Anik Ghosh
f65ea72944 package-lock.json 2022-03-16 14:10:55 +05:30
Anik Ghosh
32f8c99a71 updates 2022-03-16 14:08:22 +05:30
Anik Ghosh
8ec52a90f1 updates 2022-03-16 14:08:08 +05:30
Anik Ghosh
2498958295 updates 2022-03-16 00:07:58 +05:30
Anik Ghosh
2913fa0603 updates 2022-03-15 23:51:54 +05:30
Anik Ghosh
e126bfddad invite email modal updated 2022-03-15 20:31:54 +05:30
Lakhan Samani
83001b859c
Merge pull request #136 from authorizerdev/feat/invite-member
feat: add resolver for inviting members
2022-03-15 12:51:12 +05:30
Lakhan Samani
74a8024131 feat: add integration test for invite_member 2022-03-15 12:09:54 +05:30
Lakhan Samani
5e6ee8d9b0 fix: setup-password flow 2022-03-15 09:57:09 +05:30
Lakhan Samani
3e7150f872 fix: redirect uri 2022-03-15 09:56:50 +05:30
Lakhan Samani
9a19552f72 feat: add resolver for inviting members 2022-03-15 08:53:48 +05:30
Anik Ghosh
ab01ff249d invite email modal added 2022-03-15 01:24:14 +05:30
Lakhan Samani
1b387f7564 fix: getting version in meta api 2022-03-09 18:55:18 +05:30
Lakhan Samani
8e79ab77b2
Merge pull request #131 from authorizerdev/feat/open-id
Add open id authorization flow with PKCE
2022-03-09 17:27:16 +05:30
Lakhan Samani
2bf6b8f91d fix: remove log 2022-03-09 17:24:53 +05:30
Lakhan Samani
776c0fba8b chore: app dependencies 2022-03-09 17:21:55 +05:30
Lakhan Samani
dd64aa2e79 feat: add version info 2022-03-09 11:53:34 +05:30
Lakhan Samani
157b13baa7 fix: basic auth redirect 2022-03-09 10:10:39 +05:30
Lakhan Samani
d1e284116d fix: verification request model 2022-03-09 07:10:07 +05:30
Lakhan Samani
2f9725d8e1 fix: verification request 2022-03-09 06:41:38 +05:30
Lakhan Samani
ee7aea7bee fix: verify email 2022-03-08 22:55:45 +05:30
Lakhan Samani
5d73df0040 fix: magic link login 2022-03-08 22:41:33 +05:30
Lakhan Samani
60cd317e67 fix: add redirect url to logout 2022-03-08 21:32:42 +05:30
Lakhan Samani
f5bdc8db39 fix: refresh token store info 2022-03-08 21:13:23 +05:30
Lakhan Samani
9eca697a91 fix: refresh token param in string 2022-03-08 19:31:19 +05:30
Lakhan Samani
7136ee924d fix: rotate refresh token 2022-03-08 19:18:33 +05:30
Lakhan Samani
fd9eb7c733 fix: oauth state split 2022-03-08 19:13:45 +05:30
Lakhan Samani
917eaeb2ed feat: don't set cookie in case of offline_access 2022-03-08 18:51:46 +05:30
Lakhan Samani
3bb90acc9e feat: add revoke mutation + handler 2022-03-08 18:49:42 +05:30
Lakhan Samani
a69b8e290c feat: add ability to get access token based on refresh token 2022-03-08 14:56:46 +05:30
Lakhan Samani
674eeeea4e chore: bump authorizer-react 2022-03-08 14:20:11 +05:30
Lakhan Samani
8c2bf6ee0d fix: add token information in redirect url 2022-03-08 12:36:26 +05:30
Lakhan Samani
57bc091499 fix state management 2022-03-07 23:44:19 +05:30
Lakhan Samani
128a2a8f75 feat: add support for response mode 2022-03-07 18:49:18 +05:30
Lakhan Samani
7b09a8817c fix: env encryption 2022-03-07 16:16:54 +05:30
Lakhan Samani
1d61840c6d fix: env decryption + remove log 2022-03-07 15:35:33 +05:30
Lakhan Samani
4b25e8941c fix: env decryption 2022-03-07 15:33:39 +05:30
Lakhan Samani
136eda15bf fix: env encryption 2022-03-07 15:29:37 +05:30
Lakhan Samani
eea6349318 chore: update app version 2022-03-07 12:33:42 +05:30
Lakhan Samani
513b5d2948 fix: env client secret 2022-03-07 12:23:45 +05:30
Lakhan Samani
e61dc2f08a fix: oauth login 2022-03-07 08:31:39 +05:30
Lakhan Samani
07552bc0b1 fix: use url safe code verifier 2022-03-05 13:50:59 +05:30
Lakhan Samani
0787a3b494 feat: add token endpoint 2022-03-04 12:56:11 +05:30
Lakhan Samani
2946428ab8 feat: add userinfo + logout 2022-03-04 00:36:27 +05:30
Lakhan Samani
5c7d32ec16 fix: remove compat cookie 2022-03-03 09:21:48 +05:30
Lakhan Samani
f0f2e0b6c8 fix: auth flow 2022-03-02 17:42:31 +05:30
Lakhan Samani
5399ea8f32 feat: add session token 2022-02-28 21:26:49 +05:30
Lakhan Samani
4830a7e9ac feat: add client secret 2022-02-28 13:14:16 +05:30
Lakhan Samani
df1c56bb1c fix: tests 2022-02-28 07:55:01 +05:30
Lakhan Samani
b68d9ce661 fix: update_env resolver 2022-02-26 20:36:22 +05:30
Lakhan Samani
145091dce1 feat: add well-known jwks.json endpoint 2022-02-26 18:14:43 +05:30
Lakhan Samani
ad46210112 fix: report error on initialization 2022-02-26 10:06:26 +05:30
Lakhan Samani
4e19f73845 fix: segregate env setup 2022-02-26 09:44:55 +05:30
Lakhan Samani
332269ecf9 feat: add well-known config endpoint 2022-02-23 11:24:52 +05:30
Lakhan Samani
dfa96f09a0 feat: add required jwt claims 2022-02-22 11:06:47 +05:30
Lakhan Samani
5bf26f7385 fix: setting custom access token script env 2022-02-18 16:45:12 +05:30
Lakhan Samani
1b269dc6db fix: rename session_client -> redis_client 2022-02-18 09:21:02 +05:30
Lakhan Samani
ce9a115a14
Merge pull request #128 from agarwal-nitesh/feat/redis_cluster_client
Add redis cluster client as a session store.
2022-02-18 09:19:24 +05:30
Nitesh Agarwal
f2f4c72aa6 Add redis cluster client as a session store. 2022-02-17 20:49:54 +05:30
Samyak Bhuta
9970eb16c9
Update README
Minor changes.
2022-02-17 15:04:05 +05:30
Lakhan Samani
23e53286bd
Merge pull request #124 from anik-ghosh-au7/feat/jwt-types
support for more jwt encryption types added
2022-02-14 18:56:06 +05:30
Anik Ghosh
47acff05e2 support for more jwt encryption types added 2022-02-14 16:00:35 +05:30
Lakhan Samani
5572928619 fix: remove redundunt break statement 2022-02-12 22:55:33 +05:30
Lakhan Samani
85b4cd6339 Add support for maria db 2022-02-12 22:49:53 +05:30
Lakhan Samani
f0d38ab260
Merge pull request #121 from authorizerdev/feat/add-jwt-algos
feat: add jwt algos
2022-02-12 19:37:28 +05:30
377 changed files with 53585 additions and 11344 deletions

View File

@ -9,3 +9,4 @@ build
data.db data.db
app/node_modules app/node_modules
app/build app/build
certs/

View File

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

15
.env.test Normal file
View File

@ -0,0 +1,15 @@
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"
TWILIO_API_KEY=test
TWILIO_API_SECRET=test
TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_SENDER=909921212112
SENDER_NAME="Authorizer"
AWS_REGION=ap-south-1

36
.gitea/workflows/main.yml Normal file
View File

@ -0,0 +1,36 @@
name: "deploy"
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Cloning repo
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get Repo Name
id: repo_name
run: echo "::set-output name=repo::$(echo ${GITHUB_REPOSITORY##*/})"
- name: Get Branch Name
id: branch_name
run: echo "::set-output name=branch::$(echo ${GITHUB_REF##*/})"
- name: Push branch 'discours-dev' to staging
if: steps.branch_name.outputs.branch == 'discours-dev'
uses: dokku/github-action@master
with:
branch: "main"
git_remote_url: "ssh://dokku@staging.discours.io:22/authorizer"
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Push branch 'discours' to v2.discours.io
if: steps.branch_name.outputs.branch == 'discours'
uses: dokku/github-action@master
with:
branch: "main"
git_remote_url: "ssh://dokku@v2.discours.io:22/authorizer"
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
git_push_flags: '--force'

View File

@ -45,12 +45,30 @@ Please ask as many questions as you need, either directly in the issue or on [Di
1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**) 1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**)
2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1 2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1
3. Change directory to authorizer: `cd authorizer` 3. Change directory to authorizer: `cd authorizer`
5. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/) 4. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
6. Build Dashboard `make build-dashboard` 5. Build Dashboard `make build-dashboard`
7. Build App `make build-app` 6. Build App `make build-app`
8. Build Server `make clean && make` 7. Build Server `make clean && make`
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command > Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command. In that case you will have to build `dashboard` & `app` manually using `npm run build` on both dirs.
9. Run binary `./build/server` 8. Run binary `./build/server`
### Updating GraphQL schema
- Modify `server/graph/schema.graphqls` file
- Run `make generate-graphql` this will update the models and required methods
- If a new mutation or query is added
- Write the implementation for the new resolver in `server/resolvers/NEW_RESOLVER.GO`
- Update `server/graph/schema.resolvers.go` with the new resolver method
### Adding support for new database
- Run `make generate-db-template dbname=NEW_DB_NAME`
eg `make generate-db-template dbname=dynamodb`
This command will generate a folder in server/db/providers/ with name specified in the above command.
One will have to implement methods present in that folder.
> Note: Connection for database and schema changes are written in `server/db/providers/DB_NAME/provider.go` > `NewProvider` method is called for any given db based on the env variables present.
### Testing ### Testing
@ -87,145 +105,145 @@ For manually testing using graphql playground, you can paste following queries a
```gql ```gql
mutation Signup { mutation Signup {
signup( signup(
params: { params: {
email: "lakhan@yopmail.com" email: "lakhan@yopmail.com"
password: "test" password: "test"
confirm_password: "test" confirm_password: "test"
given_name: "lakhan" given_name: "lakhan"
} }
) { ) {
message message
user { user {
id id
family_name family_name
given_name given_name
email email
email_verified email_verified
} }
} }
} }
mutation ResendEamil { mutation ResendEamil {
resend_verify_email( resend_verify_email(
params: { email: "lakhan@yopmail.com", identifier: "basic_auth_signup" } params: { email: "lakhan@yopmail.com", identifier: "basic_auth_signup" }
) { ) {
message message
} }
} }
query GetVerifyRequests { query GetVerifyRequests {
_verification_requests { _verification_requests {
id id
token token
expires expires
identifier identifier
} }
} }
mutation VerifyEmail { mutation VerifyEmail {
verify_email(params: { token: "" }) { verify_email(params: { token: "" }) {
access_token access_token
expires_at expires_at
user { user {
id id
email email
given_name given_name
email_verified email_verified
} }
} }
} }
mutation Login { mutation Login {
login(params: { email: "lakhan@yopmail.com", password: "test" }) { login(params: { email: "lakhan@yopmail.com", password: "test" }) {
access_token access_token
expires_at expires_at
user { user {
id id
family_name family_name
given_name given_name
email email
} }
} }
} }
query GetSession { query GetSession {
session { session {
access_token access_token
expires_at expires_at
user { user {
id id
given_name given_name
family_name family_name
email email
email_verified email_verified
signup_methods signup_methods
created_at created_at
updated_at updated_at
} }
} }
} }
mutation ForgotPassword { mutation ForgotPassword {
forgot_password(params: { email: "lakhan@yopmail.com" }) { forgot_password(params: { email: "lakhan@yopmail.com" }) {
message message
} }
} }
mutation ResetPassword { mutation ResetPassword {
reset_password( reset_password(
params: { token: "", password: "test", confirm_password: "test" } params: { token: "", password: "test", confirm_password: "test" }
) { ) {
message message
} }
} }
mutation UpdateProfile { mutation UpdateProfile {
update_profile(params: { family_name: "samani" }) { update_profile(params: { family_name: "samani" }) {
message message
} }
} }
query GetUsers { query GetUsers {
_users { _users {
id id
email email
email_verified email_verified
given_name given_name
family_name family_name
picture picture
signup_methods signup_methods
phone_number phone_number
} }
} }
mutation MagicLinkLogin { mutation MagicLinkLogin {
magic_link_login(params: { email: "test@yopmail.com" }) { magic_link_login(params: { email: "test@yopmail.com" }) {
message message
} }
} }
mutation Logout { mutation Logout {
logout { logout {
message message
} }
} }
mutation UpdateUser { mutation UpdateUser {
_update_user( _update_user(
params: { params: {
id: "dafc9400-d603-4ade-997c-83fcd54bbd67" id: "dafc9400-d603-4ade-997c-83fcd54bbd67"
roles: ["user", "admin"] roles: ["user", "admin"]
} }
) { ) {
email email
roles roles
} }
} }
mutation DeleteUser { mutation DeleteUser {
_delete_user(params: { email: "signup.test134523@yopmail.com" }) { _delete_user(params: { email: "signup.test134523@yopmail.com" }) {
message message
} }
} }
``` ```

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: authorizerdev

View File

@ -1,22 +1,45 @@
on: on:
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
type: choice
options:
- info
- warning
- debug
tags:
description: 'Tags'
required: false
type: boolean
release: release:
types: [created] types: [created]
jobs: jobs:
releases: releases:
name: Release Authorizer Binary name: Release Authorizer
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '16' node-version: '16'
- # Add support for more platforms with QEMU (optional)
# https://github.com/docker/setup-qemu-action
name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
platforms: linux/amd64,linux/arm64
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: '^1.17.3' go-version: '^1.19.1'
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get install build-essential wget zip gcc-mingw-w64 && \ sudo apt-get install build-essential wget zip libc6-dev-arm64-cross && \
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH && \ echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH && \
wget --no-check-certificate --progress=dot:mega https://github.com/wangyoucao577/assets-uploader/releases/download/v0.3.0/github-assets-uploader-v0.3.0-linux-amd64.tar.gz -O github-assets-uploader.tar.gz && \ wget --no-check-certificate --progress=dot:mega https://github.com/wangyoucao577/assets-uploader/releases/download/v0.3.0/github-assets-uploader-v0.3.0-linux-amd64.tar.gz -O github-assets-uploader.tar.gz && \
tar -zxf github-assets-uploader.tar.gz && \ tar -zxf github-assets-uploader.tar.gz && \
@ -29,25 +52,28 @@ jobs:
run: whereis go run: whereis go
- name: Print Go Version - name: Print Go Version
run: go version run: go version
- name: Install gox
run: go install github.com/mitchellh/gox@latest
- name: Set VERSION env - name: Set VERSION env
run: echo VERSION=$(basename ${GITHUB_REF}) >> ${GITHUB_ENV} run: echo VERSION=$(basename ${GITHUB_REF}) >> ${GITHUB_ENV}
- name: Copy .env file - name: Copy .env file
run: mv .env.sample .env run: mv .env.sample .env
- name: Package files for windows - name: Build package
run: | run: |
make clean && \ make clean && \
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \ make build && \
mv build/server build/server.exe && \ mkdir -p authorizer-${VERSION}-darwin-arm64/build authorizer-${VERSION}-darwin-arm64/app authorizer-${VERSION}-darwin-arm64/dashboard && cp build/darwin/arm64/server authorizer-${VERSION}-darwin-arm64/build/ && cp .env authorizer-${VERSION}-darwin-arm64/.env && cp -rf app/build authorizer-${VERSION}-darwin-arm64/app/build && cp -rf templates authorizer-${VERSION}-darwin-arm64/ && cp -rf dashboard/build authorizer-${VERSION}-darwin-arm64/dashboard/build && tar cvfz authorizer-${VERSION}-darwin-arm64.tar.gz authorizer-${VERSION}-darwin-arm64 && \
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates dashboard/build mkdir -p authorizer-${VERSION}-darwin-amd64/build authorizer-${VERSION}-darwin-amd64/app authorizer-${VERSION}-darwin-amd64/dashboard && cp build/darwin/amd64/server authorizer-${VERSION}-darwin-amd64/build/ && cp .env authorizer-${VERSION}-darwin-amd64/.env && cp -rf app/build authorizer-${VERSION}-darwin-amd64/app/build && cp -rf templates authorizer-${VERSION}-darwin-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-darwin-amd64/dashboard/build && tar cvfz authorizer-${VERSION}-darwin-amd64.tar.gz authorizer-${VERSION}-darwin-amd64 && \
- name: Package files for linux mkdir -p authorizer-${VERSION}-linux-amd64/build authorizer-${VERSION}-linux-amd64/app authorizer-${VERSION}-linux-amd64/dashboard && cp build/linux/amd64/server authorizer-${VERSION}-linux-amd64/build/ && cp .env authorizer-${VERSION}-linux-amd64/.env && cp -rf app/build authorizer-${VERSION}-linux-amd64/app/build && cp -rf templates authorizer-${VERSION}-linux-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-linux-amd64/dashboard/build && tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz authorizer-${VERSION}-linux-amd64 && \
run: | mkdir -p authorizer-${VERSION}-linux-arm64/build authorizer-${VERSION}-linux-arm64/app authorizer-${VERSION}-linux-arm64/dashboard && cp build/linux/arm64/server authorizer-${VERSION}-linux-arm64/build/ && cp .env authorizer-${VERSION}-linux-arm64/.env && cp -rf app/build authorizer-${VERSION}-linux-arm64/app/build && cp -rf templates authorizer-${VERSION}-linux-arm64/ && cp -rf dashboard/build authorizer-${VERSION}-linux-arm64/dashboard/build && tar cvfz authorizer-${VERSION}-linux-arm64.tar.gz authorizer-${VERSION}-linux-arm64 && \
make clean && \ mkdir -p authorizer-${VERSION}-windows-amd64/build authorizer-${VERSION}-windows-amd64/app authorizer-${VERSION}-windows-amd64/dashboard && cp build/windows/amd64/server.exe authorizer-${VERSION}-windows-amd64/build/ && cp .env authorizer-${VERSION}-windows-amd64/.env && cp -rf app/build authorizer-${VERSION}-windows-amd64/app/build && cp -rf templates authorizer-${VERSION}-windows-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-windows-amd64/dashboard/build && zip -vr authorizer-${VERSION}-windows-amd64.zip authorizer-${VERSION}-windows-amd64
CGO_ENABLED=1 make && \
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates dashboard/build
- name: Upload assets - name: Upload assets
run: | run: |
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \ github-assets-uploader -f authorizer-${VERSION}-darwin-arm64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
github-assets-uploader -f authorizer-${VERSION}-darwin-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
github-assets-uploader -f authorizer-${VERSION}-linux-arm64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:
@ -59,6 +85,11 @@ jobs:
uses: docker/metadata-action@v3 uses: docker/metadata-action@v3
with: with:
images: lakhansamani/authorizer images: lakhansamani/authorizer
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
@ -67,5 +98,6 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
build-args: | build-args: |
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}

9
.gitignore vendored
View File

@ -8,6 +8,15 @@ dashboard/build
build build
.env .env
data.db data.db
test.db
.DS_Store .DS_Store
.env.local .env.local
*.tar.gz *.tar.gz
.vscode/
.yalc
yalc.lock
certs/
*-shm
*-wal
.idea
*.iml

View File

@ -1,4 +1,4 @@
FROM golang:1.17-alpine as go-builder FROM golang:1.21.3-alpine3.18 AS go-builder
WORKDIR /authorizer WORKDIR /authorizer
COPY server server COPY server server
COPY Makefile . COPY Makefile .
@ -11,7 +11,7 @@ RUN apk add build-base &&\
make clean && make && \ make clean && make && \
chmod 777 build/server chmod 777 build/server
FROM node:17-alpine3.12 as node-builder FROM node:20-alpine3.18 AS node-builder
WORKDIR /authorizer WORKDIR /authorizer
COPY app app COPY app app
COPY dashboard dashboard COPY dashboard dashboard
@ -20,14 +20,16 @@ RUN apk add build-base &&\
make build-app && \ make build-app && \
make build-dashboard make build-dashboard
FROM alpine:latest FROM alpine:3.18
WORKDIR /root/ RUN adduser -D -h /authorizer -u 1000 -k /dev/null authorizer
WORKDIR /authorizer
RUN mkdir app dashboard RUN mkdir app dashboard
COPY --from=node-builder /authorizer/app/build app/build COPY --from=node-builder --chown=nobody:nobody /authorizer/app/build app/build
COPY --from=node-builder /authorizer/app/favicon_io app/favicon_io COPY --from=node-builder --chown=nobody:nobody /authorizer/app/favicon_io app/favicon_io
COPY --from=node-builder /authorizer/dashboard/build dashboard/build COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/build dashboard/build
COPY --from=node-builder /authorizer/dashboard/favicon_io dashboard/favicon_io COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/favicon_io dashboard/favicon_io
COPY --from=go-builder /authorizer/build build COPY --from=go-builder --chown=nobody:nobody /authorizer/build build
COPY templates templates COPY templates templates
EXPOSE 8080 EXPOSE 8080
USER authorizer
CMD [ "./build/server" ] CMD [ "./build/server" ]

View File

@ -3,6 +3,12 @@ VERSION := $(or $(VERSION),$(DEFAULT_VERSION))
cmd: cmd:
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server' cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
build:
cd server && gox \
-osarch="linux/amd64 linux/arm64 darwin/arm64 darwin/amd64 windows/amd64" \
-ldflags "-w -X main.VERSION=$(VERSION)" \
-output="../build/{{.OS}}/{{.Arch}}/server" \
./...
build-app: build-app:
cd app && npm i && npm run build cd app && npm i && npm run build
build-dashboard: build-dashboard:
@ -10,7 +16,44 @@ build-dashboard:
clean: clean:
rm -rf build rm -rf build
test: test:
cd server && go clean --testcache && go test -v ./test rm -rf server/test/test.db server/test/test.db-shm server/test/test.db-wal && rm -rf test.db test.db-shm test.db-wal && cd server && go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./test
generate: test-mongodb:
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
cd server && go clean --testcache && TEST_DBS="mongodb" go test -p 1 -v ./test
docker rm -vf authorizer_mongodb_db
test-scylladb:
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
cd server && go clean --testcache && TEST_DBS="scylladb" go test -p 1 -v ./test
docker rm -vf authorizer_scylla_db
test-arangodb:
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.10.3
cd server && go clean --testcache && TEST_DBS="arangodb" go test -p 1 -v ./test
docker rm -vf authorizer_arangodb
test-dynamodb:
docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest
cd server && go clean --testcache && TEST_DBS="dynamodb" go test -p 1 -v ./test
docker rm -vf dynamodb-local-test
test-couchbase:
docker run -d --name couchbase-local-test -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase:latest
sh scripts/couchbase-test.sh
cd server && go clean --testcache && TEST_DBS="couchbase" go test -p 1 -v ./test
docker rm -vf couchbase-local-test
test-all-db:
rm -rf server/test/test.db server/test/test.db-shm server/test/test.db-wal && rm -rf test.db test.db-shm test.db-wal
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.10.3
docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest
docker run -d --name couchbase-local-test -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase:latest
sh scripts/couchbase-test.sh
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb,dynamodb,couchbase" go test -p 1 -v ./test
docker rm -vf authorizer_scylla_db
docker rm -vf authorizer_mongodb_db
docker rm -vf authorizer_arangodb
docker rm -vf dynamodb-local-test
docker rm -vf couchbase-local-test
generate-graphql:
cd server && go run github.com/99designs/gqlgen generate && go mod tidy
generate-db-template:
cp -rf server/db/providers/provider_template server/db/providers/${dbname}
find server/db/providers/${dbname} -type f -exec sed -i -e 's/provider_template/${dbname}/g' {} \;

116
README.md
View File

@ -7,44 +7,42 @@
Authorizer Authorizer
</h1> </h1>
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/),[ArangoDB](https://www.arangodb.com/)). **Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports 11+ databases including [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [YugaByte](https://www.yugabyte.com/), [MariaDB](https://mariadb.org/), [PlanetScale](https://planetscale.com/), [CassandraDB](https://cassandra.apache.org/_/index.html), [ScyllaDB](https://www.scylladb.com/), [MongoDB](https://mongodb.com/), [ArangoDB](https://www.arangodb.com/)).
## Table of contents For more information check:
- [Introduction](#introduction)
- [Getting Started](#getting-started)
- [Contributing](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md)
- [Docs](http://docs.authorizer.dev/) - [Docs](http://docs.authorizer.dev/)
- [Join Community](https://discord.gg/Zv2D5h6kkK) - [Discord Community](https://discord.gg/Zv2D5h6kkK)
- [Contributing Guide](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md)
# Introduction # Introduction
<img src="https://github.com/authorizerdev/authorizer/blob/main/assets/authorizer-architecture.png" style="height:20em"/> <img src="https://docs.authorizer.dev/images/authorizer-arch.png" style="height:20em"/>
#### We offer the following functionality #### We offer the following functionality
- ✅ Sign-in / Sign-up with email ID and password - ✅ Sign-in / Sign-up with email ID and password
- ✅ Secure session management - ✅ Secure session management
- ✅ Email verification - ✅ Email verification
- ✅ OAuth2 and OpenID compatible APIs
- ✅ APIs to update profile securely - ✅ APIs to update profile securely
- ✅ Forgot password flow using email - ✅ Forgot password flow using email
- ✅ Social logins (Google, Github, Facebook, more coming soon) - ✅ Social logins (Google, Github, Facebook, LinkedIn, Apple more coming soon)
- ✅ Role-based access management - ✅ Role-based access management
- ✅ Password-less login with email and magic link - ✅ Password-less login with magic link login
- ✅ Multi factor authentication
- ✅ Email templating
- ✅ Webhooks
## Roadmap ## Roadmap
- Support more JWT encryption algorithms (Currently supporting HS256) - [VueJS SDK](https://github.com/authorizerdev/authorizer-vue)
- 2 Factor authentication - [Svelte SDK](https://github.com/authorizerdev/authorizer-svelte)
- Back office (Admin dashboard to manage user) - [Golang SDK](https://github.com/authorizerdev/authorizer-go)
- Support more database
- VueJS SDK
- Svelte SDK
- React Native SDK - React Native SDK
- Flutter SDK - Flutter SDK
- Android Native SDK - Android Native SDK
- iOS native SDK - iOS native SDK
- Golang SDK
- Python SDK - Python SDK
- PHP SDK - PHP SDK
- WordPress plugin - WordPress plugin
@ -59,35 +57,44 @@
# Getting Started # Getting Started
## Trying out Authorizer ## Step 1: Get Authorizer Instance
### Deploy Production Ready Instance
Deploy production ready Authorizer instance using one click deployment options available below
| **Infra provider** | **One-click link** | **Additional information** |
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
| Railway.app | <a href="https://railway.app/new/template/nwXp1C?referralCode=FEF4uT"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
| Koyeb | <a target="_blank" href="https://app.koyeb.com/deploy?name=authorizer&type=docker&image=docker.io/lakhansamani/authorizer&env[PORT]=8000&env[DATABASE_TYPE]=postgres&env[DATABASE_URL]=CHANGE_ME&ports=8000;http;/"><img alt="Deploy to Koyeb" src="https://www.koyeb.com/static/images/deploy/button.svg" /></a> | [docs](https://docs.authorizer.dev/deployment/koyeb) |
| RepoCloud | <a href="https://repocloud.io/details/?app_id=174"><img src="https://d16t0pc4846x52.cloudfront.net/deploy.png" alt="Deploy on RepoCloud"></a> | [docs](https://repocloud.io/details/?app_id=174) |
### Deploy Authorizer Using Source Code
This guide helps you practice using Authorizer to evaluate it before you use it in a production environment. It includes instructions for installing the Authorizer server in local or standalone mode. This guide helps you practice using Authorizer to evaluate it before you use it in a production environment. It includes instructions for installing the Authorizer server in local or standalone mode.
- [Install using source code](#install-using-source-code) #### Install using source code
- [Install using binaries](#install-using-binaries)
- [Install instance on heroku](#install-instance-on-Heroku)
- [Install instance on railway.app](#install-instance-on-railway)
## Install using source code #### Prerequisites
### Prerequisites
- OS: Linux or macOS or windows - OS: Linux or macOS or windows
- Go: (Golang)(https://golang.org/dl/) >= v1.15 - Go: (Golang)(https://golang.org/dl/) >= v1.15
### Project Setup #### Project Setup
1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**) 1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**)
2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1 2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1
3. Change directory to authorizer: `cd authorizer` 3. Change directory to authorizer: `cd authorizer`
5. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/) 4. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
6. Build Dashboard `make build-dashboard` 5. Build Dashboard `make build-dashboard`
7. Build App `make build-app` 6. Build App `make build-app`
8. Build Server `make clean && make` 7. Build Server `make clean && make`
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command > Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command. In that case you will have to build `dashboard` & `app` manually using `npm run build` on both dirs.
9. Run binary `./build/server` 8. Run binary `./build/server`
## Install using binaries ### Deploy Authorizer using binaries
Deploy / Try Authorizer using binaries. With each [Authorizer Release](https://github.com/authorizerdev/authorizer/releases) Deploy / Try Authorizer using binaries. With each [Authorizer Release](https://github.com/authorizerdev/authorizer/releases)
binaries are baked with required deployment files and bundled. You can download a specific version of it for the following operating systems: binaries are baked with required deployment files and bundled. You can download a specific version of it for the following operating systems:
@ -95,7 +102,7 @@ binaries are baked with required deployment files and bundled. You can download
- Mac OSX - Mac OSX
- Linux - Linux
### Step 1: Download and unzip bundle #### Download and unzip bundle
- Download the Bundle for the specific OS from the [release page](https://github.com/authorizerdev/authorizer/releases) - Download the Bundle for the specific OS from the [release page](https://github.com/authorizerdev/authorizer/releases)
@ -115,11 +122,7 @@ binaries are baked with required deployment files and bundled. You can download
cd authorizer cd authorizer
``` ```
### Step 2: Configure environment variables #### Step 3: Start Authorizer
Required environment variables are pre-configured in `.env` file. But based on the production requirements, please configure more environment variables. You can refer to [environment variables docs](/core/env) for more information.
### Step 3: Start Authorizer
- Run following command to start authorizer - Run following command to start authorizer
@ -131,20 +134,20 @@ Required environment variables are pre-configured in `.env` file. But based on t
> Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server` > Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server`
Deploy production ready Authorizer instance using one click deployment options available below ## Step 2: Setup Instance
| **Infra provider** | **One-click link** | **Additional information** | - Open authorizer instance endpoint in browser
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: | - Sign up as an admin with a secure password
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&amp;plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) | - Configure environment variables from authorizer dashboard. Check env [docs](/core/env) for more information
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) | > Note: `DATABASE_URL`, `DATABASE_TYPE` and `DATABASE_NAME` are only configurable via platform envs
### Things to consider ### Things to consider
- For social logins, you will need respective social platform key and secret - For social logins, you will need respective social platform key and secret
- For having verified users, you will need an SMTP server with an email address and password using which system can send emails. The system will send a verification link to an email address. Once an email is verified then, only able to access it. - For having verified users, you will need an SMTP server with an email address and password using which system can send emails. The system will send a verification link to an email address. Once an email is verified then, only able to access it.
> Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅 > Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
- For persisting user sessions, you will need Redis URL (not in case of railway.app). If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server. - For persisting user sessions, you will need Redis URL (not in case of railway app). If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server.
## Testing ## Testing
@ -163,8 +166,9 @@ This example demonstrates how you can use [`@authorizerdev/authorizer-js`](/auth
<script type="text/javascript"> <script type="text/javascript">
const authorizerRef = new authorizerdev.Authorizer({ const authorizerRef = new authorizerdev.Authorizer({
authorizerURL: `AUTHORIZER_URL`, authorizerURL: `YOUR_AUTHORIZER_INSTANCE_URL`,
redirectURL: window.location.origin, redirectURL: window.location.origin,
clientID: 'YOUR_CLIENT_ID', // obtain your client id from authorizer dashboard
}); });
// use the button selector as per your application // use the button selector as per your application
@ -175,15 +179,19 @@ This example demonstrates how you can use [`@authorizerdev/authorizer-js`](/auth
}); });
async function onLoad() { async function onLoad() {
const res = await authorizerRef.browserLogin(); const res = await authorizerRef.authorize({
if (res && res.user) { response_type: 'code',
use_refresh_token: false,
});
if (res && res.access_token) {
// you can use user information here, eg: // you can use user information here, eg:
/** const user = await authorizerRef.getProfile({
const userSection = document.getElementById('user'); Authorization: `Bearer ${res.access_token}`,
const logoutSection = document.getElementById('logout-section'); });
logoutSection.classList.toggle('hide'); const userSection = document.getElementById('user');
userSection.innerHTML = `Welcome, ${res.user.email}`; const logoutSection = document.getElementById('logout-section');
*/ logoutSection.classList.toggle('hide');
userSection.innerHTML = `Welcome, ${user.email}`;
} }
} }
onLoad(); onLoad();

6
app/.prettierrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"useTabs": true
}

973
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,13 +5,14 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js", "build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
"start": "NODE_ENV=development node ./esbuild.config.js" "start": "NODE_ENV=development node ./esbuild.config.js",
"format": "prettier --write 'src/**/*.(ts|tsx|js|jsx)'"
}, },
"keywords": [], "keywords": [],
"author": "Lakhan Samani", "author": "Lakhan Samani",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-react": "latest", "@authorizerdev/authorizer-react": "^1.3.2",
"@types/react": "^17.0.15", "@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17", "esbuild": "^0.12.17",
@ -19,9 +20,12 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-is": "^17.0.2", "react-is": "^17.0.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"styled-components": "^5.3.0",
"typescript": "^4.3.5" "typescript": "^4.3.5"
}, },
"devDependencies": { "devDependencies": {
"@types/react-router-dom": "^5.1.8" "@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.11",
"prettier": "2.7.1"
} }
} }

626
app/pnpm-lock.yaml Normal file
View File

@ -0,0 +1,626 @@
lockfileVersion: 5.4
specifiers:
'@authorizerdev/authorizer-react': ^1.1.9
'@types/react': ^17.0.15
'@types/react-dom': ^17.0.9
'@types/react-router-dom': ^5.1.8
'@types/styled-components': ^5.1.11
esbuild: ^0.12.17
prettier: 2.7.1
react: ^17.0.2
react-dom: ^17.0.2
react-is: ^17.0.2
react-router-dom: ^5.2.0
styled-components: ^5.3.0
typescript: ^4.3.5
dependencies:
'@authorizerdev/authorizer-react': 1.1.9_react@17.0.2
'@types/react': 17.0.53
'@types/react-dom': 17.0.19
esbuild: 0.12.29
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
react-is: 17.0.2
react-router-dom: 5.3.4_react@17.0.2
styled-components: 5.3.9_fane7jikarojcev26y27hpbhu4
typescript: 4.9.5
devDependencies:
'@types/react-router-dom': 5.3.3
'@types/styled-components': 5.1.26
prettier: 2.7.1
packages:
/@authorizerdev/authorizer-js/1.2.1:
resolution: {integrity: sha512-/nFARvsHyZUsGFKrcYi8hgpnbThYR/NMJ2BJdQpWy/x7QsBnfLeCChBYWncbYHSIjFCa5PPKKfvhXM56HqVqsw==}
engines: {node: '>=10'}
dependencies:
cross-fetch: 3.1.5
transitivePeerDependencies:
- encoding
dev: false
/@authorizerdev/authorizer-react/1.1.9_react@17.0.2:
resolution: {integrity: sha512-BlB4ixEm9nf+yjZ9OqIWbx5fMTmzeByEsNDAd5iYkt6HB+3Sk53DGiO5h6SgJznzPyqAwl8yg6y/QgbZreDTFA==}
engines: {node: '>=10'}
peerDependencies:
react: '>=16'
dependencies:
'@authorizerdev/authorizer-js': 1.2.1
react: 17.0.2
transitivePeerDependencies:
- encoding
dev: false
/@babel/code-frame/7.18.6:
resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.18.6
dev: false
/@babel/generator/7.21.3:
resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.3
'@jridgewell/gen-mapping': 0.3.2
'@jridgewell/trace-mapping': 0.3.17
jsesc: 2.5.2
dev: false
/@babel/helper-annotate-as-pure/7.18.6:
resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.3
dev: false
/@babel/helper-environment-visitor/7.18.9:
resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-function-name/7.21.0:
resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.20.7
'@babel/types': 7.21.3
dev: false
/@babel/helper-hoist-variables/7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.3
dev: false
/@babel/helper-module-imports/7.18.6:
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.3
dev: false
/@babel/helper-split-export-declaration/7.18.6:
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.3
dev: false
/@babel/helper-string-parser/7.19.4:
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-validator-identifier/7.19.1:
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/highlight/7.18.6:
resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-validator-identifier': 7.19.1
chalk: 2.4.2
js-tokens: 4.0.0
dev: false
/@babel/parser/7.21.3:
resolution: {integrity: sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.21.3
dev: false
/@babel/runtime/7.21.0:
resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
dev: false
/@babel/template/7.20.7:
resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
'@babel/parser': 7.21.3
'@babel/types': 7.21.3
dev: false
/@babel/traverse/7.21.3_supports-color@5.5.0:
resolution: {integrity: sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
'@babel/generator': 7.21.3
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.21.0
'@babel/helper-hoist-variables': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
'@babel/parser': 7.21.3
'@babel/types': 7.21.3
debug: 4.3.4_supports-color@5.5.0
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: false
/@babel/types/7.21.3:
resolution: {integrity: sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.19.4
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
dev: false
/@emotion/is-prop-valid/1.2.0:
resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==}
dependencies:
'@emotion/memoize': 0.8.0
dev: false
/@emotion/memoize/0.8.0:
resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==}
dev: false
/@emotion/stylis/0.8.5:
resolution: {integrity: sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==}
dev: false
/@emotion/unitless/0.7.5:
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
dev: false
/@jridgewell/gen-mapping/0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.14
'@jridgewell/trace-mapping': 0.3.17
dev: false
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
dev: false
/@jridgewell/set-array/1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
dev: false
/@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: false
/@jridgewell/trace-mapping/0.3.17:
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: false
/@types/history/4.7.11:
resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
dev: true
/@types/hoist-non-react-statics/3.3.1:
resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==}
dependencies:
'@types/react': 17.0.53
hoist-non-react-statics: 3.3.2
dev: true
/@types/prop-types/15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
/@types/react-dom/17.0.19:
resolution: {integrity: sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==}
dependencies:
'@types/react': 17.0.53
dev: false
/@types/react-router-dom/5.3.3:
resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
dependencies:
'@types/history': 4.7.11
'@types/react': 17.0.53
'@types/react-router': 5.1.20
dev: true
/@types/react-router/5.1.20:
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
dependencies:
'@types/history': 4.7.11
'@types/react': 17.0.53
dev: true
/@types/react/17.0.53:
resolution: {integrity: sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.3
csstype: 3.1.1
/@types/scheduler/0.16.3:
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
/@types/styled-components/5.1.26:
resolution: {integrity: sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==}
dependencies:
'@types/hoist-non-react-statics': 3.3.1
'@types/react': 17.0.53
csstype: 3.1.1
dev: true
/ansi-styles/3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
dependencies:
color-convert: 1.9.3
dev: false
/babel-plugin-styled-components/2.0.7_styled-components@5.3.9:
resolution: {integrity: sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==}
peerDependencies:
styled-components: '>= 2'
dependencies:
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-module-imports': 7.18.6
babel-plugin-syntax-jsx: 6.18.0
lodash: 4.17.21
picomatch: 2.3.1
styled-components: 5.3.9_fane7jikarojcev26y27hpbhu4
dev: false
/babel-plugin-syntax-jsx/6.18.0:
resolution: {integrity: sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==}
dev: false
/camelize/1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
dev: false
/chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
dependencies:
ansi-styles: 3.2.1
escape-string-regexp: 1.0.5
supports-color: 5.5.0
dev: false
/color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
dev: false
/color-name/1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
dev: false
/cross-fetch/3.1.5:
resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
dependencies:
node-fetch: 2.6.7
transitivePeerDependencies:
- encoding
dev: false
/css-color-keywords/1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
dev: false
/css-to-react-native/3.2.0:
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
dependencies:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
dev: false
/csstype/3.1.1:
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
/debug/4.3.4_supports-color@5.5.0:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
supports-color: 5.5.0
dev: false
/esbuild/0.12.29:
resolution: {integrity: sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g==}
hasBin: true
requiresBuild: true
dev: false
/escape-string-regexp/1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
dev: false
/globals/11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
dev: false
/has-flag/3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
dev: false
/history/4.10.1:
resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
dependencies:
'@babel/runtime': 7.21.0
loose-envify: 1.4.0
resolve-pathname: 3.0.0
tiny-invariant: 1.3.1
tiny-warning: 1.0.3
value-equal: 1.0.1
dev: false
/hoist-non-react-statics/3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
react-is: 16.13.1
/isarray/0.0.1:
resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
dev: false
/js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: false
/jsesc/2.5.2:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'}
hasBin: true
dev: false
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
/loose-envify/1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
dependencies:
js-tokens: 4.0.0
dev: false
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: false
/node-fetch/2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/object-assign/4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
dev: false
/path-to-regexp/1.8.0:
resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
dependencies:
isarray: 0.0.1
dev: false
/picomatch/2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: false
/postcss-value-parser/4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: false
/prettier/2.7.1:
resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: true
/prop-types/15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
dev: false
/react-dom/17.0.2_react@17.0.2:
resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==}
peerDependencies:
react: 17.0.2
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react: 17.0.2
scheduler: 0.20.2
dev: false
/react-is/16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
/react-is/17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
dev: false
/react-router-dom/5.3.4_react@17.0.2:
resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==}
peerDependencies:
react: '>=15'
dependencies:
'@babel/runtime': 7.21.0
history: 4.10.1
loose-envify: 1.4.0
prop-types: 15.8.1
react: 17.0.2
react-router: 5.3.4_react@17.0.2
tiny-invariant: 1.3.1
tiny-warning: 1.0.3
dev: false
/react-router/5.3.4_react@17.0.2:
resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==}
peerDependencies:
react: '>=15'
dependencies:
'@babel/runtime': 7.21.0
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
path-to-regexp: 1.8.0
prop-types: 15.8.1
react: 17.0.2
react-is: 16.13.1
tiny-invariant: 1.3.1
tiny-warning: 1.0.3
dev: false
/react/17.0.2:
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
dev: false
/regenerator-runtime/0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: false
/resolve-pathname/3.0.0:
resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
dev: false
/scheduler/0.20.2:
resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
dev: false
/shallowequal/1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
dev: false
/styled-components/5.3.9_fane7jikarojcev26y27hpbhu4:
resolution: {integrity: sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==}
engines: {node: '>=10'}
peerDependencies:
react: '>= 16.8.0'
react-dom: '>= 16.8.0'
react-is: '>= 16.8.0'
dependencies:
'@babel/helper-module-imports': 7.18.6
'@babel/traverse': 7.21.3_supports-color@5.5.0
'@emotion/is-prop-valid': 1.2.0
'@emotion/stylis': 0.8.5
'@emotion/unitless': 0.7.5
babel-plugin-styled-components: 2.0.7_styled-components@5.3.9
css-to-react-native: 3.2.0
hoist-non-react-statics: 3.3.2
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
react-is: 17.0.2
shallowequal: 1.1.0
supports-color: 5.5.0
dev: false
/supports-color/5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
dependencies:
has-flag: 3.0.0
dev: false
/tiny-invariant/1.3.1:
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
dev: false
/tiny-warning/1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/to-fast-properties/2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
dev: false
/tr46/0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/typescript/4.9.5:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: false
/value-equal/1.0.1:
resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==}
dev: false
/webidl-conversions/3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/whatwg-url/5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false

View File

@ -2,10 +2,37 @@ import React from 'react';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { AuthorizerProvider } from '@authorizerdev/authorizer-react'; import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
import Root from './Root'; import Root from './Root';
import { createRandomString } from './utils/common';
declare global {
interface Window {
__authorizer__: any;
}
}
export default function App() { export default function App() {
// @ts-ignore const searchParams = new URLSearchParams(window.location.search);
const globalState: Record<string, string> = window['__authorizer__']; const state = searchParams.get('state') || createRandomString();
const scope = searchParams.get('scope')
? searchParams.get('scope')?.toString().split(' ')
: `openid profile email`;
const urlProps: Record<string, any> = {
state,
scope,
};
const redirectURL =
searchParams.get('redirect_uri') || searchParams.get('redirectURL');
if (redirectURL) {
urlProps.redirectURL = redirectURL;
} else {
urlProps.redirectURL = window.location.href;
}
const globalState: Record<string, string> = {
...window['__authorizer__'],
...urlProps,
};
return ( return (
<div <div
style={{ style={{
@ -26,19 +53,11 @@ export default function App() {
<img <img
src={`${globalState.organizationLogo}`} src={`${globalState.organizationLogo}`}
alt="logo" alt="logo"
style={{ height: 60, width: 60, objectFit: 'cover' }} style={{ height: 60, objectFit: 'cover' }}
/> />
<h1>{globalState.organizationName}</h1> <h1>{globalState.organizationName}</h1>
</div> </div>
<div <div className="container">
style={{
width: 400,
margin: `10px auto`,
border: `1px solid #D1D5DB`,
padding: `25px 20px`,
borderRadius: 5,
}}
>
<BrowserRouter> <BrowserRouter>
<AuthorizerProvider <AuthorizerProvider
config={{ config={{
@ -46,7 +65,7 @@ export default function App() {
redirectURL: globalState.redirectURL, redirectURL: globalState.redirectURL,
}} }}
> >
<Root /> <Root globalState={globalState} />
</AuthorizerProvider> </AuthorizerProvider>
</BrowserRouter> </BrowserRouter>
</div> </div>

View File

@ -1,23 +1,94 @@
import React, { useEffect, lazy, Suspense } from 'react'; import React, { useEffect, lazy, Suspense } from 'react';
import { Switch, Route } from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
import { useAuthorizer } from '@authorizerdev/authorizer-react'; import { useAuthorizer } from '@authorizerdev/authorizer-react';
import styled, { ThemeProvider } from 'styled-components';
import SetupPassword from './pages/setup-password';
import { hasWindow, createRandomString } from './utils/common';
import { theme } from './theme';
const ResetPassword = lazy(() => import('./pages/rest-password')); const ResetPassword = lazy(() => import('./pages/rest-password'));
const Login = lazy(() => import('./pages/login')); const Login = lazy(() => import('./pages/login'));
const Dashboard = lazy(() => import('./pages/dashboard')); const Dashboard = lazy(() => import('./pages/dashboard'));
const SignUp = lazy(() => import('./pages/signup'));
export default function Root() { const Wrapper = styled.div`
font-family: ${(props) => props.theme.fonts.fontStack};
color: ${(props) => props.theme.colors.textColor};
font-size: ${(props) => props.theme.fonts.mediumText};
box-sizing: border-box;
*,
*:before,
*:after {
box-sizing: inherit;
}
`;
export default function Root({
globalState,
}: {
globalState: Record<string, string>;
}) {
const { token, loading, config } = useAuthorizer(); const { token, loading, config } = useAuthorizer();
const searchParams = new URLSearchParams(
hasWindow() ? window.location.search : ``,
);
const state = searchParams.get('state') || createRandomString();
const scope = searchParams.get('scope')
? searchParams.get('scope')?.toString().split(' ')
: ['openid', 'profile', 'email'];
const code = searchParams.get('code') || '';
const nonce = searchParams.get('nonce') || '';
const urlProps: Record<string, any> = {
state,
scope,
};
const redirectURL =
searchParams.get('redirect_uri') || searchParams.get('redirectURL');
if (redirectURL) {
urlProps.redirectURL = redirectURL;
} else {
urlProps.redirectURL = hasWindow() ? window.location.origin : redirectURL;
}
urlProps.redirect_uri = urlProps.redirectURL;
useEffect(() => { useEffect(() => {
if (token) { if (token) {
const url = new URL(config.redirectURL || '/app'); let redirectURL = config.redirectURL || '/app';
// let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
// Note: If OIDC breaks in the future, use the above params
let params = `state=${globalState.state}`;
if (code !== '') {
params += `&code=${code}`;
}
if (nonce !== '') {
params += `&nonce=${nonce}`;
}
if (token.refresh_token) {
params += `&refresh_token=${token.refresh_token}`;
}
const url = new URL(redirectURL);
if (redirectURL.includes('?')) {
redirectURL = `${redirectURL}&${params}`;
} else {
redirectURL = `${redirectURL}?${params}`;
}
if (url.origin !== window.location.origin) { if (url.origin !== window.location.origin) {
window.location.href = config.redirectURL || '/app'; sessionStorage.removeItem('authorizer_state');
window.location.replace(redirectURL);
} }
} }
return () => {}; return () => {};
}, [token]); }, [token, config]);
if (loading) { if (loading) {
return <h1>Loading...</h1>; return <h1>Loading...</h1>;
@ -37,14 +108,24 @@ export default function Root() {
return ( return (
<Suspense fallback={<></>}> <Suspense fallback={<></>}>
<Switch> <ThemeProvider theme={theme}>
<Route path="/app" exact> <Wrapper>
<Login /> <Switch>
</Route> <Route path="/app" exact>
<Route path="/app/reset-password"> <Login urlProps={urlProps} />
<ResetPassword /> </Route>
</Route> <Route path="/app/signup">
</Switch> <SignUp urlProps={urlProps} />
</Route>
<Route path="/app/reset-password">
<ResetPassword />
</Route>
<Route path="/app/setup-password">
<SetupPassword />
</Route>
</Switch>
</Wrapper>
</ThemeProvider>
</Suspense> </Suspense>
); );
} }

View File

@ -1,5 +1,5 @@
body { body {
margin: 0; margin: 10;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif; sans-serif;
@ -14,3 +14,17 @@ body {
*:after { *:after {
box-sizing: inherit; box-sizing: inherit;
} }
.container {
box-sizing: content-box;
border: 1px solid #d1d5db;
padding: 25px 20px;
border-radius: 5px;
}
@media only screen and (min-width: 768px) {
.container {
width: 400px;
margin: 0 auto;
}
}

View File

@ -1,10 +1,98 @@
import React, { Fragment } from 'react'; import React, { Fragment, useState } from 'react';
import { Authorizer } from '@authorizerdev/authorizer-react'; import {
AuthorizerBasicAuthLogin,
AuthorizerForgotPassword,
AuthorizerMagicLinkLogin,
AuthorizerSocialLogin,
useAuthorizer,
} from '@authorizerdev/authorizer-react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
export default function Login() { const enum VIEW_TYPES {
LOGIN = 'login',
FORGOT_PASSWORD = 'forgot-password',
}
const Footer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 15px;
`;
const FooterContent = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px;
`;
export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
const { config } = useAuthorizer();
const [view, setView] = useState<VIEW_TYPES>(VIEW_TYPES.LOGIN);
const isBasicAuth = config.is_basic_authentication_enabled;
return ( return (
<Fragment> <Fragment>
<Authorizer /> {view === VIEW_TYPES.LOGIN && (
<Fragment>
<h1 style={{ textAlign: 'center' }}>Login</h1>
<AuthorizerSocialLogin urlProps={urlProps} />
<br />
{(config.is_basic_authentication_enabled ||
config.is_mobile_basic_authentication_enabled) &&
!config.is_magic_link_login_enabled && (
<AuthorizerBasicAuthLogin urlProps={urlProps} />
)}
{config.is_magic_link_login_enabled && (
<AuthorizerMagicLinkLogin urlProps={urlProps} />
)}
{(config.is_basic_authentication_enabled ||
config.is_mobile_basic_authentication_enabled) &&
!config.is_magic_link_login_enabled && (
<Footer>
<Link
to="#"
onClick={() => setView(VIEW_TYPES.FORGOT_PASSWORD)}
style={{ marginBottom: 10 }}
>
Forgot Password?
</Link>
</Footer>
)}
</Fragment>
)}
{view === VIEW_TYPES.FORGOT_PASSWORD && (
<Fragment>
<h1 style={{ textAlign: 'center' }}>Forgot Password</h1>
<AuthorizerForgotPassword
urlProps={{
...urlProps,
redirect_uri: `${window.location.origin}/app/reset-password`,
}}
onPasswordReset={() => {
setView(VIEW_TYPES.LOGIN);
}}
/>
<Footer>
<Link
to="#"
onClick={() => setView(VIEW_TYPES.LOGIN)}
style={{ marginBottom: 10 }}
>
Back
</Link>
</Footer>
</Fragment>
)}
{config.is_basic_authentication_enabled &&
!config.is_magic_link_login_enabled &&
config.is_sign_up_enabled && (
<FooterContent>
Don't have an account? &nbsp; <Link to="/app/signup"> Sign Up</Link>
</FooterContent>
)}
</Fragment> </Fragment>
); );
} }

View File

@ -0,0 +1,12 @@
import React, { Fragment } from 'react';
import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
export default function SetupPassword() {
return (
<Fragment>
<h1 style={{ textAlign: 'center' }}>Setup new Password</h1>
<br />
<AuthorizerResetPassword />
</Fragment>
);
}

29
app/src/pages/signup.tsx Normal file
View File

@ -0,0 +1,29 @@
import React, { Fragment } from 'react';
import { AuthorizerSignup, AuthorizerSocialLogin } from '@authorizerdev/authorizer-react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
const FooterContent = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
`;
export default function SignUp({
urlProps,
}: {
urlProps: Record<string, any>;
}) {
return (
<Fragment>
<h1 style={{ textAlign: 'center' }}>Sign Up</h1>
<br />
<AuthorizerSocialLogin urlProps={urlProps} />
<AuthorizerSignup urlProps={urlProps} />
<FooterContent>
Already have an account? <Link to="/app"> Login</Link>
</FooterContent>
</Fragment>
);
}

28
app/src/theme.ts Normal file
View File

@ -0,0 +1,28 @@
// colors: https://tailwindcss.com/docs/customizing-colors
export const theme = {
colors: {
primary: '#3B82F6',
primaryDisabled: '#60A5FA',
gray: '#D1D5DB',
danger: '#DC2626',
success: '#10B981',
textColor: '#374151',
},
fonts: {
// typography
fontStack: '-apple-system, system-ui, sans-serif',
// font sizes
largeText: '18px',
mediumText: '14px',
smallText: '12px',
tinyText: '10px',
},
radius: {
card: '5px',
button: '5px',
input: '5px',
},
};

24
app/src/utils/common.ts Normal file
View File

@ -0,0 +1,24 @@
export const getCrypto = () => {
//ie 11.x uses msCrypto
return (window.crypto || (window as any).msCrypto) as Crypto;
};
export const createRandomString = () => {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
let random = '';
const randomValues = Array.from(
getCrypto().getRandomValues(new Uint8Array(43)),
);
randomValues.forEach((v) => (random += charset[v % charset.length]));
return random;
};
export const createQueryParams = (params: any) => {
return Object.keys(params)
.filter((k) => typeof params[k] !== 'undefined')
.map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
.join('&');
};
export const hasWindow = (): boolean => typeof window !== 'undefined';

619
app/yarn.lock Normal file
View File

@ -0,0 +1,619 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@authorizerdev/authorizer-js@^2.0.3":
version "2.0.3"
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-2.0.3.tgz"
integrity sha512-uencwr3Ea8mwfxVKDFf2ITRCRSmzvua+O2voRuiWQORtRQTgZQjkN3M+IEkEj+WP9M1iFIl+NDgzECsp8ptC/A==
dependencies:
cross-fetch "^3.1.5"
"@authorizerdev/authorizer-react@^1.3.2":
version "1.3.2"
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.3.2.tgz"
integrity sha512-3kMAygHBCa8Fc9Oo0lz1k88r+Pd6kx1PSn3NMYLwxQXy2jRt4xWn7iuGn+SDGFs3DzofaN71I61gRwQ+6dO1rw==
dependencies:
"@authorizerdev/authorizer-js" "^2.0.3"
validator "^13.11.0"
"@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
"@babel/highlight" "^7.22.13"
chalk "^2.4.2"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.16.0":
version "7.16.7"
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz"
integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==
dependencies:
"@babel/types" "^7.16.7"
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0":
version "7.16.7"
resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz"
integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==
dependencies:
"@babel/types" "^7.16.7"
"@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/highlight@^7.22.13":
version "7.22.20"
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1":
version "7.14.8"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz"
integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.4.5":
version "7.23.2"
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz"
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.16.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
dependencies:
"@emotion/memoize" "0.7.4"
"@emotion/memoize@0.7.4":
version "0.7.4"
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
"@emotion/stylis@^0.8.4":
version "0.8.5"
resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
"@emotion/unitless@^0.7.4":
version "0.7.5"
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@jridgewell/gen-mapping@^0.3.2":
version "0.3.3"
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz"
integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.1"
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz"
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.20"
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz"
integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@types/history@*":
version "4.7.9"
resolved "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz"
integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
"@types/hoist-non-react-statics@*":
version "3.3.1"
resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/prop-types@*":
version "15.7.4"
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
"@types/react-dom@^17.0.9":
version "17.0.9"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz"
integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==
dependencies:
"@types/react" "*"
"@types/react-router-dom@^5.1.8":
version "5.1.8"
resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz"
integrity sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "5.1.16"
resolved "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz"
integrity sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react@*", "@types/react@^17.0.15":
version "17.0.15"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.15.tgz"
integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz"
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
"@types/styled-components@^5.1.11":
version "5.1.25"
resolved "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz"
integrity sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==
dependencies:
"@types/hoist-non-react-statics" "*"
"@types/react" "*"
csstype "^3.0.2"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
"babel-plugin-styled-components@>= 1.12.0":
version "2.0.2"
resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz"
integrity sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.16.0"
"@babel/helper-module-imports" "^7.16.0"
babel-plugin-syntax-jsx "^6.18.0"
lodash "^4.17.11"
babel-plugin-syntax-jsx@^6.18.0:
version "6.18.0"
resolved "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz"
integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
camelize@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz"
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
cross-fetch@^3.1.5:
version "3.1.8"
resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz"
integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==
dependencies:
node-fetch "^2.6.12"
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz"
integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=
css-to-react-native@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz"
integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
csstype@^3.0.2:
version "3.0.8"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
debug@^4.1.0:
version "4.3.3"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
dependencies:
ms "2.1.2"
esbuild@^0.12.17:
version "0.12.17"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.12.17.tgz"
integrity sha512-GshKJyVYUnlSXIZj/NheC2O0Kblh42CS7P1wJyTbbIHevTG4jYMS9NNw8EOd8dDWD0dzydYHS01MpZoUcQXB4g==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
history@^4.9.0:
version "4.10.1"
resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz"
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
dependencies:
"@babel/runtime" "^7.1.2"
loose-envify "^1.2.0"
resolve-pathname "^3.0.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
value-equal "^1.0.1"
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
lodash@^4.17.11:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
mini-create-react-context@^0.4.0:
version "0.4.1"
resolved "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz"
integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==
dependencies:
"@babel/runtime" "^7.12.1"
tiny-warning "^1.0.3"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
node-fetch@^2.6.12:
version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
postcss-value-parser@^4.0.2:
version "4.2.0"
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
prettier@2.7.1:
version "2.7.1"
resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz"
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
prop-types@^15.0.0, prop-types@^15.6.2:
version "15.7.2"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
react-dom@^17.0.2, "react-dom@>= 16.8.0":
version "17.0.2"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
react-is@^16.6.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.2, "react-is@>= 16.8.0":
version "17.0.2"
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-router-dom@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz"
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.2"
react-router "5.2.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz"
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
hoist-non-react-statics "^3.1.0"
loose-envify "^1.3.1"
mini-create-react-context "^0.4.0"
path-to-regexp "^1.7.0"
prop-types "^15.6.2"
react-is "^16.6.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
"react@^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", react@^17.0.2, "react@>= 16.8.0", react@>=15, react@>=16, react@17.0.2:
version "17.0.2"
resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
resolve-pathname@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz"
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
shallowequal@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
styled-components@^5.3.0, "styled-components@>= 2":
version "5.3.3"
resolved "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz"
integrity sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/traverse" "^7.4.5"
"@emotion/is-prop-valid" "^0.8.8"
"@emotion/stylis" "^0.8.4"
"@emotion/unitless" "^0.7.4"
babel-plugin-styled-components ">= 1.12.0"
css-to-react-native "^3.0.0"
hoist-non-react-statics "^3.0.0"
shallowequal "^1.1.0"
supports-color "^5.5.0"
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
tiny-invariant@^1.0.2:
version "1.1.0"
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
typescript@^4.3.5:
version "4.3.5"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz"
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
validator@^13.11.0:
version "13.11.0"
resolved "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz"
integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==
value-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz"
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"

View File

@ -0,0 +1,6 @@
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"useTabs": true
}

File diff suppressed because it is too large Load Diff

View File

@ -5,13 +5,15 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js", "build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
"start": "NODE_ENV=development node ./esbuild.config.js" "start": "NODE_ENV=development node ./esbuild.config.js",
"format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
}, },
"keywords": [], "keywords": [],
"author": "Lakhan Samani", "author": "Lakhan Samani",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@chakra-ui/react": "^1.7.3", "@chakra-ui/react": "^1.7.3",
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.7.1", "@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0", "@emotion/styled": "^11.6.0",
"@types/react": "^17.0.38", "@types/react": "^17.0.38",
@ -19,14 +21,22 @@
"@types/react-router-dom": "^5.3.2", "@types/react-router-dom": "^5.3.2",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"esbuild": "^0.14.9", "esbuild": "^0.14.9",
"focus-visible": "^5.2.0",
"framer-motion": "^5.5.5", "framer-motion": "^5.5.5",
"graphql": "^16.2.0", "graphql": "^16.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-draft-wysiwyg": "^1.15.0",
"react-dropzone": "^12.0.4",
"react-email-editor": "^1.6.1",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"react-router-dom": "^6.2.1", "react-router-dom": "^6.2.1",
"typescript": "^4.5.4", "typescript": "^4.5.4",
"urql": "^2.0.6" "urql": "^2.0.6"
},
"devDependencies": {
"@types/react-email-editor": "^1.1.7",
"prettier": "2.7.1"
} }
} }

BIN
dashboard/public/roblox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
foo@bar.com,test@authorizer.dev
1 foo bar.com,test authorizer.dev

View File

@ -1,4 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { Fragment } from 'react';
import { ChakraProvider, extendTheme } from '@chakra-ui/react'; import { ChakraProvider, extendTheme } from '@chakra-ui/react';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { createClient, Provider } from 'urql'; import { createClient, Provider } from 'urql';
@ -10,6 +11,9 @@ const queryClient = createClient({
fetchOptions: () => { fetchOptions: () => {
return { return {
credentials: 'include', credentials: 'include',
headers: {
'x-authorizer-url': window.location.origin,
},
}; };
}, },
requestPolicy: 'network-only', requestPolicy: 'network-only',
@ -19,8 +23,8 @@ const theme = extendTheme({
styles: { styles: {
global: { global: {
'html, body, #root': { 'html, body, #root': {
fontFamily: 'Avenir, Helvetica, Arial, sans-serif',
height: '100%', height: '100%',
outline: 'none',
}, },
}, },
}, },
@ -33,14 +37,16 @@ const theme = extendTheme({
export default function App() { export default function App() {
return ( return (
<ChakraProvider theme={theme}> <Fragment>
<Provider value={queryClient}> <ChakraProvider theme={theme}>
<BrowserRouter basename="/dashboard"> <Provider value={queryClient}>
<AuthContextProvider> <BrowserRouter basename="/dashboard">
<AppRoutes /> <AuthContextProvider>
</AuthContextProvider> <AppRoutes />
</BrowserRouter> </AuthContextProvider>
</Provider> </BrowserRouter>
</ChakraProvider> </Provider>
</ChakraProvider>
</Fragment>
); );
} }

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: 'top-right',
});
return;
} else if (res.data?._delete_email_template) {
toast({
title: capitalizeFirstLetter(res.data?._delete_email_template.message),
isClosable: true,
status: 'success',
position: 'top-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

@ -51,7 +51,7 @@ const DeleteUserModal = ({
title: capitalizeFirstLetter(res.error.message), title: capitalizeFirstLetter(res.error.message),
isClosable: true, isClosable: true,
status: 'error', status: 'error',
position: 'bottom-right', position: 'top-right',
}); });
return; return;
@ -60,7 +60,7 @@ const DeleteUserModal = ({
title: capitalizeFirstLetter(res.data?._delete_user.message), title: capitalizeFirstLetter(res.data?._delete_user.message),
isClosable: true, isClosable: true,
status: 'success', status: 'success',
position: 'bottom-right', position: 'top-right',
}); });
} }
onClose(); onClose();

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: 'top-right',
});
return;
} else if (res.data?._delete_webhook) {
toast({
title: capitalizeFirstLetter(res.data?._delete_webhook.message),
isClosable: true,
status: 'success',
position: 'top-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,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import { import {
Button, Button,
Center, Center,
@ -20,13 +20,14 @@ import { useClient } from 'urql';
import { FaSave } from 'react-icons/fa'; import { FaSave } from 'react-icons/fa';
import InputField from './InputField'; import InputField from './InputField';
import { import {
ArrayInputType,
DateInputType, DateInputType,
MultiSelectInputType,
SelectInputType, SelectInputType,
TextInputType, TextInputType,
} from '../constants'; } from '../constants';
import { getObjectDiff } from '../utils'; import { getObjectDiff } from '../utils';
import { UpdateUser } from '../graphql/mutation'; import { UpdateUser } from '../graphql/mutation';
import { GetAvailableRolesQuery } from '../graphql/queries';
const GenderTypes = { const GenderTypes = {
Undisclosed: null, Undisclosed: null,
@ -57,8 +58,9 @@ const EditUserModal = ({
}) => { }) => {
const client = useClient(); const client = useClient();
const toast = useToast(); const toast = useToast();
const [availableRoles, setAvailableRoles] = useState<string[]>([]);
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const [userData, setUserData] = React.useState<userDataTypes>({ const [userData, setUserData] = useState<userDataTypes>({
id: '', id: '',
email: '', email: '',
given_name: '', given_name: '',
@ -73,7 +75,17 @@ const EditUserModal = ({
}); });
React.useEffect(() => { React.useEffect(() => {
setUserData(user); setUserData(user);
fetchAvailableRoles();
}, []); }, []);
const fetchAvailableRoles = async () => {
const res = await client.query(GetAvailableRolesQuery).toPromise();
if (res.data?._env?.ROLES && res.data?._env?.PROTECTED_ROLES) {
setAvailableRoles([
...res.data._env.ROLES,
...res.data._env.PROTECTED_ROLES,
]);
}
};
const saveHandler = async () => { const saveHandler = async () => {
const diff = getObjectDiff(user, userData); const diff = getObjectDiff(user, userData);
const updatedUserData = diff.reduce( const updatedUserData = diff.reduce(
@ -82,7 +94,7 @@ const EditUserModal = ({
// @ts-ignore // @ts-ignore
[property]: userData[property], [property]: userData[property],
}), }),
{} {},
); );
const res = await client const res = await client
.mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } }) .mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } })
@ -92,14 +104,14 @@ const EditUserModal = ({
title: 'User data update failed', title: 'User data update failed',
isClosable: true, isClosable: true,
status: 'error', status: 'error',
position: 'bottom-right', position: 'top-right',
}); });
} else if (res.data?._update_user?.id) { } else if (res.data?._update_user?.id) {
toast({ toast({
title: 'User data update successful', title: 'User data update successful',
isClosable: true, isClosable: true,
status: 'success', status: 'success',
position: 'bottom-right', position: 'top-right',
}); });
} }
onClose(); onClose();
@ -221,7 +233,8 @@ const EditUserModal = ({
<InputField <InputField
variables={userData} variables={userData}
setVariables={setUserData} setVariables={setUserData}
inputType={ArrayInputType.USER_ROLES} availableRoles={availableRoles}
inputType={MultiSelectInputType.USER_ROLES}
/> />
</Center> </Center>
</Flex> </Flex>

View File

@ -0,0 +1,65 @@
import React from 'react';
import { Flex, Stack, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../../components/InputField';
import { TextInputType, TextAreaInputType } from '../../constants';
const AccessToken = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Access Token
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '50%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Access Token Expiry Time:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ACCESS_TOKEN_EXPIRY_TIME}
placeholder="0h15m0s"
/>
</Flex>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '60%'}
justifyContent="start"
direction="column"
>
<Text fontSize="sm">Custom Scripts:</Text>
<Text fontSize="xs" color="blackAlpha.500">
(Used to add custom fields in ID token)
</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
placeholder="Add script here"
minH="25vh"
/>
</Flex>
</Flex>
</Stack>
</div>
);
};
export default AccessToken;

View File

@ -0,0 +1,89 @@
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
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. 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;

View File

@ -0,0 +1,35 @@
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../../components/InputField';
import { ArrayInputType } from '../../constants';
const DomainWhiteListing = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Domain White Listing
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Allowed Origins:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.ALLOWED_ORIGINS}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default DomainWhiteListing;

View File

@ -0,0 +1,150 @@
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../../components/InputField';
import { TextInputType, HiddenInputType } from '../../constants';
const EmailConfigurations = ({
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
}: any) => {
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Email Configurations
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Host:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_HOST}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Port:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_PORT}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">SMTP Local Name:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_LOCAL_NAME}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">SMTP Username:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_USERNAME}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">SMTP Password:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.SMTP_PASSWORD}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">From Email:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SENDER_EMAIL}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Sender Name:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SENDER_NAME}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default EmailConfigurations;

View File

@ -0,0 +1,229 @@
import React from 'react';
import { Divider, Flex, Stack, Text } from '@chakra-ui/react';
import InputField from '../InputField';
import { SwitchInputType } from '../../constants';
const Features = ({ variables, setVariables }: any) => {
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Features
</Text>
<Stack spacing={6}>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Login Page:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_LOGIN_PAGE}
hasReversedValue
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Email Verification:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_EMAIL_VERIFICATION}
hasReversedValue
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Magic Login Link:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_MAGIC_LINK_LOGIN}
hasReversedValue
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Email Basic Authentication:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_BASIC_AUTHENTICATION}
hasReversedValue
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Mobile Basic Authentication:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_MOBILE_BASIC_AUTHENTICATION}
hasReversedValue
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Sign Up:</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_SIGN_UP}
hasReversedValue
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Strong Password:</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_STRONG_PASSWORD}
hasReversedValue
/>
</Flex>
</Flex>
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">Multi Factor Authentication (MFA):</Text>
<Text fontSize="x-small">
Note: Enabling this will ignore Enforcing MFA shown below and will
also ignore the user MFA setting.
</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_MULTI_FACTOR_AUTHENTICATION}
hasReversedValue
/>
</Flex>
</Flex>
{!variables.DISABLE_MULTI_FACTOR_AUTHENTICATION && (
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">Time Based OTP (TOTP):</Text>
<Text fontSize="x-small">Note: to enable totp mfa</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_TOTP_LOGIN}
hasReversedValue
/>
</Flex>
</Flex>
)}
{!variables.DISABLE_MULTI_FACTOR_AUTHENTICATION && (
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">EMAIL OTP:</Text>
<Text fontSize="x-small">Note: to enable email otp mfa</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_MAIL_OTP_LOGIN}
hasReversedValue
/>
</Flex>
</Flex>
)}
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">
Enforce Multi Factor Authentication (MFA):
</Text>
<Text fontSize="x-small">
Note: If you disable enforcing after it was enabled, it will still
keep MFA enabled for older users.
</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.ENFORCE_MULTI_FACTOR_AUTHENTICATION}
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Playground:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_PLAYGROUND}
hasReversedValue
/>
</Flex>
</Flex>
</Stack>
<Divider paddingY={5} />
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
Cookie Security Features
</Text>
<Stack spacing={6}>
<Flex>
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">Use Secure App Cookie:</Text>
<Text fontSize="x-small">
Note: If you set this to insecure, it will set{' '}
<code>sameSite</code> property of cookie to <code>lax</code> mode
</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.APP_COOKIE_SECURE}
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">Use Secure Admin Cookie:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.ADMIN_COOKIE_SECURE}
/>
</Flex>
</Flex>
</Stack>
</div>
);
};
export default Features;

View File

@ -0,0 +1,200 @@
import React from 'react';
import {
Flex,
Stack,
Center,
Text,
useMediaQuery,
Button,
useToast,
} from '@chakra-ui/react';
import {
HiddenInputType,
TextInputType,
TextAreaInputType,
} from '../../constants';
import GenerateKeysModal from '../GenerateKeysModal';
import InputField from '../InputField';
import { copyTextToClipboard } from '../../utils';
const JSTConfigurations = ({
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
SelectInputType,
getData,
HMACEncryptionType,
RSAEncryptionType,
ECDSAEncryptionType,
}: any) => {
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
const toast = useToast();
const copyJSON = async () => {
try {
await copyTextToClipboard(
JSON.stringify({
type: variables.JWT_TYPE,
key: variables.JWT_PUBLIC_KEY || variables.JWT_SECRET,
}),
);
toast({
title: `JWT config copied successfully`,
isClosable: true,
status: 'success',
position: 'top-right',
});
} catch (err) {
console.error({
message: `Failed to copy JWT config`,
error: err,
});
toast({
title: `Failed to copy JWT config`,
isClosable: true,
status: 'error',
position: 'top-right',
});
}
};
return (
<div>
<Flex
borderRadius={5}
width="100%"
justifyContent="space-between"
alignItems="center"
paddingTop="2%"
>
<Text
fontSize={isNotSmallerScreen ? 'md' : 'sm'}
fontWeight="bold"
mb={5}
>
JWT (JSON Web Tokens) Configurations
</Text>
<Flex mb={7}>
<Button
colorScheme="blue"
h="1.75rem"
size="sm"
variant="ghost"
onClick={copyJSON}
>
Copy As JSON Config
</Button>
<GenerateKeysModal jwtType={variables.JWT_TYPE} getData={getData} />
</Flex>
</Flex>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Type:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={SelectInputType}
value={SelectInputType}
options={{
...HMACEncryptionType,
...RSAEncryptionType,
...ECDSAEncryptionType,
}}
/>
</Flex>
</Flex>
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Secret</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.JWT_SECRET}
/>
</Center>
</Flex>
) : (
<>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Public Key</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
placeholder="Add public key here"
minH="25vh"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Private Key</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
placeholder="Add private key here"
minH="25vh"
/>
</Center>
</Flex>
</>
)}
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm" orientation="vertical">
JWT Role Claim:
</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.JWT_ROLE_CLAIM}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default JSTConfigurations;

View File

@ -0,0 +1,525 @@
import React from 'react';
import InputField from '../InputField';
import {
Flex,
Stack,
Center,
Text,
Box,
Divider,
useMediaQuery,
} from '@chakra-ui/react';
import {
FaGoogle,
FaGithub,
FaFacebookF,
FaLinkedin,
FaApple,
FaTwitter,
FaMicrosoft,
FaTwitch,
FaDiscord,
} from 'react-icons/fa';
import {
TextInputType,
HiddenInputType,
ResponseModes,
ResponseTypes,
SelectInputType,
} from '../../constants';
const OAuthConfig = ({
envVariables,
setVariables,
fieldVisibility,
setFieldVisibility,
}: any) => {
const [isNotSmallerScreen] = useMediaQuery('(min-width:667px)');
return (
<div>
<Box>
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={6}>
Authorizer Config
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Client ID</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
variables={envVariables}
setVariables={() => {}}
inputType={TextInputType.CLIENT_ID}
placeholder="Client ID"
readOnly={true}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Client Secret</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.CLIENT_SECRET}
placeholder="Client Secret"
readOnly={true}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Default Response Type:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={SelectInputType.DEFAULT_AUTHORIZE_RESPONSE_TYPE}
value={SelectInputType}
options={ResponseTypes}
/>
</Flex>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Default Response Mode:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={SelectInputType.DEFAULT_AUTHORIZE_RESPONSE_MODE}
value={SelectInputType}
options={ResponseModes}
/>
</Flex>
</Flex>
</Stack>
<Divider mt={5} mb={2} color="blackAlpha.700" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={4}>
Social Media Logins
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #ff3e30"
borderRadius="5px"
>
<FaGoogle style={{ color: '#ff3e30' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.GOOGLE_CLIENT_ID}
placeholder="Google 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.GOOGLE_CLIENT_SECRET}
placeholder="Google Client Secret"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #171515"
borderRadius="5px"
>
<FaGithub style={{ color: '#171515' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.GITHUB_CLIENT_ID}
placeholder="Github 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.GITHUB_CLIENT_SECRET}
placeholder="Github 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"
>
<FaFacebookF 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.FACEBOOK_CLIENT_ID}
placeholder="Facebook 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.FACEBOOK_CLIENT_SECRET}
placeholder="Facebook 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"
>
<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>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaDiscord style={{ color: '#7289da' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.DISCORD_CLIENT_ID}
placeholder="Discord 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.DISCORD_CLIENT_SECRET}
placeholder="Discord Client Secret"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaTwitter />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.TWITTER_CLIENT_ID}
placeholder="Twitter Client ID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.TWITTER_CLIENT_SECRET}
placeholder="Twitter Client Secret"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaMicrosoft />
</Center>
<Center
w={isNotSmallerScreen ? '35%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID}
placeholder="Microsoft Active Directory TenantID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '35%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.MICROSOFT_CLIENT_ID}
placeholder="Microsoft Client ID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.MICROSOFT_CLIENT_SECRET}
placeholder="Microsoft Client Secret"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaTwitch />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.TWITCH_CLIENT_ID}
placeholder="Twitch 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.TWITCH_CLIENT_SECRET}
placeholder="Twitch 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"
>
<img
src="https://authorizer.dev/_next/image?url=%2Fimages%2Froblox.png&w=25&q=25"
alt="Roblox"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.ROBLOX_CLIENT_ID}
placeholder="Roblox 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.ROBLOX_CLIENT_SECRET}
placeholder="Roblox Client Secret"
/>
</Center>
</Flex>
</Stack>
</Box>
</div>
);
};
export default OAuthConfig;

View File

@ -0,0 +1,60 @@
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../InputField';
import { TextInputType } from '../../constants';
const OrganizationInfo = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Organization Information
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Organization Name:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ORGANIZATION_NAME}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Organization Logo:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ORGANIZATION_LOGO}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default OrganizationInfo;

View File

@ -0,0 +1,68 @@
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>
);
};
export default Roles;

View File

@ -0,0 +1,138 @@
import React from 'react';
import {
Flex,
Stack,
Center,
Text,
Input,
InputGroup,
InputRightElement,
useMediaQuery,
} from '@chakra-ui/react';
import { FaRegEyeSlash, FaRegEye } from 'react-icons/fa';
import InputField from '../InputField';
import { HiddenInputType } from '../../constants';
const SecurityAdminSecret = ({
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
validateAdminSecretHandler,
adminSecret,
}: any) => {
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Security (Admin Secret)
</Text>
<Stack
spacing={6}
padding="0 5%"
marginTop="3%"
border="1px solid #ff7875"
borderRadius="5px"
>
<Flex
marginTop={isNotSmallerScreen ? '3%' : '5%'}
direction={isNotSmallerScreen ? 'row' : 'column'}
>
<Flex
mt={3}
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Old Admin Secret:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputGroup size="sm">
<Input
borderRadius={5}
size="sm"
placeholder="Enter Old Admin Secret"
value={adminSecret.value as string}
onChange={(event: any) => validateAdminSecretHandler(event)}
type={
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
? 'password'
: 'text'
}
/>
<InputRightElement
right="5px"
children={
<Flex>
{fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET] ? (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: false,
})
}
>
<FaRegEyeSlash color="#bfbfbf" />
</Center>
) : (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: true,
})
}
>
<FaRegEye color="#bfbfbf" />
</Center>
)}
</Flex>
}
/>
</InputGroup>
</Center>
</Flex>
<Flex
paddingBottom="3%"
direction={isNotSmallerScreen ? 'row' : 'column'}
>
<Flex
w={isNotSmallerScreen ? '30%' : '50%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">New Admin Secret:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
mb={3}
variables={variables}
setVariables={setVariables}
inputType={HiddenInputType.ADMIN_SECRET}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
isDisabled={adminSecret.disableInputField}
placeholder="Enter New Admin Secret"
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default SecurityAdminSecret;

View File

@ -0,0 +1,42 @@
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>
<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;

View File

@ -0,0 +1,247 @@
import React from 'react';
import {
Button,
Center,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
Text,
useToast,
Input,
Spinner,
} from '@chakra-ui/react';
import { useClient } from 'urql';
import { FaSave } from 'react-icons/fa';
import {
ECDSAEncryptionType,
HMACEncryptionType,
RSAEncryptionType,
SelectInputType,
TextAreaInputType,
} from '../constants';
import InputField from './InputField';
import { GenerateKeys, UpdateEnvVariables } from '../graphql/mutation';
interface propTypes {
jwtType: string;
getData: Function;
}
interface stateVarTypes {
JWT_TYPE: string;
JWT_SECRET: string;
JWT_PRIVATE_KEY: string;
JWT_PUBLIC_KEY: string;
}
const initState: stateVarTypes = {
JWT_TYPE: '',
JWT_SECRET: '',
JWT_PRIVATE_KEY: '',
JWT_PUBLIC_KEY: '',
};
const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
const client = useClient();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const [stateVariables, setStateVariables] = React.useState<stateVarTypes>({
...initState,
});
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
if (isOpen) {
setStateVariables({ ...initState, JWT_TYPE: jwtType });
}
}, [isOpen]);
const fetchKeys = async () => {
setIsLoading(true);
try {
const res = await client
.mutation(GenerateKeys, { params: { type: stateVariables.JWT_TYPE } })
.toPromise();
if (res?.error) {
toast({
title: 'Error occurred generating jwt keys',
isClosable: true,
status: 'error',
position: 'top-right',
});
closeHandler();
} else {
setStateVariables({
...stateVariables,
JWT_SECRET: res?.data?._generate_jwt_keys?.secret || '',
JWT_PRIVATE_KEY: res?.data?._generate_jwt_keys?.private_key || '',
JWT_PUBLIC_KEY: res?.data?._generate_jwt_keys?.public_key || '',
});
}
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
React.useEffect(() => {
if (isOpen && stateVariables.JWT_TYPE) {
fetchKeys();
}
}, [stateVariables.JWT_TYPE]);
const saveHandler = async () => {
const res = await client
.mutation(UpdateEnvVariables, { params: { ...stateVariables } })
.toPromise();
if (res.error) {
toast({
title: 'Error occurred setting jwt keys',
isClosable: true,
status: 'error',
position: 'top-right',
});
return;
}
toast({
title: 'JWT keys updated successfully',
isClosable: true,
status: 'success',
position: 'top-right',
});
closeHandler();
};
const closeHandler = () => {
setStateVariables({ ...initState });
getData();
onClose();
};
return (
<>
<Button
colorScheme="blue"
h="1.75rem"
size="sm"
variant="ghost"
onClick={onOpen}
>
Generate new keys
</Button>
<Modal isOpen={isOpen} onClose={closeHandler}>
<ModalOverlay />
<ModalContent>
<ModalHeader>New JWT keys</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Type:</Text>
</Flex>
<InputField
variables={stateVariables}
setVariables={setStateVariables}
inputType={SelectInputType.JWT_TYPE}
value={SelectInputType.JWT_TYPE}
options={{
...HMACEncryptionType,
...RSAEncryptionType,
...ECDSAEncryptionType,
}}
/>
</Flex>
{isLoading ? (
<Center minH="25vh">
<Spinner />
</Center>
) : (
<>
{Object.values(HMACEncryptionType).includes(
stateVariables.JWT_TYPE,
) ? (
<Flex marginTop="8">
<Flex w="23%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Secret</Text>
</Flex>
<Center w="77%">
<Input
size="sm"
value={stateVariables.JWT_SECRET}
onChange={(event: any) =>
setStateVariables({
...stateVariables,
JWT_SECRET: event.target.value,
})
}
readOnly
/>
</Center>
</Flex>
) : (
<>
<Flex marginTop="8">
<Flex w="23%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Public Key</Text>
</Flex>
<Center w="77%">
<InputField
variables={stateVariables}
setVariables={setStateVariables}
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
placeholder="Add public key here"
minH="25vh"
readOnly
/>
</Center>
</Flex>
<Flex marginTop="8">
<Flex w="23%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Private Key</Text>
</Flex>
<Center w="77%">
<InputField
variables={stateVariables}
setVariables={setStateVariables}
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
placeholder="Add private key here"
minH="25vh"
readOnly
/>
</Center>
</Flex>
</>
)}
</>
)}
</ModalBody>
<ModalFooter>
<Button
leftIcon={<FaSave />}
colorScheme="blue"
variant="solid"
onClick={saveHandler}
isDisabled={isLoading}
>
<Center h="100%" pt="5%">
Apply
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default GenerateKeysModal;

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import { import {
Box, Box,
Flex, Flex,
@ -12,7 +12,13 @@ import {
Select, Select,
Textarea, Textarea,
Switch, Switch,
Code, Text,
MenuButton,
MenuList,
MenuItemOption,
MenuOptionGroup,
Button,
Menu,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import {
FaRegClone, FaRegClone,
@ -20,6 +26,7 @@ import {
FaRegEyeSlash, FaRegEyeSlash,
FaPlus, FaPlus,
FaTimes, FaTimes,
FaAngleDown,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { import {
ArrayInputOperations, ArrayInputOperations,
@ -30,6 +37,7 @@ import {
TextAreaInputType, TextAreaInputType,
SwitchInputType, SwitchInputType,
DateInputType, DateInputType,
MultiSelectInputType,
} from '../constants'; } from '../constants';
import { copyTextToClipboard } from '../utils'; import { copyTextToClipboard } from '../utils';
@ -39,13 +47,18 @@ const InputField = ({
setVariables, setVariables,
fieldVisibility, fieldVisibility,
setFieldVisibility, setFieldVisibility,
availableRoles,
// This prop is added to improve the user experience for the boolean ENV variable having `DISABLE_` prefix, as those values need to be considered in inverted form.
hasReversedValue,
...downshiftProps ...downshiftProps
}: any) => { }: any) => {
const props = { const props = {
size: 'sm', size: 'sm',
...downshiftProps, ...downshiftProps,
}; };
const [inputFieldVisibility, setInputFieldVisibility] = React.useState< const [availableUserRoles, setAvailableUserRoles] =
useState<string[]>(availableRoles);
const [inputFieldVisibility, setInputFieldVisibility] = useState<
Record<string, boolean> Record<string, boolean>
>({ >({
ROLES: false, ROLES: false,
@ -54,7 +67,7 @@ const InputField = ({
ALLOWED_ORIGINS: false, ALLOWED_ORIGINS: false,
roles: false, roles: false,
}); });
const [inputData, setInputData] = React.useState<Record<string, string>>({ const [inputData, setInputData] = useState<Record<string, string>>({
ROLES: '', ROLES: '',
DEFAULT_ROLES: '', DEFAULT_ROLES: '',
PROTECTED_ROLES: '', PROTECTED_ROLES: '',
@ -64,7 +77,7 @@ const InputField = ({
const updateInputHandler = ( const updateInputHandler = (
type: string, type: string,
operation: any, operation: any,
role: string = '' role: string = '',
) => { ) => {
if (operation === ArrayInputOperations.APPEND) { if (operation === ArrayInputOperations.APPEND) {
if (inputData[type] !== '') { if (inputData[type] !== '') {
@ -78,7 +91,7 @@ const InputField = ({
} }
if (operation === ArrayInputOperations.REMOVE) { if (operation === ArrayInputOperations.REMOVE) {
let updatedEnvVars = variables[type].filter( let updatedEnvVars = variables[type].filter(
(item: string) => item !== role (item: string) => item !== role,
); );
setVariables({ setVariables({
...variables, ...variables,
@ -95,7 +108,7 @@ const InputField = ({
onChange={( onChange={(
event: Event & { event: Event & {
target: HTMLInputElement; target: HTMLInputElement;
} },
) => ) =>
setVariables({ setVariables({
...variables, ...variables,
@ -116,11 +129,11 @@ const InputField = ({
<InputGroup size="sm"> <InputGroup size="sm">
<Input <Input
{...props} {...props}
value={variables[inputType]} value={variables[inputType] || ''}
onChange={( onChange={(
event: Event & { event: Event & {
target: HTMLInputElement; target: HTMLInputElement;
} },
) => ) =>
setVariables({ setVariables({
...variables, ...variables,
@ -181,8 +194,9 @@ const InputField = ({
<Flex <Flex
border="1px solid #e2e8f0" border="1px solid #e2e8f0"
w="100%" w="100%"
borderRadius={5}
paddingTop="0.5%" paddingTop="0.5%"
overflowX="scroll" overflowX={variables[inputType].length > 3 ? 'scroll' : 'hidden'}
overflowY="hidden" overflowY="hidden"
justifyContent="start" justifyContent="start"
alignItems="center" alignItems="center"
@ -206,7 +220,7 @@ const InputField = ({
updateInputHandler( updateInputHandler(
inputType, inputType,
ArrayInputOperations.REMOVE, ArrayInputOperations.REMOVE,
role role,
) )
} }
/> />
@ -220,7 +234,7 @@ const InputField = ({
size="xs" size="xs"
minW="150px" minW="150px"
placeholder="add a new value" placeholder="add a new value"
value={inputData[inputType]} value={inputData[inputType] || ''}
onChange={(e: any) => { onChange={(e: any) => {
setInputData({ ...inputData, [inputType]: e.target.value }); setInputData({ ...inputData, [inputType]: e.target.value });
}} }}
@ -259,17 +273,6 @@ const InputField = ({
); );
} }
if (Object.values(SelectInputType).includes(inputType)) { if (Object.values(SelectInputType).includes(inputType)) {
if (inputType === SelectInputType.JWT_TYPE) {
return (
<Select size="sm" {...props}>
{[variables[inputType]].map((value: string) => (
<option value="value" key={value}>
{value}
</option>
))}
</Select>
);
}
const { options, ...rest } = props; const { options, ...rest } = props;
return ( return (
<Select <Select
@ -288,25 +291,118 @@ const InputField = ({
</Select> </Select>
); );
} }
if (Object.values(MultiSelectInputType).includes(inputType)) {
return (
<Flex w="100%" style={{ position: 'relative' }}>
<Flex
border="1px solid #e2e8f0"
w="100%"
borderRadius="var(--chakra-radii-sm)"
p="1% 0 0 2.5%"
overflowX={variables[inputType].length > 3 ? 'scroll' : 'hidden'}
overflowY="hidden"
justifyContent="space-between"
alignItems="center"
>
<Flex justifyContent="start" alignItems="center" w="100%" wrap="wrap">
{variables[inputType].map((role: string, index: number) => (
<Box key={index} margin="0.5%" role="group">
<Tag
size="sm"
variant="outline"
colorScheme="gray"
minW="fit-content"
>
<TagLabel cursor="default">{role}</TagLabel>
<TagRightIcon
boxSize="12px"
as={FaTimes}
display="none"
cursor="pointer"
_groupHover={{ display: 'block' }}
onClick={() =>
updateInputHandler(
inputType,
ArrayInputOperations.REMOVE,
role,
)
}
/>
</Tag>
</Box>
))}
</Flex>
<Menu matchWidth={true}>
<MenuButton px="10px" py="7.5px">
<FaAngleDown />
</MenuButton>
<MenuList
position="absolute"
top="0"
right="0"
zIndex="10"
maxH="150"
overflowX="scroll"
>
<MenuOptionGroup
title={undefined}
value={variables[inputType]}
type="checkbox"
onChange={(values: string[] | string) => {
setVariables({
...variables,
[inputType]: values,
});
}}
>
{availableUserRoles.map((role) => {
return (
<MenuItemOption
key={`multiselect-menu-${role}`}
value={role}
>
{role}
</MenuItemOption>
);
})}
</MenuOptionGroup>
</MenuList>
</Menu>
</Flex>
</Flex>
);
}
if (Object.values(TextAreaInputType).includes(inputType)) { if (Object.values(TextAreaInputType).includes(inputType)) {
return ( return (
<Textarea <Textarea
{...props} {...props}
size="lg" size="lg"
value={inputData[inputType]} fontSize={14}
onChange={(e: any) => { value={variables[inputType] ? variables[inputType] : ''}
setInputData({ ...inputData, [inputType]: e.target.value }); onChange={(
}} event: Event & {
target: HTMLInputElement;
},
) =>
setVariables({
...variables,
[inputType]: event.target.value,
})
}
/> />
); );
} }
if (Object.values(SwitchInputType).includes(inputType)) { if (Object.values(SwitchInputType).includes(inputType)) {
return ( return (
<Flex w="25%" justifyContent="space-between"> <Flex w="25%" justifyContent="space-between">
<Code h="75%">Off</Code> <Text h="75%" fontWeight="bold" marginRight="2">
Off
</Text>
<Switch <Switch
size="md" size="md"
isChecked={variables[inputType]} isChecked={
hasReversedValue ? !variables[inputType] : variables[inputType]
}
onChange={() => { onChange={() => {
setVariables({ setVariables({
...variables, ...variables,
@ -314,7 +410,9 @@ const InputField = ({
}); });
}} }}
/> />
<Code h="75%">On</Code> <Text h="75%" fontWeight="bold" marginLeft="2">
On
</Text>
</Flex> </Flex>
); );
} }

View File

@ -0,0 +1,385 @@
import React, { useState, useCallback, useEffect } from 'react';
import {
Button,
Center,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
useToast,
Tabs,
TabList,
Tab,
TabPanels,
TabPanel,
InputGroup,
Input,
InputRightElement,
Text,
Link,
Tooltip,
} from '@chakra-ui/react';
import { useClient } from 'urql';
import { FaUserPlus, FaMinusCircle, FaPlus, FaUpload } from 'react-icons/fa';
import { useDropzone } from 'react-dropzone';
import { validateEmail, validateURI } from '../utils';
import { InviteMembers } from '../graphql/mutation';
import { ArrayInputOperations } from '../constants';
import parseCSV from '../utils/parseCSV';
interface stateDataTypes {
value: string;
isInvalid: boolean;
}
interface requestParamTypes {
emails: string[];
redirect_uri?: string;
}
const initData: stateDataTypes = {
value: '',
isInvalid: false,
};
const InviteMembersModal = ({
updateUserList,
disabled = true,
}: {
updateUserList: Function;
disabled: boolean;
}) => {
const client = useClient();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const [tabIndex, setTabIndex] = useState<number>(0);
const [redirectURI, setRedirectURI] = useState<stateDataTypes>({
...initData,
});
const [emails, setEmails] = useState<stateDataTypes[]>([{ ...initData }]);
const [disableSendButton, setDisableSendButton] = useState<boolean>(false);
const [loading, setLoading] = React.useState<boolean>(false);
useEffect(() => {
if (redirectURI.isInvalid) {
setDisableSendButton(true);
} else if (emails.some((emailData) => emailData.isInvalid)) {
setDisableSendButton(true);
} else {
setDisableSendButton(false);
}
}, [redirectURI, emails]);
useEffect(() => {
return () => {
setRedirectURI({ ...initData });
setEmails([{ ...initData }]);
};
}, []);
const sendInviteHandler = async () => {
setLoading(true);
try {
const emailList = emails
.filter((emailData) => !emailData.isInvalid)
.map((emailData) => emailData.value);
const params: requestParamTypes = {
emails: emailList,
};
if (redirectURI.value !== '' && !redirectURI.isInvalid) {
params.redirect_uri = redirectURI.value;
}
if (emailList.length > 0) {
const res = await client
.mutation(InviteMembers, {
params,
})
.toPromise();
if (res.error) {
throw new Error('Internal server error');
return;
}
toast({
title: 'Invites sent successfully!',
isClosable: true,
status: 'success',
position: 'top-right',
});
setLoading(false);
updateUserList();
} else {
throw new Error('Please add emails');
}
} catch (error: any) {
toast({
title: error?.message || 'Error occurred, try again!',
isClosable: true,
status: 'error',
position: 'top-right',
});
setLoading(false);
}
closeModalHandler();
};
const updateEmailListHandler = (operation: string, index: number = 0) => {
switch (operation) {
case ArrayInputOperations.APPEND:
setEmails([...emails, { ...initData }]);
break;
case ArrayInputOperations.REMOVE:
const updatedEmailList = [...emails];
updatedEmailList.splice(index, 1);
setEmails(updatedEmailList);
break;
default:
break;
}
};
const inputChangeHandler = (value: string, index: number) => {
const updatedEmailList = [...emails];
updatedEmailList[index].value = value;
updatedEmailList[index].isInvalid = !validateEmail(value);
setEmails(updatedEmailList);
};
const changeTabsHandler = (index: number) => {
setTabIndex(index);
};
const onDrop = useCallback(async (acceptedFiles) => {
const result = await parseCSV(acceptedFiles[0], ',');
setEmails(result);
changeTabsHandler(0);
}, []);
const setRedirectURIHandler = (value: string) => {
const updatedRedirectURI: stateDataTypes = {
value: '',
isInvalid: false,
};
updatedRedirectURI.value = value;
updatedRedirectURI.isInvalid = !validateURI(value);
setRedirectURI(updatedRedirectURI);
};
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: 'text/csv',
});
const closeModalHandler = () => {
setRedirectURI({
value: '',
isInvalid: false,
});
setEmails([
{
value: '',
isInvalid: false,
},
]);
onClose();
};
return (
<>
<Button
leftIcon={<FaUserPlus />}
colorScheme="blue"
variant="solid"
onClick={onOpen}
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 'Features' tab within 'Environment' to enable it."
>
Invite Members
</Tooltip>
) : (
'Invite Members'
)}
</Center>{' '}
</Button>
<Modal isOpen={isOpen} onClose={closeModalHandler} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Invite Members</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Tabs
isFitted
variant="enclosed"
index={tabIndex}
onChange={changeTabsHandler}
>
<TabList>
<Tab>Enter emails</Tab>
<Tab>Upload CSV</Tab>
</TabList>
<TabPanels
border="1px"
borderTop="0"
borderBottomRadius="5px"
borderColor="inherit"
>
<TabPanel>
<Flex flexDirection="column">
<Flex
width="100%"
justifyContent="start"
alignItems="center"
marginBottom="2%"
>
<Flex marginLeft="2.5%">Redirect URI</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<InputGroup size="md" marginBottom="2.5%">
<Input
pr="4.5rem"
type="text"
placeholder="https://domain.com/sign-up"
value={redirectURI.value}
isInvalid={redirectURI.isInvalid}
onChange={(e) =>
setRedirectURIHandler(e.currentTarget.value)
}
/>
</InputGroup>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex marginLeft="2.5%">Emails</Flex>
<Flex>
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
h="1.75rem"
size="sm"
variant="ghost"
onClick={() =>
updateEmailListHandler(ArrayInputOperations.APPEND)
}
>
Add more emails
</Button>
</Flex>
</Flex>
<Flex flexDirection="column" maxH={250} overflowY="scroll">
{emails.map((emailData, index) => (
<Flex
key={`email-data-${index}`}
justifyContent="center"
alignItems="center"
>
<InputGroup size="md" marginBottom="2.5%">
<Input
pr="4.5rem"
type="text"
placeholder="name@domain.com"
value={emailData.value}
isInvalid={emailData.isInvalid}
onChange={(e) =>
inputChangeHandler(e.currentTarget.value, index)
}
/>
<InputRightElement width="3rem">
<Button
h="1.75rem"
size="sm"
colorScheme="blackAlpha"
variant="ghost"
onClick={() =>
updateEmailListHandler(
ArrayInputOperations.REMOVE,
index,
)
}
>
<FaMinusCircle />
</Button>
</InputRightElement>
</InputGroup>
</Flex>
))}
</Flex>
</Flex>
</TabPanel>
<TabPanel>
<Flex
justify="center"
align="center"
textAlign="center"
bg="#f0f0f0"
h={230}
p={50}
m={2}
borderRadius={5}
{...getRootProps()}
>
<input {...getInputProps()} />
{isDragActive ? (
<Text>Drop the files here...</Text>
) : (
<Flex
flexDirection="column"
justifyContent="center"
alignItems="center"
>
<Center boxSize="20" color="blackAlpha.500">
<FaUpload fontSize="40" />
</Center>
<Text>
Drag 'n' drop the csv file here, or click to select.
</Text>
<Text size="xs">
Download{' '}
<Link
href={`/dashboard/public/sample.csv`}
download="sample.csv"
color="blue.600"
onClick={(e) => e.stopPropagation()}
>
{' '}
sample.csv
</Link>{' '}
and modify it.{' '}
</Text>
</Flex>
)}
</Flex>
</TabPanel>
</TabPanels>
</Tabs>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
variant="solid"
onClick={sendInviteHandler}
isDisabled={disableSendButton || loading}
>
<Center h="100%" pt="5%">
Send
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default InviteMembersModal;

View File

@ -1,4 +1,4 @@
import React, { ReactNode } from 'react'; import React, { Fragment, ReactNode } from 'react';
import { import {
IconButton, IconButton,
Box, Box,
@ -17,32 +17,104 @@ import {
MenuButton, MenuButton,
MenuItem, MenuItem,
MenuList, MenuList,
Accordion,
AccordionButton,
AccordionPanel,
AccordionItem,
useMediaQuery,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import {
FiHome, FiUser,
FiCode, FiCode,
FiSettings, FiSettings,
FiMenu, FiMenu,
FiUser,
FiUsers, FiUsers,
FiChevronDown, FiChevronDown,
FiLink,
FiFileText,
} from 'react-icons/fi'; } from 'react-icons/fi';
import { BiCustomize } from 'react-icons/bi';
import { AiOutlineKey } from 'react-icons/ai';
import { SiOpenaccess, SiJsonwebtokens } from 'react-icons/si';
import { MdSecurity } from 'react-icons/md';
import { RiDatabase2Line } from 'react-icons/ri';
import { BsCheck2Circle } from 'react-icons/bs';
import { HiOutlineMail, HiOutlineOfficeBuilding } from 'react-icons/hi';
import { IconType } from 'react-icons'; import { IconType } from 'react-icons';
import { ReactText } from 'react'; import { ReactText } from 'react';
import { useMutation } from 'urql'; import { useMutation, useQuery } from 'urql';
import { NavLink, useNavigate, useLocation } from 'react-router-dom'; import { NavLink, useNavigate, useLocation } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext'; import { useAuthContext } from '../contexts/AuthContext';
import { AdminLogout } from '../graphql/mutation'; import { AdminLogout } from '../graphql/mutation';
import { MetaQuery } from '../graphql/queries';
interface SubRoutes {
name: string;
icon: IconType;
route: string;
}
interface LinkItemProps { interface LinkItemProps {
name: string; name: string;
icon: IconType; icon: IconType;
route: string; route: string;
subRoutes?: SubRoutes[];
} }
const LinkItems: Array<LinkItemProps> = [ const LinkItems: Array<LinkItemProps> = [
// { name: 'Home', icon: FiHome, route: '/' }, {
{ name: 'Environment Variables', icon: FiSettings, route: '/' }, name: 'Environment ',
icon: FiSettings,
route: '/',
subRoutes: [
{
name: 'OAuth Config',
icon: AiOutlineKey,
route: '/oauth-setting',
},
{ name: 'Roles', icon: FiUser, route: '/roles' },
{
name: 'JWT Secrets',
icon: SiJsonwebtokens,
route: '/jwt-config',
},
{
name: 'Session Storage',
icon: RiDatabase2Line,
route: '/session-storage',
},
{
name: 'Email Configurations',
icon: HiOutlineMail,
route: '/email-config',
},
{
name: 'Domain White Listing',
icon: BsCheck2Circle,
route: '/whitelist-variables',
},
{
name: 'Organization Info',
icon: HiOutlineOfficeBuilding,
route: '/organization-info',
},
{ name: 'Access Token', icon: SiOpenaccess, route: '/access-token' },
{
name: 'Features',
icon: BiCustomize,
route: '/features',
},
{ name: 'Database', icon: RiDatabase2Line, route: '/db-cred' },
{
name: ' Security',
icon: MdSecurity,
route: '/admin-secret',
},
],
},
{ name: 'Users', icon: FiUsers, route: '/users' }, { name: 'Users', icon: FiUsers, route: '/users' },
{ name: 'Webhooks', icon: FiLink, route: '/webhooks' },
{ name: 'Email Templates', icon: FiFileText, route: '/email-templates' },
]; ];
interface SidebarProps extends BoxProps { interface SidebarProps extends BoxProps {
@ -51,20 +123,28 @@ interface SidebarProps extends BoxProps {
export const Sidebar = ({ onClose, ...rest }: SidebarProps) => { export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
const { pathname } = useLocation(); const { pathname } = useLocation();
const [{ data }] = useQuery({ query: MetaQuery });
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return ( return (
<Box <Box
transition="3s ease" transition="3s ease"
bg={useColorModeValue('white', 'gray.900')} bg={useColorModeValue('white', 'gray.900')}
borderRight="1px" borderRight="1px"
borderRightColor={useColorModeValue('gray.200', 'gray.700')} borderRightColor={useColorModeValue('gray.200', 'gray.700')}
w={{ base: 'full', md: 60 }} w={{ base: 'full', md: '64' }}
pos="fixed" pos="fixed"
h="full" h="full"
{...rest} {...rest}
> >
<Flex h="20" alignItems="center" mx="8" justifyContent="space-between"> <Flex
h="20"
alignItems="center"
mx="18"
justifyContent="space-between"
flexDirection="row"
>
<NavLink to="/"> <NavLink to="/">
<Flex alignItems="center"> <Flex alignItems="center" mt="6">
<Image <Image
src="https://authorizer.dev/images/logo.png" src="https://authorizer.dev/images/logo.png"
alt="logo" alt="logo"
@ -77,34 +157,104 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
</NavLink> </NavLink>
<CloseButton display={{ base: 'flex', md: 'none' }} onClick={onClose} /> <CloseButton display={{ base: 'flex', md: 'none' }} onClick={onClose} />
</Flex> </Flex>
{LinkItems.map((link) => (
<NavLink key={link.name} to={link.route}>
<NavItem
icon={link.icon}
color={pathname === link.route ? 'blue.500' : ''}
>
{link.name}
</NavItem>
</NavLink>
))}
<Link <Accordion defaultIndex={[0]} allowMultiple>
href="/playground" <AccordionItem textAlign="center" border="none" w="100%">
target="_blank" {LinkItems.map((link) =>
style={{ link?.subRoutes ? (
textDecoration: 'none', <div key={link.name}>
}} <AccordionButton _focus={{ boxShadow: 'none' }}>
_focus={{ _boxShadow: 'none' }} <Text as="div" fontSize="md">
> <NavItem
<NavItem icon={FiCode}>API Playground</NavItem> icon={link.icon}
</Link> color={pathname === link.route ? 'blue.500' : ''}
style={{ outline: 'unset' }}
height={12}
ml={-1}
mb={isNotSmallerScreen ? -1 : -4}
w={isNotSmallerScreen ? '100%' : '310%'}
>
<Fragment>
{link.name}
<Box display={{ base: 'none', md: 'flex' }} ml={12}>
<FiChevronDown />
</Box>
</Fragment>
</NavItem>
</Text>
</AccordionButton>
<AccordionPanel>
{link.subRoutes?.map((sublink) => (
<NavLink
key={sublink.name}
to={sublink.route}
onClick={onClose}
>
{' '}
<Text as="div" fontSize="xs" ml={2}>
<NavItem
icon={sublink.icon}
color={pathname === sublink.route ? 'blue.500' : ''}
height={8}
>
{sublink.name}
</NavItem>{' '}
</Text>
</NavLink>
))}
</AccordionPanel>
</div>
) : (
<NavLink key={link.name} to={link.route}>
{' '}
<Text as="div" fontSize="md" w="100%" mt={-2}>
<NavItem
icon={link.icon}
color={pathname === link.route ? 'blue.500' : ''}
height={12}
onClick={onClose}
>
{link.name}
</NavItem>{' '}
</Text>
</NavLink>
),
)}
<Link
href="/playground"
target="_blank"
style={{
textDecoration: 'none',
}}
_focus={{ _boxShadow: 'none' }}
>
<NavItem icon={FiCode}>API Playground</NavItem>
</Link>
</AccordionItem>
</Accordion>
{data?.meta?.version && (
<Flex alignContent="center">
{' '}
<Text
color="gray.400"
fontSize="sm"
textAlign="center"
position="absolute"
bottom="5"
left="7"
>
Current Version: {data.meta.version}
</Text>
</Flex>
)}
</Box> </Box>
); );
}; };
interface NavItemProps extends FlexProps { interface NavItemProps extends FlexProps {
icon: IconType; icon: IconType;
children: ReactText; children: ReactText | JSX.Element | JSX.Element[];
} }
export const NavItem = ({ icon, children, ...rest }: NavItemProps) => { export const NavItem = ({ icon, children, ...rest }: NavItemProps) => {
return ( return (
@ -152,7 +302,7 @@ export const MobileNav = ({ onOpen, ...rest }: MobileProps) => {
return ( return (
<Flex <Flex
ml={{ base: 0, md: 60 }} ml={{ base: 0, md: 64 }}
px={{ base: 4, md: 4 }} px={{ base: 4, md: 4 }}
height="20" height="20"
position="fixed" position="fixed"
@ -189,7 +339,7 @@ export const MobileNav = ({ onOpen, ...rest }: MobileProps) => {
transition="all 0.3s" transition="all 0.3s"
_focus={{ boxShadow: 'none' }} _focus={{ boxShadow: 'none' }}
> >
<HStack> <HStack mr={5}>
<FiUser /> <FiUser />
<VStack <VStack
display={{ base: 'none', md: 'flex' }} display={{ base: 'none', md: 'flex' }}

View File

@ -0,0 +1,565 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Button,
Center,
Flex,
Input,
InputGroup,
MenuItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Select,
Text,
useDisclosure,
useToast,
Alert,
AlertIcon,
Collapse,
Box,
TableContainer,
Table,
Thead,
Tr,
Th,
Tbody,
Td,
Code,
Radio,
RadioGroup,
Stack,
Textarea,
} from '@chakra-ui/react';
import { FaPlus, FaAngleDown, FaAngleUp } from 'react-icons/fa';
import { useClient } from 'urql';
import EmailEditor from 'react-email-editor';
import {
UpdateModalViews,
EmailTemplateInputDataFields,
emailTemplateEventNames,
emailTemplateVariables,
EmailTemplateEditors,
} from '../constants';
import { capitalizeFirstLetter } from '../utils';
import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation';
interface selectedEmailTemplateDataTypes {
[EmailTemplateInputDataFields.ID]: string;
[EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string;
[EmailTemplateInputDataFields.CREATED_AT]: number;
[EmailTemplateInputDataFields.TEMPLATE]: string;
[EmailTemplateInputDataFields.DESIGN]: string;
}
interface UpdateEmailTemplateInputPropTypes {
view: UpdateModalViews;
selectedTemplate?: selectedEmailTemplateDataTypes;
fetchEmailTemplatesData: Function;
}
interface templateVariableDataTypes {
text: string;
value: string;
description: string;
}
interface emailTemplateDataType {
[EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string;
[EmailTemplateInputDataFields.TEMPLATE]: string;
[EmailTemplateInputDataFields.DESIGN]: string;
}
interface validatorDataType {
[EmailTemplateInputDataFields.SUBJECT]: boolean;
}
const initTemplateData: emailTemplateDataType = {
[EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup,
[EmailTemplateInputDataFields.SUBJECT]: '',
[EmailTemplateInputDataFields.TEMPLATE]: '',
[EmailTemplateInputDataFields.DESIGN]: '',
};
const initTemplateValidatorData: validatorDataType = {
[EmailTemplateInputDataFields.SUBJECT]: true,
};
const UpdateEmailTemplate = ({
view,
selectedTemplate,
fetchEmailTemplatesData,
}: UpdateEmailTemplateInputPropTypes) => {
const client = useClient();
const toast = useToast();
const emailEditorRef = useRef(null);
const { isOpen, onOpen, onClose } = useDisclosure();
const [loading, setLoading] = useState<boolean>(false);
const [editor, setEditor] = useState<string>(
EmailTemplateEditors.PLAIN_HTML_EDITOR,
);
const [templateVariables, setTemplateVariables] = useState<
templateVariableDataTypes[]
>([]);
const [templateData, setTemplateData] = useState<emailTemplateDataType>({
...initTemplateData,
});
const [validator, setValidator] = useState<validatorDataType>({
...initTemplateValidatorData,
});
const [isDynamicVariableInfoOpen, setIsDynamicVariableInfoOpen] =
useState<boolean>(false);
const onReady = () => {
if (selectedTemplate) {
const { design } = selectedTemplate;
try {
if (design) {
const designData = JSON.parse(design);
// @ts-ignore
emailEditorRef.current.editor.loadDesign(designData);
}
} catch (error) {
console.error(error);
onClose();
}
}
};
const inputChangehandler = (inputType: string, value: any) => {
if (inputType !== EmailTemplateInputDataFields.EVENT_NAME) {
setValidator({
...validator,
[inputType]: value?.trim().length,
});
}
setTemplateData({ ...templateData, [inputType]: value });
};
const validateData = () => {
return (
!loading &&
templateData[EmailTemplateInputDataFields.EVENT_NAME].length > 0 &&
templateData[EmailTemplateInputDataFields.SUBJECT].length > 0 &&
validator[EmailTemplateInputDataFields.SUBJECT]
);
};
const updateTemplate = async (params: emailTemplateDataType) => {
let res: any = {};
if (
view === UpdateModalViews.Edit &&
selectedTemplate?.[EmailTemplateInputDataFields.ID]
) {
res = await client
.mutation(EditEmailTemplate, {
params: {
...params,
id: selectedTemplate[EmailTemplateInputDataFields.ID],
},
})
.toPromise();
} else {
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
}
setLoading(false);
if (res.error) {
toast({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'top-right',
});
} else if (
res.data?._add_email_template ||
res.data?._update_email_template
) {
toast({
title: capitalizeFirstLetter(
res.data?._add_email_template?.message ||
res.data?._update_email_template?.message,
),
isClosable: true,
status: 'success',
position: 'top-right',
});
setTemplateData({
...initTemplateData,
});
setValidator({ ...initTemplateValidatorData });
fetchEmailTemplatesData();
}
};
const saveData = async () => {
if (!validateData()) return;
setLoading(true);
let params: emailTemplateDataType = {
[EmailTemplateInputDataFields.EVENT_NAME]:
templateData[EmailTemplateInputDataFields.EVENT_NAME],
[EmailTemplateInputDataFields.SUBJECT]:
templateData[EmailTemplateInputDataFields.SUBJECT],
[EmailTemplateInputDataFields.TEMPLATE]:
templateData[EmailTemplateInputDataFields.TEMPLATE],
[EmailTemplateInputDataFields.DESIGN]: '',
};
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
// @ts-ignore
await emailEditorRef.current.editor.exportHtml(async (data) => {
const { design, html } = data;
if (!html || !design) {
setLoading(false);
return;
}
params = {
...params,
[EmailTemplateInputDataFields.TEMPLATE]: html.trim(),
[EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design),
};
await updateTemplate(params);
});
} else {
await updateTemplate(params);
}
view === UpdateModalViews.ADD && onClose();
};
const resetData = () => {
if (selectedTemplate) {
setTemplateData(selectedTemplate);
} else {
setTemplateData({ ...initTemplateData });
}
};
// set template data if edit modal is open
useEffect(() => {
if (
isOpen &&
view === UpdateModalViews.Edit &&
selectedTemplate &&
Object.keys(selectedTemplate || {}).length
) {
const { id, created_at, ...rest } = selectedTemplate;
setTemplateData(rest);
}
}, [isOpen]);
// set template variables
useEffect(() => {
const updatedTemplateVariables = Object.entries(
emailTemplateVariables,
).reduce((acc, [key, val]): any => {
if (
(templateData[EmailTemplateInputDataFields.EVENT_NAME] !==
emailTemplateEventNames['Verify Otp'] &&
val === emailTemplateVariables.otp) ||
(templateData[EmailTemplateInputDataFields.EVENT_NAME] ===
emailTemplateEventNames['Verify Otp'] &&
val === emailTemplateVariables.verification_url)
) {
return acc;
}
return [
...acc,
{
text: key,
value: val.value,
description: val.description,
},
];
}, []);
setTemplateVariables(updatedTemplateVariables);
}, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]);
// change editor
useEffect(() => {
if (isOpen && selectedTemplate) {
const { design } = selectedTemplate;
if (design) {
setEditor(EmailTemplateEditors.UNLAYER_EDITOR);
} else {
setEditor(EmailTemplateEditors.PLAIN_HTML_EDITOR);
}
}
}, [isOpen, selectedTemplate]);
// reset fields when editor is changed
useEffect(() => {
if (selectedTemplate?.design) {
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate.template,
[EmailTemplateInputDataFields.DESIGN]: selectedTemplate.design,
});
} else {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: '',
[EmailTemplateInputDataFields.DESIGN]: '',
});
}
} else if (selectedTemplate?.template) {
if (editor === EmailTemplateEditors.UNLAYER_EDITOR) {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: '',
[EmailTemplateInputDataFields.DESIGN]: '',
});
} else {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: selectedTemplate?.template,
[EmailTemplateInputDataFields.DESIGN]: '',
});
}
}
}, [editor]);
return (
<>
{view === UpdateModalViews.ADD ? (
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
variant="solid"
onClick={onOpen}
isDisabled={false}
size="sm"
>
<Center h="100%">Add Template</Center>{' '}
</Button>
) : (
<MenuItem onClick={onOpen}>Edit</MenuItem>
)}
<Modal
isOpen={isOpen}
onClose={() => {
resetData();
onClose();
}}
size="6xl"
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
{view === UpdateModalViews.ADD
? 'Add New Email Template'
: 'Edit Email Template'}
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex
flexDirection="column"
border="1px"
borderRadius="md"
borderColor="gray.200"
p="5"
>
<Alert
status="info"
onClick={() =>
setIsDynamicVariableInfoOpen(!isDynamicVariableInfoOpen)
}
borderRadius="5"
marginBottom={5}
cursor="pointer"
fontSize="sm"
>
<AlertIcon />
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<Box width="85%">
<b>Note:</b> You can add set of dynamic variables to subject
and email body. Click here to see the set of dynamic
variables.
</Box>
{isDynamicVariableInfoOpen ? <FaAngleUp /> : <FaAngleDown />}
</Flex>
</Alert>
<Collapse
style={{
width: '100%',
}}
in={isDynamicVariableInfoOpen}
>
<TableContainer
background="gray.100"
borderRadius={5}
height={200}
width="100%"
overflowY="auto"
overflowWrap="break-word"
>
<Table variant="simple">
<Thead>
<Tr>
<Th>Variable</Th>
<Th>Description</Th>
</Tr>
</Thead>
<Tbody>
{templateVariables.map((i) => (
<Tr key={i.text}>
<Td>
<Code fontSize="sm">{`{{.${i.text}}}`}</Code>
</Td>
<Td>
<Text
size="sm"
fontSize="sm"
overflowWrap="break-word"
width="100%"
>
{i.description}
</Text>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Collapse>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex flex="1">Event Name</Flex>
<Flex flex="3">
<Select
size="md"
value={
templateData[EmailTemplateInputDataFields.EVENT_NAME]
}
onChange={(e) =>
inputChangehandler(
EmailTemplateInputDataFields.EVENT_NAME,
e.currentTarget.value,
)
}
>
{Object.entries(emailTemplateEventNames).map(
([key, value]: any) => (
<option value={value} key={key}>
{key}
</option>
),
)}
</Select>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="start"
alignItems="center"
marginBottom="2%"
>
<Flex flex="1">Subject</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="Subject Line"
value={templateData[EmailTemplateInputDataFields.SUBJECT]}
isInvalid={
!validator[EmailTemplateInputDataFields.SUBJECT]
}
onChange={(e) =>
inputChangehandler(
EmailTemplateInputDataFields.SUBJECT,
e.currentTarget.value,
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="flex-start"
alignItems="center"
marginBottom="2%"
>
<Flex flex="1">Template Body</Flex>
<Flex flex="3">
<RadioGroup
onChange={(value) => setEditor(value)}
value={editor}
>
<Stack direction="row" spacing="50px">
<Radio value={EmailTemplateEditors.PLAIN_HTML_EDITOR}>
Plain HTML
</Radio>
<Radio value={EmailTemplateEditors.UNLAYER_EDITOR}>
Unlayer Editor
</Radio>
</Stack>
</RadioGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="flex-start"
alignItems="center"
border="1px solid"
borderColor="gray.200"
>
{editor === EmailTemplateEditors.UNLAYER_EDITOR ? (
<EmailEditor ref={emailEditorRef} onReady={onReady} />
) : (
<Textarea
value={templateData.template}
onChange={(e) => {
setTemplateData({
...templateData,
[EmailTemplateInputDataFields.TEMPLATE]: e.target.value,
});
}}
placeholder="Template HTML"
border="0"
height="500px"
/>
)}
</Flex>
</Flex>
</ModalBody>
<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,697 @@
import React, { useEffect, useState } from 'react';
import {
Button,
Center,
Code,
Collapse,
Flex,
Input,
InputGroup,
InputRightElement,
MenuItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Select,
Switch,
Text,
useDisclosure,
useToast,
Alert,
AlertIcon,
Divider,
} from '@chakra-ui/react';
import {
FaAngleDown,
FaAngleUp,
FaMinusCircle,
FaPlus,
FaRegClone,
} from 'react-icons/fa';
import { useClient } from 'urql';
import {
webhookEventNames,
ArrayInputOperations,
WebhookInputDataFields,
WebhookInputHeaderFields,
UpdateModalViews,
webhookVerifiedStatus,
webhookPayloadExample,
} from '../constants';
import {
capitalizeFirstLetter,
copyTextToClipboard,
validateURI,
} from '../utils';
import { AddWebhook, EditWebhook, TestEndpoint } from '../graphql/mutation';
import { BiCheckCircle, BiError, BiErrorCircle } from 'react-icons/bi';
interface headersDataType {
[WebhookInputHeaderFields.KEY]: string;
[WebhookInputHeaderFields.VALUE]: string;
}
interface headersValidatorDataType {
[WebhookInputHeaderFields.KEY]: boolean;
[WebhookInputHeaderFields.VALUE]: boolean;
}
interface selecetdWebhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]?: 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.EVENT_DESCRIPTION]?: 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.EVENT_DESCRIPTION]: '',
[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.EVENT_DESCRIPTION:
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.EVENT_DESCRIPTION]:
webhook[WebhookInputDataFields.EVENT_DESCRIPTION],
[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: 'top-right',
});
} else if (res.data?._add_webhook || res.data?._update_webhook) {
toast({
title: capitalizeFirstLetter(
res.data?._add_webhook?.message || res.data?._update_webhook?.message,
),
isClosable: true,
status: 'success',
position: 'top-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].split('-')[0]
}
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">Event Description</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="User event"
value={webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.EVENT_DESCRIPTION,
e.currentTarget.value,
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="start"
alignItems="center"
marginBottom="5%"
>
<Flex flex="1">Endpoint</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="https://domain.com/webhook"
value={webhook[WebhookInputDataFields.ENDPOINT]}
isInvalid={!validator[WebhookInputDataFields.ENDPOINT]}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.ENDPOINT,
e.currentTarget.value,
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="5%"
>
<Flex flex="1">Enabled</Flex>
<Flex w="25%" justifyContent="space-between">
<Text h="75%" fontWeight="bold" marginRight="2">
Off
</Text>
<Switch
size="md"
isChecked={webhook[WebhookInputDataFields.ENABLED]}
onChange={() =>
inputChangehandler(
WebhookInputDataFields.ENABLED,
!webhook[WebhookInputDataFields.ENABLED],
)
}
/>
<Text h="75%" fontWeight="bold" marginLeft="2">
On
</Text>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="5%"
>
<Flex>Headers</Flex>
<Flex>
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
h="1.75rem"
size="sm"
variant="ghost"
paddingRight="0"
onClick={() => updateHeaders(ArrayInputOperations.APPEND)}
>
Add more Headers
</Button>
</Flex>
</Flex>
<Flex flexDirection="column" maxH={220} overflowY="auto">
{webhook[WebhookInputDataFields.HEADERS]?.map(
(headerData, index) => (
<Flex
key={`header-data-${index}`}
justifyContent="center"
alignItems="center"
>
<InputGroup size="md" marginBottom="2.5%">
<Input
type="text"
placeholder="key"
value={headerData[WebhookInputHeaderFields.KEY]}
isInvalid={
!validator[WebhookInputDataFields.HEADERS][index]?.[
WebhookInputHeaderFields.KEY
]
}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.HEADERS,
e.target.value,
WebhookInputHeaderFields.KEY,
index,
)
}
width="30%"
marginRight="2%"
/>
<Center marginRight="2%">
<Text fontWeight="bold">:</Text>
</Center>
<Input
type="text"
placeholder="value"
value={headerData[WebhookInputHeaderFields.VALUE]}
isInvalid={
!validator[WebhookInputDataFields.HEADERS][index]?.[
WebhookInputHeaderFields.VALUE
]
}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.HEADERS,
e.target.value,
WebhookInputHeaderFields.VALUE,
index,
)
}
width="65%"
/>
<InputRightElement width="3rem">
<Button
width="6rem"
colorScheme="blackAlpha"
variant="ghost"
padding="0"
onClick={() =>
updateHeaders(ArrayInputOperations.REMOVE, index)
}
>
<FaMinusCircle />
</Button>
</InputRightElement>
</InputGroup>
</Flex>
),
)}
</Flex>
<Divider marginY={5} />
<Alert
status="info"
onClick={() => setIsShowingPayload(!isShowingPayload)}
borderRadius="5"
cursor="pointer"
fontSize="sm"
>
<AlertIcon />
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
Checkout the example payload
{isShowingPayload ? <FaAngleUp /> : <FaAngleDown />}
</Flex>
</Alert>
<Collapse
style={{
marginTop: 10,
width: '100%',
}}
in={isShowingPayload}
>
<Code
width="inherit"
borderRadius={5}
padding={2}
position="relative"
>
<pre style={{ overflow: 'auto' }}>
{webhookPayloadExample}
</pre>
{isShowingPayload && (
<Flex
position="absolute"
top={4}
right={4}
cursor="pointer"
onClick={() => copyTextToClipboard(webhookPayloadExample)}
>
<FaRegClone color="#bfbfbf" />
</Flex>
)}
</Code>
</Collapse>
</Flex>
</ModalBody>
<ModalFooter>
<Button
colorScheme={
verifiedStatus === webhookVerifiedStatus.VERIFIED
? 'green'
: verifiedStatus === webhookVerifiedStatus.PENDING
? 'yellow'
: 'red'
}
variant="outline"
onClick={verifyEndpoint}
isLoading={verifyingEndpoint}
isDisabled={!validateData()}
marginRight="5"
leftIcon={
verifiedStatus === webhookVerifiedStatus.VERIFIED ? (
<BiCheckCircle />
) : verifiedStatus === webhookVerifiedStatus.PENDING ? (
<BiErrorCircle />
) : (
<BiError />
)
}
>
{verifiedStatus === webhookVerifiedStatus.VERIFIED
? 'Endpoint Verified'
: verifiedStatus === webhookVerifiedStatus.PENDING
? 'Test Endpoint'
: 'Endpoint Not Verified'}
</Button>
<Button
colorScheme="blue"
variant="solid"
onClick={saveData}
isDisabled={!validateData()}
>
<Center h="100%" pt="5%">
Save
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default UpdateWebhookModal;

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

@ -2,15 +2,27 @@ export const LOGO_URL =
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png'; 'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
export const TextInputType = { export const TextInputType = {
ACCESS_TOKEN_EXPIRY_TIME: 'ACCESS_TOKEN_EXPIRY_TIME',
CLIENT_ID: 'CLIENT_ID',
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID', GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID', GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID', FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
DISCORD_CLIENT_ID: 'DISCORD_CLIENT_ID',
TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID',
MICROSOFT_CLIENT_ID: 'MICROSOFT_CLIENT_ID',
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: 'MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID',
TWITCH_CLIENT_ID: 'TWITCH_CLIENT_ID',
ROBLOX_CLIENT_ID: 'ROBLOX_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM', JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL', REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST', SMTP_HOST: 'SMTP_HOST',
SMTP_PORT: 'SMTP_PORT', SMTP_PORT: 'SMTP_PORT',
SMTP_USERNAME: 'SMTP_USERNAME', SMTP_USERNAME: 'SMTP_USERNAME',
SMTP_LOCAL_NAME: 'SMTP_LOCAL_NAME',
SENDER_EMAIL: 'SENDER_EMAIL', SENDER_EMAIL: 'SENDER_EMAIL',
SENDER_NAME: 'SENDER_NAME',
ORGANIZATION_NAME: 'ORGANIZATION_NAME', ORGANIZATION_NAME: 'ORGANIZATION_NAME',
ORGANIZATION_LOGO: 'ORGANIZATION_LOGO', ORGANIZATION_LOGO: 'ORGANIZATION_LOGO',
DATABASE_NAME: 'DATABASE_NAME', DATABASE_NAME: 'DATABASE_NAME',
@ -25,9 +37,17 @@ export const TextInputType = {
}; };
export const HiddenInputType = { export const HiddenInputType = {
CLIENT_SECRET: 'CLIENT_SECRET',
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET', GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET', GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET', FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
DISCORD_CLIENT_SECRET: 'DISCORD_CLIENT_SECRET',
TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET',
MICROSOFT_CLIENT_SECRET: 'MICROSOFT_CLIENT_SECRET',
TWITCH_CLIENT_SECRET: 'TWITCH_CLIENT_SECRET',
ROBLOX_CLIENT_SECRET: 'ROBLOX_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET', JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD', SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET', ADMIN_SECRET: 'ADMIN_SECRET',
@ -39,23 +59,41 @@ export const ArrayInputType = {
DEFAULT_ROLES: 'DEFAULT_ROLES', DEFAULT_ROLES: 'DEFAULT_ROLES',
PROTECTED_ROLES: 'PROTECTED_ROLES', PROTECTED_ROLES: 'PROTECTED_ROLES',
ALLOWED_ORIGINS: 'ALLOWED_ORIGINS', ALLOWED_ORIGINS: 'ALLOWED_ORIGINS',
USER_ROLES: 'roles',
}; };
export const SelectInputType = { export const SelectInputType = {
JWT_TYPE: 'JWT_TYPE', JWT_TYPE: 'JWT_TYPE',
GENDER: 'gender', GENDER: 'gender',
DEFAULT_AUTHORIZE_RESPONSE_TYPE: 'DEFAULT_AUTHORIZE_RESPONSE_TYPE',
DEFAULT_AUTHORIZE_RESPONSE_MODE: 'DEFAULT_AUTHORIZE_RESPONSE_MODE',
};
export const MultiSelectInputType = {
USER_ROLES: 'roles',
}; };
export const TextAreaInputType = { export const TextAreaInputType = {
CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT', CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT',
JWT_PRIVATE_KEY: 'JWT_PRIVATE_KEY',
JWT_PUBLIC_KEY: 'JWT_PUBLIC_KEY',
}; };
export const SwitchInputType = { export const SwitchInputType = {
APP_COOKIE_SECURE: 'APP_COOKIE_SECURE',
ADMIN_COOKIE_SECURE: 'ADMIN_COOKIE_SECURE',
DISABLE_LOGIN_PAGE: 'DISABLE_LOGIN_PAGE', DISABLE_LOGIN_PAGE: 'DISABLE_LOGIN_PAGE',
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN', DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION', DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION', DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
DISABLE_MOBILE_BASIC_AUTHENTICATION: 'DISABLE_MOBILE_BASIC_AUTHENTICATION',
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
DISABLE_MULTI_FACTOR_AUTHENTICATION: 'DISABLE_MULTI_FACTOR_AUTHENTICATION',
ENFORCE_MULTI_FACTOR_AUTHENTICATION: 'ENFORCE_MULTI_FACTOR_AUTHENTICATION',
DISABLE_PLAYGROUND: 'DISABLE_PLAYGROUND',
DISABLE_TOTP_LOGIN: 'DISABLE_TOTP_LOGIN',
DISABLE_MAIL_OTP_LOGIN: 'DISABLE_MAIL_OTP_LOGIN',
}; };
export const DateInputType = { export const DateInputType = {
@ -66,3 +104,288 @@ export const ArrayInputOperations = {
APPEND: 'APPEND', APPEND: 'APPEND',
REMOVE: 'REMOVE', REMOVE: 'REMOVE',
}; };
export const HMACEncryptionType = {
HS256: 'HS256',
HS384: 'HS384',
HS512: 'HS512',
};
export const RSAEncryptionType = {
RS256: 'RS256',
RS384: 'RS384',
RS512: 'RS512',
};
export const ECDSAEncryptionType = {
ES256: 'ES256',
ES384: 'ES384',
ES512: 'ES512',
};
export interface envVarTypes {
GOOGLE_CLIENT_ID: string;
GOOGLE_CLIENT_SECRET: string;
GITHUB_CLIENT_ID: string;
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;
DISCORD_CLIENT_ID: string;
DISCORD_CLIENT_SECRET: string;
TWITTER_CLIENT_ID: string;
TWITTER_CLIENT_SECRET: string;
MICROSOFT_CLIENT_ID: string;
MICROSOFT_CLIENT_SECRET: string;
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: string;
TWITCH_CLIENT_ID: string;
TWITCH_CLIENT_SECRET: string;
ROBLOX_CLIENT_ID: string;
ROBLOX_CLIENT_SECRET: string;
ROLES: [string] | [];
DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | [];
JWT_TYPE: string;
JWT_SECRET: string;
JWT_ROLE_CLAIM: string;
JWT_PRIVATE_KEY: string;
JWT_PUBLIC_KEY: string;
REDIS_URL: string;
SMTP_HOST: string;
SMTP_PORT: string;
SMTP_USERNAME: string;
SMTP_PASSWORD: string;
SMTP_LOCAL_NAME: string;
SENDER_EMAIL: string;
SENDER_NAME: string;
ALLOWED_ORIGINS: [string] | [];
ORGANIZATION_NAME: string;
ORGANIZATION_LOGO: string;
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
ADMIN_SECRET: string;
APP_COOKIE_SECURE: boolean;
ADMIN_COOKIE_SECURE: boolean;
DISABLE_LOGIN_PAGE: boolean;
DISABLE_MAGIC_LINK_LOGIN: boolean;
DISABLE_EMAIL_VERIFICATION: boolean;
DISABLE_BASIC_AUTHENTICATION: boolean;
DISABLE_MOBILE_BASIC_AUTHENTICATION: boolean;
DISABLE_SIGN_UP: boolean;
DISABLE_STRONG_PASSWORD: boolean;
OLD_ADMIN_SECRET: string;
DATABASE_NAME: string;
DATABASE_TYPE: string;
DATABASE_URL: string;
ACCESS_TOKEN_EXPIRY_TIME: string;
DISABLE_MULTI_FACTOR_AUTHENTICATION: boolean;
ENFORCE_MULTI_FACTOR_AUTHENTICATION: boolean;
DEFAULT_AUTHORIZE_RESPONSE_TYPE: string;
DEFAULT_AUTHORIZE_RESPONSE_MODE: string;
DISABLE_PLAYGROUND: boolean;
DISABLE_TOTP_LOGIN: boolean;
DISABLE_MAIL_OTP_LOGIN: boolean;
}
export const envSubViews = {
INSTANCE_INFO: 'instance-info',
ROLES: 'roles',
JWT_CONFIG: 'jwt-config',
SESSION_STORAGE: 'session-storage',
EMAIL_CONFIG: 'email-config',
WHITELIST_VARIABLES: 'whitelist-variables',
ORGANIZATION_INFO: 'organization-info',
ACCESS_TOKEN: 'access-token',
FEATURES: 'features',
ADMIN_SECRET: 'admin-secret',
DB_CRED: 'db-cred',
};
export enum WebhookInputDataFields {
ID = 'id',
EVENT_DESCRIPTION = 'event_description',
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
HEADERS = 'headers',
}
export enum EmailTemplateInputDataFields {
ID = 'id',
EVENT_NAME = 'event_name',
SUBJECT = 'subject',
CREATED_AT = 'created_at',
TEMPLATE = 'template',
DESIGN = 'design',
}
export enum WebhookInputHeaderFields {
KEY = 'key',
VALUE = 'value',
}
export enum UpdateModalViews {
ADD = 'add',
Edit = 'edit',
}
export const pageLimits: number[] = [5, 10, 15];
export const webhookEventNames = {
'User signup': 'user.signup',
'User created': 'user.created',
'User login': 'user.login',
'User deleted': 'user.deleted',
'User access enabled': 'user.access_enabled',
'User access revoked': 'user.access_revoked',
'User deactivated': 'user.deactivated',
};
export const emailTemplateEventNames = {
Signup: 'basic_auth_signup',
'Magic Link Login': 'magic_link_login',
'Update Email': 'update_email',
'Forgot Password': 'forgot_password',
'Verify Otp': 'verify_otp',
'Invite member': 'invite_member',
};
export enum webhookVerifiedStatus {
VERIFIED = 'verified',
NOT_VERIFIED = 'not_verified',
PENDING = 'verification_pending',
}
export const emailTemplateVariables = {
'user.id': {
description: `User identifier`,
value: '{.user.id}}',
},
'user.email': {
description: 'User email address',
value: '{.user.email}}',
},
'user.given_name': {
description: `User first name`,
value: '{.user.given_name}}',
},
'user.family_name': {
description: `User last name`,
value: '{.user.family_name}}',
},
'user.middle_name': {
description: `Middle name of user`,
value: '{.user.middle_name}}',
},
'user.nickname': {
description: `Nick name of user`,
value: '{.user.nickname}}',
},
'user.preferred_username': {
description: `Username, by default it is email`,
value: '{.user.preferred_username}}',
},
'user.signup_methods': {
description: `Comma separated list of methods using which user has signed up`,
value: '{.user.signup_methods}}',
},
'user.email_verified': {
description: `Whether email is verified or not`,
value: '{.user.email_verified}}',
},
'user.picture': {
description: `URL of the user profile picture`,
value: '{.user.picture}}',
},
'user.roles': {
description: `Comma separated list of roles assigned to user`,
value: '{.user.roles}}',
},
'user.gender': {
description: `Gender of user`,
value: '{.user.gender}}',
},
'user.birthdate': {
description: `BirthDate of user`,
value: '{.user.birthdate}}',
},
'user.phone_number': {
description: `Phone number of user`,
value: '{.user.phone_number}}',
},
'user.phone_number_verified': {
description: `Whether phone number is verified or not`,
value: '{.user.phone_number_verified}}',
},
'user.created_at': {
description: `User created at time`,
value: '{.user.created_at}}',
},
'user.updated_at': {
description: `Last updated time at user`,
value: '{.user.updated_at}}',
},
'organization.name': {
description: `Organization name`,
value: '{.organization.name}}',
},
'organization.logo': {
description: `Organization logo`,
value: '{.organization.logo}}',
},
verification_url: {
description: `Verification URL in case of events other than verify otp`,
value: '{.verification_url}}',
},
otp: {
description: `OTP sent during login with Multi factor authentication`,
value: '{.otp}}',
},
};
export const webhookPayloadExample: string = `{
"event_name":"user.login",
"user":{
"birthdate":null,
"created_at":1657524721,
"email":"lakhan.m.samani@gmail.com",
"email_verified":true,
"family_name":"Samani",
"gender":null,
"given_name":"Lakhan",
"id":"466d0b31-1b87-420e-bea5-09d05d79c586",
"middle_name":null,
"nickname":null,
"phone_number":null,
"phone_number_verified":false,
"picture":"https://lh3.googleusercontent.com/a-/AFdZucppvU6a2zIDkX0wvhhapVjT0ZMKDlYCkQDi3NxcUg=s96-c",
"preferred_username":"lakhan.m.samani@gmail.com",
"revoked_timestamp":null,
"roles":[
"user"
],
"signup_methods":"google",
"updated_at":1657526492
},
"auth_recipe":"google"
}`;
export enum EmailTemplateEditors {
UNLAYER_EDITOR = 'unlayer_editor',
PLAIN_HTML_EDITOR = 'plain_html_editor',
}
export const ResponseTypes = {
token: 'token',
code: 'code',
id_token: 'id_token',
};
export const ResponseModes = {
query: 'query',
form_post: 'form_post',
fragment: 'fragment',
web_message: 'web_message',
};

View File

@ -45,3 +45,94 @@ export const DeleteUser = `
} }
} }
`; `;
export const InviteMembers = `
mutation inviteMembers($params: InviteMemberInput!) {
_invite_members(params: $params) {
message
}
}
`;
export const RevokeAccess = `
mutation revokeAccess($param: UpdateAccessInput!) {
_revoke_access(param: $param) {
message
}
}
`;
export const EnableAccess = `
mutation revokeAccess($param: UpdateAccessInput!) {
_enable_access(param: $param) {
message
}
}
`;
export const GenerateKeys = `
mutation generateKeys($params: GenerateJWTKeysInput!) {
_generate_jwt_keys(params: $params) {
secret
public_key
private_key
}
}
`;
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

@ -1,3 +1,12 @@
export const MetaQuery = `
query MetaQuery {
meta {
version
client_id
}
}
`;
export const AdminSessionQuery = ` export const AdminSessionQuery = `
query { query {
_admin_session{ _admin_session{
@ -9,36 +18,71 @@ export const AdminSessionQuery = `
export const EnvVariablesQuery = ` export const EnvVariablesQuery = `
query { query {
_env{ _env{
GOOGLE_CLIENT_ID, CLIENT_ID
GOOGLE_CLIENT_SECRET, CLIENT_SECRET
GITHUB_CLIENT_ID, GOOGLE_CLIENT_ID
GITHUB_CLIENT_SECRET, GOOGLE_CLIENT_SECRET
FACEBOOK_CLIENT_ID, GITHUB_CLIENT_ID
FACEBOOK_CLIENT_SECRET, GITHUB_CLIENT_SECRET
ROLES, FACEBOOK_CLIENT_ID
DEFAULT_ROLES, FACEBOOK_CLIENT_SECRET
PROTECTED_ROLES, LINKEDIN_CLIENT_ID
JWT_TYPE, LINKEDIN_CLIENT_SECRET
JWT_SECRET, APPLE_CLIENT_ID
JWT_ROLE_CLAIM, APPLE_CLIENT_SECRET
REDIS_URL, DISCORD_CLIENT_ID
SMTP_HOST, DISCORD_CLIENT_SECRET
SMTP_PORT, TWITTER_CLIENT_ID
SMTP_USERNAME, TWITTER_CLIENT_SECRET
SMTP_PASSWORD, MICROSOFT_CLIENT_ID
SENDER_EMAIL, MICROSOFT_CLIENT_SECRET
ALLOWED_ORIGINS, MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID
ORGANIZATION_NAME, TWITCH_CLIENT_ID
ORGANIZATION_LOGO, TWITCH_CLIENT_SECRET
ADMIN_SECRET, ROBLOX_CLIENT_ID
DISABLE_LOGIN_PAGE, ROBLOX_CLIENT_SECRET
DISABLE_MAGIC_LINK_LOGIN, DEFAULT_ROLES
DISABLE_EMAIL_VERIFICATION, PROTECTED_ROLES
DISABLE_BASIC_AUTHENTICATION, ROLES
CUSTOM_ACCESS_TOKEN_SCRIPT, JWT_TYPE
DATABASE_NAME, JWT_SECRET
DATABASE_TYPE, JWT_ROLE_CLAIM
DATABASE_URL, JWT_PRIVATE_KEY
JWT_PUBLIC_KEY
REDIS_URL
SMTP_HOST
SMTP_PORT
SMTP_USERNAME
SMTP_PASSWORD
SMTP_LOCAL_NAME
SENDER_EMAIL
SENDER_NAME
ALLOWED_ORIGINS
ORGANIZATION_NAME
ORGANIZATION_LOGO
ADMIN_SECRET
APP_COOKIE_SECURE
ADMIN_COOKIE_SECURE
DISABLE_LOGIN_PAGE
DISABLE_MAGIC_LINK_LOGIN
DISABLE_EMAIL_VERIFICATION
DISABLE_BASIC_AUTHENTICATION
DISABLE_MOBILE_BASIC_AUTHENTICATION
DISABLE_SIGN_UP
DISABLE_STRONG_PASSWORD
DISABLE_REDIS_FOR_ENV
CUSTOM_ACCESS_TOKEN_SCRIPT
DATABASE_NAME
DATABASE_TYPE
DATABASE_URL
ACCESS_TOKEN_EXPIRY_TIME
DISABLE_MULTI_FACTOR_AUTHENTICATION
ENFORCE_MULTI_FACTOR_AUTHENTICATION
DEFAULT_AUTHORIZE_RESPONSE_TYPE
DEFAULT_AUTHORIZE_RESPONSE_MODE
DISABLE_PLAYGROUND
DISABLE_TOTP_LOGIN
DISABLE_MAIL_OTP_LOGIN
} }
} }
`; `;
@ -56,6 +100,7 @@ export const UserDetailsQuery = `
id id
email email
email_verified email_verified
phone_number_verified
given_name given_name
family_name family_name
middle_name middle_name
@ -67,7 +112,88 @@ export const UserDetailsQuery = `
signup_methods signup_methods
roles roles
created_at created_at
revoked_timestamp
is_multi_factor_auth_enabled
} }
} }
} }
`; `;
export const EmailVerificationQuery = `
query {
_env{
DISABLE_EMAIL_VERIFICATION
}
}
`;
export const WebhooksDataQuery = `
query getWebhooksData($params: PaginatedInput!) {
_webhooks(params: $params){
webhooks{
id
event_description
event_name
endpoint
enabled
headers
}
pagination{
limit
page
offset
total
}
}
}
`;
export const EmailTemplatesQuery = `
query getEmailTemplates($params: PaginatedInput!) {
_email_templates(params: $params) {
email_templates {
id
event_name
subject
created_at
template
design
}
pagination {
limit
page
offset
total
}
}
}
`;
export const WebhookLogsQuery = `
query getWebhookLogs($params: ListWebhookLogRequest!) {
_webhook_logs(params: $params) {
webhook_logs {
id
http_status
request
response
created_at
}
pagination {
limit
page
offset
total
}
}
}
`;
export const GetAvailableRolesQuery = `
query {
_env {
ROLES
PROTECTED_ROLES
}
}
`;

View File

@ -2,4 +2,9 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import App from './App'; import App from './App';
ReactDOM.render(<App />, document.getElementById('root')); ReactDOM.render(
<div>
<App />
</div>,
document.getElementById('root'),
);

View File

@ -1,18 +1,28 @@
import { Box, Center, Flex, Image, Text } from '@chakra-ui/react'; import {
Box,
Flex,
Image,
Text,
Spinner,
useMediaQuery,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { LOGO_URL } from '../constants'; import { useQuery } from 'urql';
import { MetaQuery } from '../graphql/queries';
export function AuthLayout({ children }: { children: React.ReactNode }) { export function AuthLayout({ children }: { children: React.ReactNode }) {
const [{ fetching, data }] = useQuery({ query: MetaQuery });
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return ( return (
<Flex <Flex
flexWrap="wrap" h="100vh"
h="100%"
bg="gray.100" bg="gray.100"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
flexDirection="column" direction={['column', 'column']}
padding={['2%', '2%', '2%', '2%']}
> >
<Flex alignItems="center"> <Flex alignItems="center" maxW="100%">
<Image <Image
src="https://authorizer.dev/images/logo.png" src="https://authorizer.dev/images/logo.png"
alt="logo" alt="logo"
@ -23,9 +33,26 @@ export function AuthLayout({ children }: { children: React.ReactNode }) {
</Text> </Text>
</Flex> </Flex>
<Box p="6" m="5" rounded="5" bg="white" w="500px" shadow="xl"> {fetching ? (
{children} <Spinner />
</Box> ) : (
<>
<Box
p="6"
m="5"
rounded="5"
bg="white"
w={isNotSmallerScreen ? '500px' : '450px'}
shadow="xl"
maxW="100%"
>
{children}
</Box>
<Text color="gray.600" fontSize="sm">
Current Version: {data.meta.version}
</Text>
</>
)}
</Flex> </Flex>
); );
} }

View File

@ -31,7 +31,7 @@ export function DashboardLayout({ children }: { children: ReactNode }) {
</Drawer> </Drawer>
{/* mobilenav */} {/* mobilenav */}
<MobileNav onOpen={onOpen} /> <MobileNav onOpen={onOpen} />
<Box ml={{ base: 0, md: 60 }} p="4" pt="24"> <Box ml={{ base: 0, md: '64' }} p="4" pt="24">
{children} {children}
</Box> </Box>
</Box> </Box>

View File

@ -6,7 +6,6 @@ import {
useToast, useToast,
VStack, VStack,
Text, Text,
Divider,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useMutation } from 'urql'; import { useMutation } from 'urql';
@ -58,7 +57,7 @@ export default function Auth() {
title: capitalizeFirstLetter(error.message), title: capitalizeFirstLetter(error.message),
isClosable: true, isClosable: true,
status: 'error', status: 'error',
position: 'bottom-right', position: 'top-right',
}); });
}); });
} }
@ -102,10 +101,10 @@ export default function Auth() {
</FormControl> </FormControl>
<Button <Button
isLoading={signUpResult.fetching || loginResult.fetching} isLoading={signUpResult.fetching || loginResult.fetching}
loadingText="Submitting"
colorScheme="blue" colorScheme="blue"
size="lg" size="lg"
w="100%" w="100%"
d="block"
type="submit" type="submit"
> >
{isLogin ? 'Login' : 'Sign up'} {isLogin ? 'Login' : 'Sign up'}

View File

@ -0,0 +1,348 @@
import React, { useEffect, useState } from 'react';
import { useClient } from 'urql';
import {
Box,
Button,
Center,
Flex,
IconButton,
Menu,
MenuButton,
MenuList,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Select,
Spinner,
Table,
TableCaption,
Tbody,
Td,
Text,
Th,
Thead,
Tooltip,
Tr,
} from '@chakra-ui/react';
import {
FaAngleDoubleLeft,
FaAngleDoubleRight,
FaAngleDown,
FaAngleLeft,
FaAngleRight,
FaExclamationCircle,
} from 'react-icons/fa';
import UpdateEmailTemplateModal from '../components/UpdateEmailTemplateModal';
import {
pageLimits,
UpdateModalViews,
EmailTemplateInputDataFields,
} from '../constants';
import { EmailTemplatesQuery } from '../graphql/queries';
import dayjs from 'dayjs';
import DeleteEmailTemplateModal from '../components/DeleteEmailTemplateModal';
interface paginationPropTypes {
limit: number;
page: number;
offset: number;
total: number;
maxPages: number;
}
interface EmailTemplateDataType {
[EmailTemplateInputDataFields.ID]: string;
[EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string;
[EmailTemplateInputDataFields.CREATED_AT]: number;
[EmailTemplateInputDataFields.TEMPLATE]: string;
[EmailTemplateInputDataFields.DESIGN]: string;
}
const EmailTemplates = () => {
const client = useClient();
const [loading, setLoading] = useState<boolean>(false);
const [emailTemplatesData, setEmailTemplatesData] = useState<
EmailTemplateDataType[]
>([]);
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
limit: 5,
page: 1,
offset: 0,
total: 0,
maxPages: 1,
});
const getMaxPages = (pagination: paginationPropTypes) => {
const { limit, total } = pagination;
if (total > 1) {
return total % limit === 0
? total / limit
: parseInt(`${total / limit}`) + 1;
} else return 1;
};
const fetchEmailTemplatesData = async () => {
setLoading(true);
const res = await client
.query(EmailTemplatesQuery, {
params: {
pagination: {
limit: paginationProps.limit,
page: paginationProps.page,
},
},
})
.toPromise();
if (res.data?._email_templates) {
const { pagination, email_templates: emailTemplates } =
res.data?._email_templates;
const maxPages = getMaxPages(pagination);
if (emailTemplates?.length) {
setEmailTemplatesData(emailTemplates);
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
} else {
if (paginationProps.page !== 1) {
setPaginationProps({
...paginationProps,
...pagination,
maxPages,
page: 1,
});
}
}
}
setLoading(false);
};
const paginationHandler = (value: Record<string, number>) => {
setPaginationProps({ ...paginationProps, ...value });
};
useEffect(() => {
fetchEmailTemplatesData();
}, [paginationProps.page, paginationProps.limit]);
return (
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
<Text fontSize="md" fontWeight="bold">
Email Templates
</Text>
<UpdateEmailTemplateModal
view={UpdateModalViews.ADD}
fetchEmailTemplatesData={fetchEmailTemplatesData}
/>
</Flex>
{!loading ? (
emailTemplatesData.length ? (
<Table variant="simple">
<Thead>
<Tr>
<Th>Event Name</Th>
<Th>Subject</Th>
<Th>Created At</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{emailTemplatesData.map((templateData: EmailTemplateDataType) => (
<Tr
key={templateData[EmailTemplateInputDataFields.ID]}
style={{ fontSize: 14 }}
>
<Td maxW="300">
{templateData[EmailTemplateInputDataFields.EVENT_NAME]}
</Td>
<Td>{templateData[EmailTemplateInputDataFields.SUBJECT]}</Td>
<Td>
{dayjs(templateData.created_at * 1000).format(
'MMM DD, YYYY',
)}
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
>
<Text fontSize="sm" fontWeight="light">
Menu
</Text>
<FaAngleDown style={{ marginLeft: 10 }} />
</Flex>
</MenuButton>
<MenuList>
<UpdateEmailTemplateModal
view={UpdateModalViews.Edit}
selectedTemplate={templateData}
fetchEmailTemplatesData={fetchEmailTemplatesData}
/>
<DeleteEmailTemplateModal
emailTemplateId={
templateData[EmailTemplateInputDataFields.ID]
}
eventName={
templateData[
EmailTemplateInputDataFields.EVENT_NAME
]
}
fetchEmailTemplatesData={fetchEmailTemplatesData}
/>
</MenuList>
</Menu>
</Td>
</Tr>
))}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: 1,
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
>
{pageLimits.map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
</Flex>
</TableCaption>
)}
</Table>
) : (
<Flex
flexDirection="column"
minH="25vh"
justifyContent="center"
alignItems="center"
>
<Center w="50px" marginRight="1.5%">
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
</Center>
<Text
fontSize="2xl"
paddingRight="1%"
fontWeight="bold"
color="#d9d9d9"
>
No Data
</Text>
</Flex>
)
) : (
<Center minH="25vh">
<Spinner />
</Center>
)}
</Box>
);
};
export default EmailTemplates;

View File

@ -1,75 +1,35 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { import { useParams } from 'react-router-dom';
Box, import { Box, Flex, Stack, Button, useToast } from '@chakra-ui/react';
Divider,
Flex,
Stack,
Center,
Text,
Button,
Input,
InputGroup,
InputRightElement,
useToast,
} from '@chakra-ui/react';
import { useClient } from 'urql'; import { useClient } from 'urql';
import { import { FaSave } from 'react-icons/fa';
FaGoogle,
FaGithub,
FaFacebookF,
FaSave,
FaRegEyeSlash,
FaRegEye,
} from 'react-icons/fa';
import _ from 'lodash'; import _ from 'lodash';
import InputField from '../components/InputField';
import { EnvVariablesQuery } from '../graphql/queries'; import { EnvVariablesQuery } from '../graphql/queries';
import { import {
ArrayInputType,
SelectInputType, SelectInputType,
HiddenInputType, HiddenInputType,
TextInputType, TextInputType,
TextAreaInputType, HMACEncryptionType,
SwitchInputType, RSAEncryptionType,
ECDSAEncryptionType,
envVarTypes,
envSubViews,
} from '../constants'; } from '../constants';
import { UpdateEnvVariables } from '../graphql/mutation'; import { UpdateEnvVariables } from '../graphql/mutation';
import { getObjectDiff, capitalizeFirstLetter } from '../utils'; import { getObjectDiff, capitalizeFirstLetter } from '../utils';
import OAuthConfig from '../components/EnvComponents/OAuthConfig';
import Roles from '../components/EnvComponents/Roles';
import JWTConfigurations from '../components/EnvComponents/JWTConfiguration';
import SessionStorage from '../components/EnvComponents/SessionStorage';
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 Features from '../components/EnvComponents/Features';
import SecurityAdminSecret from '../components/EnvComponents/SecurityAdminSecret';
import DatabaseCredentials from '../components/EnvComponents/DatabaseCredentials';
interface envVarTypes { const Environment = () => {
GOOGLE_CLIENT_ID: string;
GOOGLE_CLIENT_SECRET: string;
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET: string;
FACEBOOK_CLIENT_ID: string;
FACEBOOK_CLIENT_SECRET: string;
ROLES: [string] | [];
DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | [];
JWT_TYPE: string;
JWT_SECRET: string;
JWT_ROLE_CLAIM: string;
REDIS_URL: string;
SMTP_HOST: string;
SMTP_PORT: string;
SMTP_USERNAME: string;
SMTP_PASSWORD: string;
SENDER_EMAIL: string;
ALLOWED_ORIGINS: [string] | [];
ORGANIZATION_NAME: string;
ORGANIZATION_LOGO: string;
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
ADMIN_SECRET: string;
DISABLE_LOGIN_PAGE: boolean;
DISABLE_MAGIC_LINK_LOGIN: boolean;
DISABLE_EMAIL_VERIFICATION: boolean;
DISABLE_BASIC_AUTHENTICATION: boolean;
OLD_ADMIN_SECRET: string;
DATABASE_NAME: string;
DATABASE_TYPE: string;
DATABASE_URL: string;
}
export default function Environment() {
const client = useClient(); const client = useClient();
const toast = useToast(); const toast = useToast();
const [adminSecret, setAdminSecret] = React.useState< const [adminSecret, setAdminSecret] = React.useState<
@ -86,31 +46,63 @@ export default function Environment() {
GITHUB_CLIENT_SECRET: '', GITHUB_CLIENT_SECRET: '',
FACEBOOK_CLIENT_ID: '', FACEBOOK_CLIENT_ID: '',
FACEBOOK_CLIENT_SECRET: '', FACEBOOK_CLIENT_SECRET: '',
LINKEDIN_CLIENT_ID: '',
LINKEDIN_CLIENT_SECRET: '',
APPLE_CLIENT_ID: '',
APPLE_CLIENT_SECRET: '',
DISCORD_CLIENT_ID: '',
DISCORD_CLIENT_SECRET: '',
TWITTER_CLIENT_ID: '',
TWITTER_CLIENT_SECRET: '',
MICROSOFT_CLIENT_ID: '',
MICROSOFT_CLIENT_SECRET: '',
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: '',
TWITCH_CLIENT_ID: '',
TWITCH_CLIENT_SECRET: '',
ROBLOX_CLIENT_ID: '',
ROBLOX_CLIENT_SECRET: '',
ROLES: [], ROLES: [],
DEFAULT_ROLES: [], DEFAULT_ROLES: [],
PROTECTED_ROLES: [], PROTECTED_ROLES: [],
JWT_TYPE: '', JWT_TYPE: '',
JWT_SECRET: '', JWT_SECRET: '',
JWT_ROLE_CLAIM: '', JWT_ROLE_CLAIM: '',
JWT_PRIVATE_KEY: '',
JWT_PUBLIC_KEY: '',
REDIS_URL: '', REDIS_URL: '',
SMTP_HOST: '', SMTP_HOST: '',
SMTP_PORT: '', SMTP_PORT: '',
SMTP_USERNAME: '', SMTP_USERNAME: '',
SMTP_PASSWORD: '', SMTP_PASSWORD: '',
SMTP_LOCAL_NAME: '',
SENDER_EMAIL: '', SENDER_EMAIL: '',
SENDER_NAME: '',
ALLOWED_ORIGINS: [], ALLOWED_ORIGINS: [],
ORGANIZATION_NAME: '', ORGANIZATION_NAME: '',
ORGANIZATION_LOGO: '', ORGANIZATION_LOGO: '',
CUSTOM_ACCESS_TOKEN_SCRIPT: '', CUSTOM_ACCESS_TOKEN_SCRIPT: '',
ADMIN_SECRET: '', ADMIN_SECRET: '',
APP_COOKIE_SECURE: false,
ADMIN_COOKIE_SECURE: false,
DISABLE_LOGIN_PAGE: false, DISABLE_LOGIN_PAGE: false,
DISABLE_MAGIC_LINK_LOGIN: false, DISABLE_MAGIC_LINK_LOGIN: false,
DISABLE_EMAIL_VERIFICATION: false, DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false, DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_MOBILE_BASIC_AUTHENTICATION: false,
DISABLE_SIGN_UP: false,
DISABLE_STRONG_PASSWORD: false,
OLD_ADMIN_SECRET: '', OLD_ADMIN_SECRET: '',
DATABASE_NAME: '', DATABASE_NAME: '',
DATABASE_TYPE: '', DATABASE_TYPE: '',
DATABASE_URL: '', DATABASE_URL: '',
ACCESS_TOKEN_EXPIRY_TIME: '',
DISABLE_MULTI_FACTOR_AUTHENTICATION: false,
ENFORCE_MULTI_FACTOR_AUTHENTICATION: false,
DEFAULT_AUTHORIZE_RESPONSE_TYPE: '',
DEFAULT_AUTHORIZE_RESPONSE_MODE: '',
DISABLE_PLAYGROUND: false,
DISABLE_TOTP_LOGIN: false,
DISABLE_MAIL_OTP_LOGIN: true,
}); });
const [fieldVisibility, setFieldVisibility] = React.useState< const [fieldVisibility, setFieldVisibility] = React.useState<
@ -119,39 +111,39 @@ export default function Environment() {
GOOGLE_CLIENT_SECRET: false, GOOGLE_CLIENT_SECRET: false,
GITHUB_CLIENT_SECRET: false, GITHUB_CLIENT_SECRET: false,
FACEBOOK_CLIENT_SECRET: false, FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false,
DISCORD_CLIENT_SECRET: false,
TWITTER_CLIENT_SECRET: false,
TWITCH_CLIENT_SECRET: false,
JWT_SECRET: false, JWT_SECRET: false,
SMTP_PASSWORD: false, SMTP_PASSWORD: false,
ADMIN_SECRET: false, ADMIN_SECRET: false,
OLD_ADMIN_SECRET: false, OLD_ADMIN_SECRET: false,
}); });
const { sec } = useParams();
async function getData() {
const {
data: { _env: envData },
} = await client.query(EnvVariablesQuery).toPromise();
setLoading(false);
setEnvVariables({
...envData,
OLD_ADMIN_SECRET: envData.ADMIN_SECRET,
ADMIN_SECRET: '',
});
setAdminSecret({
value: '',
disableInputField: true,
});
}
useEffect(() => { useEffect(() => {
let isMounted = true;
async function getData() {
const {
data: { _env: envData },
} = await client.query(EnvVariablesQuery).toPromise();
if (isMounted) {
setLoading(false);
setEnvVariables({
...envData,
OLD_ADMIN_SECRET: envData.ADMIN_SECRET,
ADMIN_SECRET: '',
});
setAdminSecret({
value: '',
disableInputField: true,
});
}
}
getData(); getData();
}, [sec]);
return () => {
isMounted = false;
};
}, []);
const validateAdminSecretHandler = (event: any) => { const validateAdminSecretHandler = (event: any) => {
if (envVariables.OLD_ADMIN_SECRET === event.target.value) { if (envVariables.OLD_ADMIN_SECRET === event.target.value) {
@ -177,7 +169,6 @@ export default function Environment() {
const { const {
data: { _env: envData }, data: { _env: envData },
} = await client.query(EnvVariablesQuery).toPromise(); } = await client.query(EnvVariablesQuery).toPromise();
const diff = getObjectDiff(envVariables, envData); const diff = getObjectDiff(envVariables, envData);
const updatedEnvVariables = diff.reduce( const updatedEnvVariables = diff.reduce(
(acc: any, property: string) => ({ (acc: any, property: string) => ({
@ -185,7 +176,7 @@ export default function Environment() {
// @ts-ignore // @ts-ignore
[property]: envVariables[property], [property]: envVariables[property],
}), }),
{} {},
); );
if ( if (
updatedEnvVariables[HiddenInputType.ADMIN_SECRET] === '' || updatedEnvVariables[HiddenInputType.ADMIN_SECRET] === '' ||
@ -222,540 +213,122 @@ export default function Environment() {
disableInputField: true, disableInputField: true,
}); });
getData();
toast({ toast({
title: `Successfully updated ${ title: `Successfully updated ${
Object.keys(updatedEnvVariables).length Object.keys(updatedEnvVariables).length
} variables`, } variables`,
isClosable: true, isClosable: true,
status: 'success', status: 'success',
position: 'bottom-right', position: 'top-right',
}); });
}; };
const renderComponent = (tab: any) => {
switch (tab) {
case envSubViews.INSTANCE_INFO:
return (
<OAuthConfig
envVariables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
/>
);
case envSubViews.ROLES:
return (
<Roles variables={envVariables} setVariables={setEnvVariables} />
);
case envSubViews.JWT_CONFIG:
return (
<JWTConfigurations
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
SelectInputType={SelectInputType.JWT_TYPE}
HMACEncryptionType={HMACEncryptionType}
RSAEncryptionType={RSAEncryptionType}
ECDSAEncryptionType={ECDSAEncryptionType}
getData={getData}
/>
);
case envSubViews.SESSION_STORAGE:
return (
<SessionStorage
variables={envVariables}
setVariables={setEnvVariables}
RedisURL={TextInputType.REDIS_URL}
/>
);
case envSubViews.EMAIL_CONFIG:
return (
<EmailConfigurations
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
/>
);
case envSubViews.WHITELIST_VARIABLES:
return (
<DomainWhiteListing
variables={envVariables}
setVariables={setEnvVariables}
/>
);
case envSubViews.ORGANIZATION_INFO:
return (
<OrganizationInfo
variables={envVariables}
setVariables={setEnvVariables}
/>
);
case envSubViews.ACCESS_TOKEN:
return (
<AccessToken
variables={envVariables}
setVariables={setEnvVariables}
/>
);
case envSubViews.FEATURES:
return (
<Features variables={envVariables} setVariables={setEnvVariables} />
);
case envSubViews.ADMIN_SECRET:
return (
<SecurityAdminSecret
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
validateAdminSecretHandler={validateAdminSecretHandler}
adminSecret={adminSecret}
/>
);
case envSubViews.DB_CRED:
return (
<DatabaseCredentials
variables={envVariables}
setVariables={setEnvVariables}
/>
);
default:
return (
<OAuthConfig
envVariables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
/>
);
}
};
return ( return (
<Box m="5" py="5" px="10" bg="white" rounded="md"> <Box m="5" py="5" px="10" bg="white" rounded="md">
<Text fontSize="md" paddingTop="2%" fontWeight="bold"> {renderComponent(sec)}
Social Media Logins <Stack spacing={6} padding="1% 0" mt={4}>
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Center
w="50px"
marginRight="1.5%"
border="1px solid #e2e8f0"
borderRadius="5px"
>
<FaGoogle style={{ color: '#8c8c8c' }} />
</Center>
<Center w="45%" marginRight="1.5%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.GOOGLE_CLIENT_ID}
placeholder="Google Client ID"
/>
</Center>
<Center w="45%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.GOOGLE_CLIENT_SECRET}
placeholder="Google Secret"
/>
</Center>
</Flex>
<Flex>
<Center
w="50px"
marginRight="1.5%"
border="1px solid #e2e8f0"
borderRadius="5px"
>
<FaGithub style={{ color: '#8c8c8c' }} />
</Center>
<Center w="45%" marginRight="1.5%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.GITHUB_CLIENT_ID}
placeholder="Github Client ID"
/>
</Center>
<Center w="45%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.GITHUB_CLIENT_SECRET}
placeholder="Github Secret"
/>
</Center>
</Flex>
<Flex>
<Center
w="50px"
marginRight="1.5%"
border="1px solid #e2e8f0"
borderRadius="5px"
>
<FaFacebookF style={{ color: '#8c8c8c' }} />
</Center>
<Center w="45%" marginRight="1.5%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.FACEBOOK_CLIENT_ID}
placeholder="Facebook Client ID"
/>
</Center>
<Center w="45%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.FACEBOOK_CLIENT_SECRET}
placeholder="Facebook Secret"
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Roles
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Roles:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={ArrayInputType.ROLES}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Default Roles:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={ArrayInputType.DEFAULT_ROLES}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Protected Roles:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={ArrayInputType.PROTECTED_ROLES}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
JWT (JSON Web Tokens) Configurations
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Type:</Text>
</Flex>
<Center w="70%">
<Flex w="100%" justifyContent="space-between">
<Flex flex="2">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={SelectInputType.JWT_TYPE}
isDisabled={true}
defaultValue={SelectInputType.JWT_TYPE}
/>
</Flex>
<Flex flex="3" justifyContent="center" alignItems="center">
<Text fontSize="sm">
More JWT types will be enabled in upcoming releases.
</Text>
</Flex>
</Flex>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Secret</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.JWT_SECRET}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Role Claim:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.JWT_ROLE_CLAIM}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Session Storage
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Redis URL:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.REDIS_URL}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Email Configurations
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Host:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.SMTP_HOST}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Port:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.SMTP_PORT}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Username:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.SMTP_USERNAME}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Password:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.SMTP_PASSWORD}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">From Email:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.SENDER_EMAIL}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
White Listing
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Allowed Origins:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={ArrayInputType.ALLOWED_ORIGINS}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Organization Information
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Organization Name:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.ORGANIZATION_NAME}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Organization Logo:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.ORGANIZATION_LOGO}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Custom Access Token Scripts
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Center w="100%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
placeholder="Add script here"
minH="25vh"
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Disable Features
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Login Page:</Text>
</Flex>
<Flex justifyContent="start" w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={SwitchInputType.DISABLE_LOGIN_PAGE}
/>
</Flex>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Email Verification:</Text>
</Flex>
<Flex justifyContent="start" w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={SwitchInputType.DISABLE_EMAIL_VERIFICATION}
/>
</Flex>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Magic Login Link:</Text>
</Flex>
<Flex justifyContent="start" w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={SwitchInputType.DISABLE_MAGIC_LINK_LOGIN}
/>
</Flex>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Basic Authentication:</Text>
</Flex>
<Flex justifyContent="start" w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={SwitchInputType.DISABLE_BASIC_AUTHENTICATION}
/>
</Flex>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Danger
</Text>
<Stack
spacing={6}
padding="0 5%"
marginTop="3%"
border="1px solid #ff7875"
borderRadius="5px"
>
<Stack spacing={6} padding="3% 0">
<Text fontStyle="italic" fontSize="sm" color="gray.600">
Note: Database related environment variables cannot be updated from
dashboard :(
</Text>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">DataBase Name:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.DATABASE_NAME}
isDisabled={true}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">DataBase Type:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.DATABASE_TYPE}
isDisabled={true}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">DataBase URL:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.DATABASE_URL}
isDisabled={true}
/>
</Center>
</Flex>
</Stack>
<Flex marginTop="3%">
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Old Admin Secret:</Text>
</Flex>
<Center w="70%">
<InputGroup size="sm">
<Input
size="sm"
placeholder="Enter Old Admin Secret"
value={adminSecret.value as string}
onChange={(event: any) => validateAdminSecretHandler(event)}
type={
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
? 'password'
: 'text'
}
/>
<InputRightElement
right="5px"
children={
<Flex>
{fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET] ? (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: false,
})
}
>
<FaRegEyeSlash color="#bfbfbf" />
</Center>
) : (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: true,
})
}
>
<FaRegEye color="#bfbfbf" />
</Center>
)}
</Flex>
}
/>
</InputGroup>
</Center>
</Flex>
<Flex paddingBottom="3%">
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">New Admin Secret:</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={HiddenInputType.ADMIN_SECRET}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
isDisabled={adminSecret.disableInputField}
placeholder="Enter New Admin Secret"
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="5%" marginBottom="2%" />
<Stack spacing={6} padding="1% 0">
<Flex justifyContent="end" alignItems="center"> <Flex justifyContent="end" alignItems="center">
<Button <Button
leftIcon={<FaSave />} leftIcon={<FaSave />}
@ -770,4 +343,6 @@ export default function Environment() {
</Stack> </Stack>
</Box> </Box>
); );
} };
export default Environment;

View File

@ -29,6 +29,7 @@ import {
MenuItem, MenuItem,
useToast, useToast,
Spinner, Spinner,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import {
FaAngleLeft, FaAngleLeft,
@ -38,10 +39,11 @@ import {
FaExclamationCircle, FaExclamationCircle,
FaAngleDown, FaAngleDown,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { UserDetailsQuery } from '../graphql/queries'; import { EmailVerificationQuery, UserDetailsQuery } from '../graphql/queries';
import { UpdateUser } from '../graphql/mutation'; import { EnableAccess, RevokeAccess, UpdateUser } from '../graphql/mutation';
import EditUserModal from '../components/EditUserModal'; import EditUserModal from '../components/EditUserModal';
import DeleteUserModal from '../components/DeleteUserModal'; import DeleteUserModal from '../components/DeleteUserModal';
import InviteMembersModal from '../components/InviteMembersModal';
interface paginationPropTypes { interface paginationPropTypes {
limit: number; limit: number;
@ -66,6 +68,13 @@ interface userDataTypes {
signup_methods: string; signup_methods: string;
roles: [string]; roles: [string];
created_at: number; created_at: number;
revoked_timestamp: number;
is_multi_factor_auth_enabled?: boolean;
}
const enum updateAccessActions {
REVOKE = 'REVOKE',
ENABLE = 'ENABLE',
} }
const getMaxPages = (pagination: paginationPropTypes) => { const getMaxPages = (pagination: paginationPropTypes) => {
@ -101,6 +110,8 @@ export default function Users() {
}); });
const [userList, setUserList] = React.useState<userDataTypes[]>([]); const [userList, setUserList] = React.useState<userDataTypes[]>([]);
const [loading, setLoading] = React.useState<boolean>(false); const [loading, setLoading] = React.useState<boolean>(false);
const [disableInviteMembers, setDisableInviteMembers] =
React.useState<boolean>(true);
const updateUserList = async () => { const updateUserList = async () => {
setLoading(true); setLoading(true);
const { data } = await client const { data } = await client
@ -132,8 +143,18 @@ export default function Users() {
} }
setLoading(false); setLoading(false);
}; };
const checkEmailVerification = async () => {
setLoading(true);
const { data } = await client.query(EmailVerificationQuery).toPromise();
if (data?._env) {
const { DISABLE_EMAIL_VERIFICATION } = data._env;
setDisableInviteMembers(DISABLE_EMAIL_VERIFICATION);
}
setLoading(false);
};
React.useEffect(() => { React.useEffect(() => {
updateUserList(); updateUserList();
checkEmailVerification();
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
updateUserList(); updateUserList();
@ -144,14 +165,25 @@ export default function Users() {
}; };
const userVerificationHandler = async (user: userDataTypes) => { const userVerificationHandler = async (user: userDataTypes) => {
const { id, email } = user; const { id, email, phone_number } = user;
let params = {};
if (email) {
params = {
id,
email,
email_verified: true,
};
}
if (phone_number) {
params = {
id,
phone_number,
phone_number_verified: true,
};
}
const res = await client const res = await client
.mutation(UpdateUser, { .mutation(UpdateUser, {
params: { params,
id,
email,
email_verified: true,
},
}) })
.toPromise(); .toPromise();
if (res.error) { if (res.error) {
@ -159,217 +191,392 @@ export default function Users() {
title: 'User verification failed', title: 'User verification failed',
isClosable: true, isClosable: true,
status: 'error', status: 'error',
position: 'bottom-right', position: 'top-right',
}); });
} else if (res.data?._update_user?.id) { } else if (res.data?._update_user?.id) {
toast({ toast({
title: 'User verification successful', title: 'User verification successful',
isClosable: true, isClosable: true,
status: 'success', status: 'success',
position: 'bottom-right', position: 'top-right',
}); });
} }
updateUserList(); updateUserList();
}; };
const updateAccessHandler = async (
id: string,
action: updateAccessActions,
) => {
switch (action) {
case updateAccessActions.ENABLE:
const enableAccessRes = await client
.mutation(EnableAccess, {
param: {
user_id: id,
},
})
.toPromise();
if (enableAccessRes.error) {
toast({
title: 'User access enable failed',
isClosable: true,
status: 'error',
position: 'top-right',
});
} else {
toast({
title: 'User access enabled successfully',
isClosable: true,
status: 'success',
position: 'top-right',
});
}
updateUserList();
break;
case updateAccessActions.REVOKE:
const revokeAccessRes = await client
.mutation(RevokeAccess, {
param: {
user_id: id,
},
})
.toPromise();
if (revokeAccessRes.error) {
toast({
title: 'User access revoke failed',
isClosable: true,
status: 'error',
position: 'top-right',
});
} else {
toast({
title: 'User access revoked successfully',
isClosable: true,
status: 'success',
position: 'top-right',
});
}
updateUserList();
break;
default:
break;
}
};
const multiFactorAuthUpdateHandler = async (user: userDataTypes) => {
const res = await client
.mutation(UpdateUser, {
params: {
id: user.id,
is_multi_factor_auth_enabled: !user.is_multi_factor_auth_enabled,
},
})
.toPromise();
if (res.data?._update_user?.id) {
toast({
title: `Multi factor authentication ${
user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled'
} for user`,
isClosable: true,
status: 'success',
position: 'top-right',
});
updateUserList();
return;
}
toast({
title: 'Multi factor authentication update failed for user',
isClosable: true,
status: 'error',
position: 'top-right',
});
};
return ( return (
<Box m="5" py="5" px="10" bg="white" rounded="md"> <Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center"> <Flex margin="2% 0" justifyContent="space-between" alignItems="center">
<Text fontSize="md" fontWeight="bold"> <Text fontSize="md" fontWeight="bold">
Users Users
</Text> </Text>
<InviteMembersModal
disabled={disableInviteMembers}
updateUserList={updateUserList}
/>
</Flex> </Flex>
{!loading ? ( {!loading ? (
userList.length > 0 ? ( userList.length > 0 ? (
<Table variant="simple"> <TableContainer>
<Thead> <Table variant="simple">
<Tr> <Thead>
<Th>Email</Th> <Tr>
<Th>Created At</Th> <Th>Email / Phone</Th>
<Th>Signup Methods</Th> <Th>Created At</Th>
<Th>Roles</Th> <Th>Signup Methods</Th>
<Th>Verified</Th> <Th>Roles</Th>
<Th>Actions</Th> <Th>Verified</Th>
</Tr> <Th>Access</Th>
</Thead> <Th>
<Tbody> <Tooltip label="MultiFactor Authentication Enabled / Disabled">
{userList.map((user: userDataTypes) => { MFA
const { email_verified, created_at, ...rest }: any = user; </Tooltip>
return ( </Th>
<Tr key={user.id} style={{ fontSize: 14 }}> <Th>Actions</Th>
<Td>{user.email}</Td> </Tr>
<Td> </Thead>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')} <Tbody>
</Td> {userList.map((user: userDataTypes) => {
<Td>{user.signup_methods}</Td> const {
<Td>{user.roles.join(', ')}</Td> email_verified,
<Td> phone_number_verified,
<Tag created_at,
size="sm" ...rest
variant="outline" }: any = user;
colorScheme={user.email_verified ? 'green' : 'yellow'} return (
> <Tr key={user.id} style={{ fontSize: 14 }}>
{user.email_verified.toString()} <Td maxW="300">{user.email || user.phone_number}</Td>
</Tag> <Td>
</Td> {dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
<Td> </Td>
<Menu> <Td>{user.signup_methods}</Td>
<MenuButton as={Button} variant="unstyled" size="sm"> <Td>{user.roles.join(', ')}</Td>
<Flex <Td>
justifyContent="space-between" <Tag
alignItems="center" size="sm"
> variant="outline"
<Text fontSize="sm" fontWeight="light"> colorScheme={
Menu user.email_verified || user.phone_number_verified
</Text> ? 'green'
<FaAngleDown style={{ marginLeft: 10 }} /> : 'yellow'
</Flex> }
</MenuButton> >
<MenuList> {(
{!user.email_verified && ( user.email_verified || user.phone_number_verified
<MenuItem ).toString()}
onClick={() => userVerificationHandler(user)} </Tag>
</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
>
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
</Tag>
</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={
user.is_multi_factor_auth_enabled ? 'green' : 'red'
}
>
{user.is_multi_factor_auth_enabled
? 'Enabled'
: 'Disabled'}
</Tag>
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
> >
Verify User <Text fontSize="sm" fontWeight="light">
</MenuItem> Menu
)} </Text>
<EditUserModal <FaAngleDown style={{ marginLeft: 10 }} />
user={rest} </Flex>
updateUserList={updateUserList} </MenuButton>
/> <MenuList>
<DeleteUserModal {!user.email_verified &&
user={rest} !user.phone_number_verified && (
updateUserList={updateUserList} <MenuItem
/> onClick={() => userVerificationHandler(user)}
</MenuList> >
</Menu> Verify User
</Td> </MenuItem>
</Tr> )}
); <EditUserModal
})} user={rest}
</Tbody> updateUserList={updateUserList}
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && ( />
<TableCaption> <DeleteUserModal
<Flex user={rest}
justifyContent="space-between" updateUserList={updateUserList}
alignItems="center" />
m="2% 0" {user.revoked_timestamp ? (
> <MenuItem
<Flex flex="1"> onClick={() =>
<Tooltip label="First Page"> updateAccessHandler(
<IconButton user.id,
aria-label="icon button" updateAccessActions.ENABLE,
onClick={() => )
}
>
Enable Access
</MenuItem>
) : (
<MenuItem
onClick={() =>
updateAccessHandler(
user.id,
updateAccessActions.REVOKE,
)
}
>
Revoke Access
</MenuItem>
)}
{user.is_multi_factor_auth_enabled ? (
<MenuItem
onClick={() =>
multiFactorAuthUpdateHandler(user)
}
>
Disable MultiFactor Authentication
</MenuItem>
) : (
<MenuItem
onClick={() =>
multiFactorAuthUpdateHandler(user)
}
>
Enable MultiFactor Authentication
</MenuItem>
)}
</MenuList>
</Menu>
</Td>
</Tr>
);
})}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: 1,
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({ paginationHandler({
page: 1, page: 1,
limit: parseInt(e.target.value),
}) })
} }
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
> >
<NumberInputField /> {getLimits(paginationProps).map((pageSize) => (
<NumberInputStepper> <option key={pageSize} value={pageSize}>
<NumberIncrementStepper /> Show {pageSize}
<NumberDecrementStepper /> </option>
</NumberInputStepper> ))}
</NumberInput> </Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex> </Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
>
{getLimits(paginationProps).map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex> </Flex>
<Flex flex="1"> </TableCaption>
<Tooltip label="Next Page"> )}
<IconButton </Table>
aria-label="icon button" </TableContainer>
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
</Flex>
</TableCaption>
)}
</Table>
) : ( ) : (
<Flex <Flex
flexDirection="column" flexDirection="column"

View File

@ -0,0 +1,374 @@
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.EVENT_DESCRIPTION]?: 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>Event Description</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].split('-')[0]}
</Td>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
</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>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,33 +3,41 @@ import { Outlet, Route, Routes } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext'; import { useAuthContext } from '../contexts/AuthContext';
import { DashboardLayout } from '../layouts/DashboardLayout'; import { DashboardLayout } from '../layouts/DashboardLayout';
import EmailTemplates from '../pages/EmailTemplates';
const Auth = lazy(() => import('../pages/Auth')); const Auth = lazy(() => import('../pages/Auth'));
const Environment = lazy(() => import('../pages/Environment')); const Environment = lazy(() => import('../pages/Environment'));
const Home = lazy(() => import('../pages/Home')); const Home = lazy(() => import('../pages/Home'));
const Users = lazy(() => import('../pages/Users')); const Users = lazy(() => import('../pages/Users'));
const Webhooks = lazy(() => import('../pages/Webhooks'));
export const AppRoutes = () => { export const AppRoutes = () => {
const { isLoggedIn } = useAuthContext(); const { isLoggedIn } = useAuthContext();
if (isLoggedIn) { if (isLoggedIn) {
return ( return (
<Suspense fallback={<></>}> <div>
<Routes> <Suspense fallback={<></>}>
<Route <Routes>
element={ <Route
<DashboardLayout> element={
<Outlet /> <DashboardLayout>
</DashboardLayout> <Outlet />
} </DashboardLayout>
> }
<Route path="/" element={<Environment />} /> >
<Route path="users" element={<Users />} /> <Route path="/" element={<Outlet />}>
<Route path="environment" element={<Environment />} /> <Route index element={<Environment />} />
<Route path="*" element={<Home />} /> <Route path="/:sec" element={<Environment />} />
</Route> </Route>
</Routes> <Route path="users" element={<Users />} />
</Suspense> <Route path="webhooks" element={<Webhooks />} />
<Route path="email-templates" element={<EmailTemplates />} />
<Route path="*" element={<Home />} />
</Route>
</Routes>
</Suspense>
</div>
); );
} }
return ( return (

View File

@ -29,19 +29,16 @@ const fallbackCopyTextToClipboard = (text: string) => {
document.body.removeChild(textArea); document.body.removeChild(textArea);
}; };
export const copyTextToClipboard = (text: string) => { export const copyTextToClipboard = async (text: string) => {
if (!navigator.clipboard) { if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text); fallbackCopyTextToClipboard(text);
return; return;
} }
navigator.clipboard.writeText(text).then( try {
() => { navigator.clipboard.writeText(text);
console.log('Async: Copying to clipboard was successful!'); } catch (err) {
}, throw err;
(err) => { }
console.error('Async: Could not copy text: ', err);
}
);
}; };
export const getObjectDiff = (obj1: any, obj2: any) => { export const getObjectDiff = (obj1: any, obj2: any) => {
@ -64,3 +61,25 @@ export const getObjectDiff = (obj1: any, obj2: any) => {
return diff; return diff;
}; };
export const validateEmail = (email: string) => {
if (!email || email === '') return true;
return email
.toLowerCase()
.match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
)
? true
: false;
};
export const validateURI = (uri: string) => {
if (!uri || uri === '') return true;
return uri
.toLowerCase()
.match(
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/,
)
? true
: false;
};

View File

@ -0,0 +1,39 @@
import _flatten from 'lodash/flatten';
import { validateEmail } from '.';
interface dataTypes {
value: string;
isInvalid: boolean;
}
const parseCSV = (file: File, delimiter: string): Promise<dataTypes[]> => {
return new Promise((resolve) => {
const reader = new FileReader();
// When the FileReader has loaded the file...
reader.onload = (e: any) => {
// Split the result to an array of lines
const lines = e.target.result.split('\n');
// Split the lines themselves by the specified
// delimiter, such as a comma
let result = lines.map((line: string) => line.split(delimiter));
// As the FileReader reads asynchronously,
// we can't just return the result; instead,
// we're passing it to a callback function
result = _flatten(result);
resolve(
result.map((email: string) => {
return {
value: email.trim(),
isInvalid: !validateEmail(email.trim()),
};
}),
);
};
// Read the file content as a single string
reader.readAsText(file);
});
};
export default parseCSV;

1887
dashboard/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

20
go.mod Normal file
View File

@ -0,0 +1,20 @@
module server
go 1.21.5
require (
github.com/99designs/gqlgen v0.17.43 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sosodev/duration v1.1.0 // indirect
github.com/urfave/cli/v2 v2.25.5 // indirect
github.com/vektah/gqlparser/v2 v2.5.11 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.9.3 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

31
go.sum Normal file
View File

@ -0,0 +1,31 @@
github.com/99designs/gqlgen v0.17.43 h1:I4SYg6ahjowErAQcHFVKy5EcWuwJ3+Xw9z2fLpuFCPo=
github.com/99designs/gqlgen v0.17.43/go.mod h1:lO0Zjy8MkZgBdv4T1U91x09r0e0WFOdhVUutlQs1Rsc=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sosodev/duration v1.1.0 h1:kQcaiGbJaIsRqgQy7VGlZrVw1giWO+lDoX3MCPnpVO4=
github.com/sosodev/duration v1.1.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/urfave/cli/v2 v2.25.5 h1:d0NIAyhh5shGscroL7ek/Ya9QYQE0KNabJgiUinIQkc=
github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8=
github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

6
package-lock.json generated Normal file
View File

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

39
scripts/couchbase-test.sh Normal file
View File

@ -0,0 +1,39 @@
#!/bin/sh
set -x
set -m
sleep 15
# Setup index and memory quota
# curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=300 -d indexMemoryQuota=300
# Setup services
curl -v http://127.0.0.1:8091/node/controller/setupServices -d services=kv%2Cn1ql%2Cindex
# Setup credentials
curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=Administrator -d password=password
# Setup Memory Optimized Indexes
curl -i -u Administrator:password -X POST http://127.0.0.1:8091/settings/indexes -d 'storageMode=memory_optimized'
# Load travel-sample bucket
#curl -v -u Administrator:password -X POST http://127.0.0.1:8091/sampleBuckets/install -d '["travel-sample"]'
echo "Type: $TYPE"
if [ "$TYPE" = "WORKER" ]; then
echo "Sleeping ..."
sleep 15
#IP=`hostname -s`
IP=`hostname -I | cut -d ' ' -f1`
echo "IP: " $IP
echo "Auto Rebalance: $AUTO_REBALANCE"
if [ "$AUTO_REBALANCE" = "true" ]; then
couchbase-cli rebalance --cluster=$COUCHBASE_MASTER:8091 --user=Administrator --password=password --server-add=$IP --server-add-username=Administrator --server-add-password=password
else
couchbase-cli server-add --cluster=$COUCHBASE_MASTER:8091 --user=Administrator --password=password --server-add=$IP --server-add-username=Administrator --server-add-password=password
fi;
fi;

View File

@ -0,0 +1,25 @@
package providers
import "context"
// AuthenticatorConfig defines authenticator config
type AuthenticatorConfig struct {
// ScannerImage is the base64 of QR code image
ScannerImage string
// Secrets is the secret key
Secret string
// RecoveryCode is the list of recovery codes
RecoveryCodes []string
// RecoveryCodeMap is the map of recovery codes
RecoveryCodeMap map[string]bool
}
// Provider defines authenticators provider
type Provider interface {
// Generate totp: to generate totp, store secret into db and returns base64 of QR code image
Generate(ctx context.Context, id string) (*AuthenticatorConfig, error)
// Validate totp: user passcode with secret stored in our db
Validate(ctx context.Context, passcode string, userID string) (bool, error)
// ValidateRecoveryCode totp: allows user to validate using recovery code incase if they lost their device
ValidateRecoveryCode(ctx context.Context, recoveryCode, userID string) (bool, error)
}

View File

@ -0,0 +1,23 @@
package totp
import (
"context"
)
type provider struct {
ctx context.Context
}
// TOTPConfig defines totp config
type TOTPConfig struct {
ScannerImage string
Secret string
}
// NewProvider returns a new totp provider
func NewProvider() (*provider, error) {
ctx := context.Background()
return &provider{
ctx: ctx,
}, nil
}

View File

@ -0,0 +1,151 @@
package totp
import (
"bytes"
"context"
"encoding/json"
"fmt"
"image/png"
"time"
"github.com/google/uuid"
"github.com/pquerna/otp/totp"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/authenticators/providers"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/refs"
)
// Generate generates a Time-Based One-Time Password (TOTP) for a user and returns the base64-encoded QR code for frontend display.
func (p *provider) Generate(ctx context.Context, id string) (*providers.AuthenticatorConfig, error) {
var buf bytes.Buffer
//get user details
user, err := db.Provider.GetUserByID(ctx, id)
if err != nil {
return nil, err
}
// generate totp, Authenticators hash is valid for 30 seconds
key, err := totp.Generate(totp.GenerateOpts{
Issuer: "authorizer",
AccountName: refs.StringValue(user.Email),
})
if err != nil {
return nil, err
}
//generating image for key and encoding to base64 for displaying in frontend
img, err := key.Image(200, 200)
if err != nil {
return nil, err
}
png.Encode(&buf, img)
encodedText := crypto.EncryptB64(buf.String())
secret := key.Secret()
recoveryCodes := []string{}
for i := 0; i < 10; i++ {
recoveryCodes = append(recoveryCodes, uuid.NewString())
}
// Converting recoveryCodes to string
recoverCodesMap := map[string]bool{}
for i := 0; i < len(recoveryCodes); i++ {
recoverCodesMap[recoveryCodes[i]] = false
}
// Converting recoveryCodesMap to string
jsonData, err := json.Marshal(recoverCodesMap)
if err != nil {
return nil, err
}
recoveryCodesString := string(jsonData)
totpModel := &models.Authenticator{
Secret: secret,
RecoveryCodes: refs.NewStringRef(recoveryCodesString),
UserID: user.ID,
Method: constants.EnvKeyTOTPAuthenticator,
}
authenticator, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, user.ID, constants.EnvKeyTOTPAuthenticator)
if err != nil {
log.Debug("Failed to get authenticator details by user id, creating new record: ", err)
// continue
}
if authenticator == nil {
// if authenticator is nil then create new authenticator
_, err = db.Provider.AddAuthenticator(ctx, totpModel)
if err != nil {
return nil, err
}
} else {
authenticator.Secret = secret
authenticator.RecoveryCodes = refs.NewStringRef(recoveryCodesString)
// if authenticator is not nil then update authenticator
_, err = db.Provider.UpdateAuthenticator(ctx, authenticator)
if err != nil {
return nil, err
}
}
return &providers.AuthenticatorConfig{
ScannerImage: encodedText,
Secret: secret,
RecoveryCodes: recoveryCodes,
RecoveryCodeMap: recoverCodesMap,
}, nil
}
// Validate validates a Time-Based One-Time Password (TOTP) against the stored TOTP secret for a user.
func (p *provider) Validate(ctx context.Context, passcode string, userID string) (bool, error) {
// get totp details
totpModel, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, userID, constants.EnvKeyTOTPAuthenticator)
if err != nil {
return false, err
}
// validate totp
status := totp.Validate(passcode, totpModel.Secret)
// checks if user not signed in for totp and totp code is correct then VerifiedAt will be stored in db
if totpModel.VerifiedAt == nil && status {
timeNow := time.Now().Unix()
totpModel.VerifiedAt = &timeNow
_, err = db.Provider.UpdateAuthenticator(ctx, totpModel)
if err != nil {
return false, err
}
}
return status, nil
}
// ValidateRecoveryCode validates a Time-Based One-Time Password (TOTP) recovery code against the stored TOTP recovery code for a user.
func (p *provider) ValidateRecoveryCode(ctx context.Context, recoveryCode, userID string) (bool, error) {
// get totp details
totpModel, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, userID, constants.EnvKeyTOTPAuthenticator)
if err != nil {
return false, err
}
// convert recoveryCodes to map
recoveryCodesMap := map[string]bool{}
err = json.Unmarshal([]byte(refs.StringValue(totpModel.RecoveryCodes)), &recoveryCodesMap)
if err != nil {
return false, err
}
// check if recovery code is valid
if val, ok := recoveryCodesMap[recoveryCode]; !ok {
return false, fmt.Errorf("invalid recovery code")
} else if val {
return false, fmt.Errorf("recovery code already used")
}
// update recovery code map
recoveryCodesMap[recoveryCode] = true
// convert recoveryCodesMap to string
jsonData, err := json.Marshal(recoveryCodesMap)
if err != nil {
return false, err
}
recoveryCodesString := string(jsonData)
totpModel.RecoveryCodes = refs.NewStringRef(recoveryCodesString)
// update recovery code map in db
_, err = db.Provider.UpdateAuthenticator(ctx, totpModel)
if err != nil {
return false, err
}
return true, nil
}

View File

@ -0,0 +1,26 @@
package authenticators
import (
"github.com/authorizerdev/authorizer/server/authenticators/providers"
"github.com/authorizerdev/authorizer/server/authenticators/providers/totp"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// Provider is the global authenticators provider.
var Provider providers.Provider
// InitTOTPStore initializes the TOTP authenticator store if it's not disabled in the environment variables.
// It sets the global Provider variable to a new TOTP provider.
func InitTOTPStore() error {
var err error
isTOTPEnvServiceDisabled, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableTOTPLogin)
if !isTOTPEnvServiceDisabled {
Provider, err = totp.NewProvider()
if err != nil {
return err
}
}
return nil
}

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,32 @@
package constants
const (
// AuthRecipeMethodBasicAuth is the basic_auth auth method
AuthRecipeMethodBasicAuth = "basic_auth"
// AuthRecipeMethodMobileBasicAuth is the mobile basic_auth method, where user can signup using mobile number and password
AuthRecipeMethodMobileBasicAuth = "mobile_basic_auth"
// AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
AuthRecipeMethodMagicLinkLogin = "magic_link_login"
// AuthRecipeMethodMobileOTP is the mobile_otp auth method
AuthRecipeMethodMobileOTP = "mobile_otp"
// 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"
// AuthRecipeMethodDiscord is the discord auth method
AuthRecipeMethodDiscord = "discord"
// AuthRecipeMethodTwitter is the twitter auth method
AuthRecipeMethodTwitter = "twitter"
// AuthRecipeMethodMicrosoft is the microsoft auth method
AuthRecipeMethodMicrosoft = "microsoft"
// AuthRecipeMethodTwitch is the twitch auth method
AuthRecipeMethodTwitch = "twitch"
// AuthRecipeMethodRoblox is the roblox auth method
AuthRecipeMethodRoblox = "roblox"
)

View File

@ -0,0 +1,7 @@
package constants
// Authenticators Methods
const (
// EnvKeyTOTPAuthenticator key for env variable TOTP
EnvKeyTOTPAuthenticator = "totp"
)

View File

@ -0,0 +1,10 @@
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"
// MfaCookieName is the name of the cookie that is used to store the mfa session
MfaCookieName = "mfa"
)

View File

@ -5,6 +5,8 @@ const (
DbTypePostgres = "postgres" DbTypePostgres = "postgres"
// DbTypeSqlite is the sqlite database type // DbTypeSqlite is the sqlite database type
DbTypeSqlite = "sqlite" DbTypeSqlite = "sqlite"
// DbTypeLibSQL is the libsql / Turso database type
DbTypeLibSQL = "libsql"
// DbTypeMysql is the mysql database type // DbTypeMysql is the mysql database type
DbTypeMysql = "mysql" DbTypeMysql = "mysql"
// DbTypeSqlserver is the sqlserver database type // DbTypeSqlserver is the sqlserver database type
@ -15,4 +17,18 @@ const (
DbTypeMongodb = "mongodb" DbTypeMongodb = "mongodb"
// DbTypeYugabyte is the yugabyte database type // DbTypeYugabyte is the yugabyte database type
DbTypeYugabyte = "yugabyte" DbTypeYugabyte = "yugabyte"
// DbTypeMariaDB is the mariadb database type
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"
// DbTypeDynamoDB is the Dynamo database type
DbTypeDynamoDB = "dynamodb"
// DbTypeCouchbaseDB is the Couchbase database type
DbTypeCouchbaseDB = "couchbase"
) )

View File

@ -1,34 +1,55 @@
package constants package constants
const ( var VERSION = "0.0.1"
// Envstore identifier
// StringStore string store identifier
StringStoreIdentifier = "stringStore"
// BoolStore bool store identifier
BoolStoreIdentifier = "boolStore"
// SliceStore slice store identifier
SliceStoreIdentifier = "sliceStore"
const (
// TestEnv is used for testing
TestEnv = "test"
// EnvKeyEnv key for env variable ENV // EnvKeyEnv key for env variable ENV
EnvKeyEnv = "ENV" EnvKeyEnv = "ENV"
// EnvKeyEnvPath key for cli arg variable ENV_PATH // EnvKeyEnvPath key for cli arg variable ENV_PATH
EnvKeyEnvPath = "ENV_PATH" EnvKeyEnvPath = "ENV_PATH"
// EnvKeyVersion key for build arg version
EnvKeyVersion = "VERSION"
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL // EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
// TODO: remove support AUTHORIZER_URL env
EnvKeyAuthorizerURL = "AUTHORIZER_URL" EnvKeyAuthorizerURL = "AUTHORIZER_URL"
// EnvKeyPort key for env variable PORT // EnvKeyPort key for env variable PORT
EnvKeyPort = "PORT" EnvKeyPort = "PORT"
// EnvKeyAccessTokenExpiryTime key for env variable ACCESS_TOKEN_EXPIRY_TIME
EnvKeyAccessTokenExpiryTime = "ACCESS_TOKEN_EXPIRY_TIME"
// EnvKeyAdminSecret key for env variable ADMIN_SECRET // EnvKeyAdminSecret key for env variable ADMIN_SECRET
EnvKeyAdminSecret = "ADMIN_SECRET" EnvKeyAdminSecret = "ADMIN_SECRET"
// EnvKeyDatabaseType key for env variable DATABASE_TYPE // EnvKeyDatabaseType key for env variable DATABASE_TYPE
EnvKeyDatabaseType = "DATABASE_TYPE" EnvKeyDatabaseType = "DATABASE_TYPE"
// EnvKeyDatabaseURL key for env variable DATABASE_URL // EnvKeyDatabaseURL key for env variable DATABASE_URL
EnvKeyDatabaseURL = "DATABASE_URL" EnvKeyDatabaseURL = "DATABASE_URL"
// EnvAwsRegion key for env variable AWS REGION
EnvAwsRegion = "AWS_REGION"
// EnvAwsAccessKeyID key for env variable AWS_ACCESS_KEY_ID
EnvAwsAccessKeyID = "AWS_ACCESS_KEY_ID"
// EnvAwsAccessKey key for env variable AWS_SECRET_ACCESS_KEY
EnvAwsSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
// EnvKeyDatabaseName key for env variable DATABASE_NAME // EnvKeyDatabaseName key for env variable DATABASE_NAME
EnvKeyDatabaseName = "DATABASE_NAME" EnvKeyDatabaseName = "DATABASE_NAME"
// EnvKeyDatabaseUsername key for env variable DATABASE_USERNAME
EnvKeyDatabaseUsername = "DATABASE_USERNAME"
// EnvKeyDatabasePassword key for env variable DATABASE_PASSWORD
EnvKeyDatabasePassword = "DATABASE_PASSWORD"
// EnvKeyDatabasePort key for env variable DATABASE_PORT
EnvKeyDatabasePort = "DATABASE_PORT"
// EnvKeyDatabaseHost key for env variable DATABASE_HOST
EnvKeyDatabaseHost = "DATABASE_HOST"
// EnvKeyDatabaseCert key for env variable DATABASE_CERT
EnvKeyDatabaseCert = "DATABASE_CERT"
// EnvKeyDatabaseCertKey key for env variable DATABASE_KEY
EnvKeyDatabaseCertKey = "DATABASE_CERT_KEY"
// EnvKeyDatabaseCACert key for env variable DATABASE_CA_CERT
EnvKeyDatabaseCACert = "DATABASE_CA_CERT"
// EnvCouchbaseBucket key for env variable COUCHBASE_BUCKET
EnvCouchbaseBucket = "COUCHBASE_BUCKET"
// EnvCouchbaseBucketRAMQuotaMB key for env variable COUCHBASE_BUCKET_RAM_QUOTA
// This value should be parsed as number
EnvCouchbaseBucketRAMQuotaMB = "COUCHBASE_BUCKET_RAM_QUOTA"
// EnvCouchbaseBucket key for env variable COUCHBASE_SCOPE
EnvCouchbaseScope = "COUCHBASE_SCOPE"
// EnvKeySmtpHost key for env variable SMTP_HOST // EnvKeySmtpHost key for env variable SMTP_HOST
EnvKeySmtpHost = "SMTP_HOST" EnvKeySmtpHost = "SMTP_HOST"
// EnvKeySmtpPort key for env variable SMTP_PORT // EnvKeySmtpPort key for env variable SMTP_PORT
@ -37,8 +58,20 @@ const (
EnvKeySmtpUsername = "SMTP_USERNAME" EnvKeySmtpUsername = "SMTP_USERNAME"
// EnvKeySmtpPassword key for env variable SMTP_PASSWORD // EnvKeySmtpPassword key for env variable SMTP_PASSWORD
EnvKeySmtpPassword = "SMTP_PASSWORD" EnvKeySmtpPassword = "SMTP_PASSWORD"
// EnvKeySmtpLocalName key for env variable SMTP_LOCAL_NAME
EnvKeySmtpLocalName = "SMTP_LOCAL_NAME"
// EnvKeySenderEmail key for env variable SENDER_EMAIL // EnvKeySenderEmail key for env variable SENDER_EMAIL
EnvKeySenderEmail = "SENDER_EMAIL" EnvKeySenderEmail = "SENDER_EMAIL"
// EnvKeySenderName key for env variable SENDER_NAME
EnvKeySenderName = "SENDER_NAME"
// EnvKeyIsEmailServiceEnabled key for env variable IS_EMAIL_SERVICE_ENABLED
EnvKeyIsEmailServiceEnabled = "IS_EMAIL_SERVICE_ENABLED"
// EnvKeyIsSMSServiceEnabled key for env variable IS_SMS_SERVICE_ENABLED
EnvKeyIsSMSServiceEnabled = "IS_SMS_SERVICE_ENABLED"
// EnvKeyAppCookieSecure key for env variable APP_COOKIE_SECURE
EnvKeyAppCookieSecure = "APP_COOKIE_SECURE"
// EnvKeyAdminCookieSecure key for env variable ADMIN_COOKIE_SECURE
EnvKeyAdminCookieSecure = "ADMIN_COOKIE_SECURE"
// EnvKeyJwtType key for env variable JWT_TYPE // EnvKeyJwtType key for env variable JWT_TYPE
EnvKeyJwtType = "JWT_TYPE" EnvKeyJwtType = "JWT_TYPE"
// EnvKeyJwtSecret key for env variable JWT_SECRET // EnvKeyJwtSecret key for env variable JWT_SECRET
@ -47,34 +80,12 @@ const (
EnvKeyJwtPrivateKey = "JWT_PRIVATE_KEY" EnvKeyJwtPrivateKey = "JWT_PRIVATE_KEY"
// EnvKeyJwtPublicKey key for env variable JWT_PUBLIC_KEY // EnvKeyJwtPublicKey key for env variable JWT_PUBLIC_KEY
EnvKeyJwtPublicKey = "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 key for env variable APP_URL
EnvKeyAppURL = "APP_URL" EnvKeyAppURL = "APP_URL"
// EnvKeyRedisURL key for env variable REDIS_URL // EnvKeyRedisURL key for env variable REDIS_URL
EnvKeyRedisURL = "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 key for env variable RESET_PASSWORD_URL
EnvKeyResetPasswordURL = "RESET_PASSWORD_URL" EnvKeyResetPasswordURL = "RESET_PASSWORD_URL"
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
// 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"
// 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 key for env variable JWT_ROLE_CLAIM
EnvKeyJwtRoleClaim = "JWT_ROLE_CLAIM" EnvKeyJwtRoleClaim = "JWT_ROLE_CLAIM"
// EnvKeyGoogleClientID key for env variable GOOGLE_CLIENT_ID // EnvKeyGoogleClientID key for env variable GOOGLE_CLIENT_ID
@ -89,12 +100,116 @@ const (
EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID" EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID"
// EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET // EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET
EnvKeyFacebookClientSecret = "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"
// EnvKeyDiscordClientID key for env variable DISCORD_CLIENT_ID
EnvKeyDiscordClientID = "DISCORD_CLIENT_ID"
// EnvKeyDiscordClientSecret key for env variable DISCORD_CLIENT_SECRET
EnvKeyDiscordClientSecret = "DISCORD_CLIENT_SECRET"
// EnvKeyTwitterClientID key for env variable TWITTER_CLIENT_ID
EnvKeyTwitterClientID = "TWITTER_CLIENT_ID"
// EnvKeyTwitterClientSecret key for env variable TWITTER_CLIENT_SECRET
EnvKeyTwitterClientSecret = "TWITTER_CLIENT_SECRET"
// EnvKeyMicrosoftClientID key for env variable MICROSOFT_CLIENT_ID
EnvKeyMicrosoftClientID = "MICROSOFT_CLIENT_ID"
// EnvKeyMicrosoftActiveDirectoryTenantID key for env variable MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID
EnvKeyMicrosoftActiveDirectoryTenantID = "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID"
// EnvKeyMicrosoftClientSecret key for env variable MICROSOFT_CLIENT_SECRET
EnvKeyMicrosoftClientSecret = "MICROSOFT_CLIENT_SECRET"
// EnvKeyTwitchClientID key for env variable TWITCH_CLIENT_ID
EnvKeyTwitchClientID = "TWITCH_CLIENT_ID"
// EnvKeyTwitchClientSecret key for env variable TWITCH_CLIENT_SECRET
EnvKeyTwitchClientSecret = "TWITCH_CLIENT_SECRET"
// EnvKeyRobloxClientID key for env variable ROBLOX_CLIENT_ID
EnvKeyRobloxClientID = "ROBLOX_CLIENT_ID"
// EnvKeyRobloxClientSecret key for env variable ROBLOX_CLIENT_SECRET
EnvKeyRobloxClientSecret = "ROBLOX_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME // EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME" EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO // EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
EnvKeyOrganizationLogo = "ORGANIZATION_LOGO" EnvKeyOrganizationLogo = "ORGANIZATION_LOGO"
// EnvKeyIsProd key for env variable IS_PROD
EnvKeyIsProd = "IS_PROD"
// EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT // EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT
EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT" EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT"
// Not Exposed Keys
// EnvKeyClientID key for env variable CLIENT_ID
EnvKeyClientID = "CLIENT_ID"
// EnvKeyClientSecret key for env variable CLIENT_SECRET
EnvKeyClientSecret = "CLIENT_SECRET"
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
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"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_MOBILE_BASIC_AUTH
EnvKeyDisableMobileBasicAuthentication = "DISABLE_MOBILE_BASIC_AUTHENTICATION"
// EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
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"
// EnvKeyEnforceMultiFactorAuthentication is key for env variable ENFORCE_MULTI_FACTOR_AUTHENTICATION
// If enforced and changed later on, existing user will have MFA but new user will not have MFA
EnvKeyEnforceMultiFactorAuthentication = "ENFORCE_MULTI_FACTOR_AUTHENTICATION"
// EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION
// this variable is used to completely disable multi factor authentication. It will have no effect on profile preference
EnvKeyDisableMultiFactorAuthentication = "DISABLE_MULTI_FACTOR_AUTHENTICATION"
// EnvKeyDisableTOTPLogin is key for env variable DISABLE_TOTP_LOGIN
// this variable is used to completely disable totp verification
EnvKeyDisableTOTPLogin = "DISABLE_TOTP_LOGIN"
// EnvKeyDisableMailOTPLogin is key for env variable DISABLE_MAIL_OTP_LOGIN
// this variable is used to completely disable totp verification
EnvKeyDisableMailOTPLogin = "DISABLE_MAIL_OTP_LOGIN"
// EnvKeyDisablePhoneVerification is key for env variable DISABLE_PHONE_VERIFICATION
// this variable is used to disable phone verification
EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION"
// EnvKeyDisablePlayGround is key for env variable DISABLE_PLAYGROUND
// this variable will disable or enable playground use in dashboard
EnvKeyDisablePlayGround = "DISABLE_PLAYGROUND"
// 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"
// For oauth/openid/authorize
// EnvKeyDefaultAuthorizeResponseType key for env variable DEFAULT_AUTHORIZE_RESPONSE_TYPE
// This env is used for setting default response type in authorize handler
EnvKeyDefaultAuthorizeResponseType = "DEFAULT_AUTHORIZE_RESPONSE_TYPE"
// EnvKeyDefaultAuthorizeResponseMode key for env variable DEFAULT_AUTHORIZE_RESPONSE_MODE
// This env is used for setting default response mode in authorize handler
EnvKeyDefaultAuthorizeResponseMode = "DEFAULT_AUTHORIZE_RESPONSE_MODE"
// Twilio env variables
// EnvKeyTwilioAPIKey key for env variable TWILIO_API_KEY
EnvKeyTwilioAPIKey = "TWILIO_API_KEY"
// EnvKeyTwilioAPISecret key for env variable TWILIO_API_SECRET
EnvKeyTwilioAPISecret = "TWILIO_API_SECRET"
// EnvKeyTwilioAccountSID key for env variable TWILIO_ACCOUNT_SID
EnvKeyTwilioAccountSID = "TWILIO_ACCOUNT_SID"
// EnvKeyTwilioSender key for env variable TWILIO_SENDER
EnvKeyTwilioSender = "TWILIO_SENDER"
) )

View File

@ -0,0 +1,22 @@
package constants
const (
// - query: for Authorization Code grant. 302 Found triggers redirect.
ResponseModeQuery = "query"
// - fragment: for Implicit grant. 302 Found triggers redirect.
ResponseModeFragment = "fragment"
// - form_post: 200 OK with response parameters embedded in an HTML form as hidden parameters.
ResponseModeFormPost = "form_post"
// - web_message: For Silent Authentication. Uses HTML5 web messaging.
ResponseModeWebMessage = "web_message"
// For the Authorization Code grant, use response_type=code to include the authorization code.
ResponseTypeCode = "code"
// For the Implicit grant, use response_type=token to include an access token.
ResponseTypeToken = "token"
// For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
ResponseTypeIDToken = "id_token"
// Constant indicating the "signup" screen hint for customizing authentication process and redirect to a signup page.
ScreenHintSignUp = "signup"
)

View File

@ -8,4 +8,20 @@ const (
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token=" FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token // Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
GithubUserInfoURL = "https://api.github.com/user" GithubUserInfoURL = "https://api.github.com/user"
// Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123
GithubUserEmails = "https://api.github.com/user/emails"
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
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~))"
TwitterUserInfoURL = "https://api.twitter.com/2/users/me?user.fields=id,name,profile_image_url,username"
// RobloxUserInfoURL is the URL to get user info from Roblox
RobloxUserInfoURL = "https://apis.roblox.com/oauth/v1/userinfo"
DiscordUserInfoURL = "https://discord.com/api/oauth2/@me"
// Get microsoft user info.
// Ref: https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo
MicrosoftUserInfoURL = "https://graph.microsoft.com/oidc/userinfo"
) )

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

@ -5,4 +5,8 @@ const (
TokenTypeRefreshToken = "refresh_token" TokenTypeRefreshToken = "refresh_token"
// TokenTypeAccessToken is the access_token token type // TokenTypeAccessToken is the access_token token type
TokenTypeAccessToken = "access_token" 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

@ -9,4 +9,19 @@ const (
VerificationTypeUpdateEmail = "update_email" VerificationTypeUpdateEmail = "update_email"
// VerificationTypeForgotPassword is the forgot_password verification type // VerificationTypeForgotPassword is the forgot_password verification type
VerificationTypeForgotPassword = "forgot_password" VerificationTypeForgotPassword = "forgot_password"
// VerificationTypeInviteMember is the invite_member verification type
VerificationTypeInviteMember = "invite_member"
// VerificationTypeOTP is the otp verification type
VerificationTypeOTP = "verify_otp"
)
var (
// VerificationTypes is slice of all verification types
VerificationTypes = []string{
VerificationTypeBasicAuthSignup,
VerificationTypeMagicLinkLogin,
VerificationTypeUpdateEmail,
VerificationTypeForgotPassword,
VerificationTypeInviteMember,
}
) )

View File

@ -0,0 +1,20 @@
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`
// UserDeactivatedWebhookEvent name for user deactivated event
UserDeactivatedWebhookEvent = `user.deactivated`
)

View File

@ -3,25 +3,32 @@ package cookie
import ( import (
"net/url" "net/url"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/parsers"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// SetAdminCookie sets the admin cookie in the response // SetAdminCookie sets the admin cookie in the response
func SetAdminCookie(gc *gin.Context, token string) { func SetAdminCookie(gc *gin.Context, token string) {
secure := true adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
httpOnly := true if err != nil {
hostname := utils.GetHost(gc) log.Debug("Error while getting admin cookie secure from env variable: %v", err)
host, _ := utils.GetHostParts(hostname) adminCookieSecure = true
}
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly) secure := adminCookieSecure
httpOnly := adminCookieSecure
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 // GetAdminCookie gets the admin cookie from the request
func GetAdminCookie(gc *gin.Context) (string, error) { func GetAdminCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName)) cookie, err := gc.Request.Cookie(constants.AdminCookieName)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -37,10 +44,15 @@ func GetAdminCookie(gc *gin.Context) (string, error) {
// DeleteAdminCookie sets the response cookie to empty // DeleteAdminCookie sets the response cookie to empty
func DeleteAdminCookie(gc *gin.Context) { func DeleteAdminCookie(gc *gin.Context) {
secure := true adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
httpOnly := true if err != nil {
hostname := utils.GetHost(gc) log.Debug("Error while getting admin cookie secure from env variable: %v", err)
host, _ := utils.GetHostParts(hostname) adminCookieSecure = true
}
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly) secure := adminCookieSecure
httpOnly := adminCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
gc.SetCookie(constants.AdminCookieName, "", -1, "/", host, secure, httpOnly)
} }

View File

@ -4,99 +4,86 @@ import (
"net/http" "net/http"
"net/url" "net/url"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/parsers"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// SetCookie sets the cookie in the response. It sets 4 cookies // SetSession sets the session cookie in the response
// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com) func SetSession(gc *gin.Context, sessionID string) {
// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com). appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification. if err != nil {
// 4 COOKIE_NAME.refresh_token refresh token log.Debug("Error while getting app cookie secure from env variable: %v", err)
// Note all sites don't allow 2nd type of cookie appCookieSecure = true
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) { }
secure := true
httpOnly := true secure := appCookieSecure
hostname := utils.GetHost(gc) httpOnly := appCookieSecure
host, _ := utils.GetHostParts(hostname) hostname := parsers.GetHost(gc)
domain := utils.GetDomainName(hostname) host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)
if domain != "localhost" { if domain != "localhost" {
domain = "." + domain domain = "." + domain
} }
// Since app cookie can come from cross site it becomes important to set this in lax mode when insecure.
// Example person using custom UI on their app domain and making request to authorizer domain.
// For more information check:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
// https://github.com/gin-gonic/gin/blob/master/context.go#L86
// TODO add ability to sameSite = none / strict from dashboard
if !appCookieSecure {
gc.SetSameSite(http.SameSiteLaxMode)
} else {
gc.SetSameSite(http.SameSiteNoneMode)
}
// TODO allow configuring from dashboard
year := 60 * 60 * 24 * 365 year := 60 * 60 * 24 * 365
thirtyMin := 60 * 30
gc.SetSameSite(http.SameSiteNoneMode) gc.SetCookie(constants.AppCookieName+"_session", sessionID, year, "/", host, secure, httpOnly)
// set cookie for host gc.SetCookie(constants.AppCookieName+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly)
// in case of subdomain, set cookie for domain
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly)
// set finger print cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly)
// set refresh token cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly)
} }
// GetAccessTokenCookie to get access token cookie from the request // DeleteSession sets session cookies to expire
func GetAccessTokenCookie(gc *gin.Context) (string, error) { func DeleteSession(gc *gin.Context) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token") appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
if err != nil { if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain") log.Debug("Error while getting app cookie secure from env variable: %v", err)
appCookieSecure = true
}
secure := appCookieSecure
httpOnly := appCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
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(constants.AppCookieName + "_session")
if err != nil {
cookie, err = gc.Request.Cookie(constants.AppCookieName + "_session_domain")
if err != nil { if err != nil {
return "", err return "", err
} }
} }
return cookie.Value, nil decodedValue, err := url.PathUnescape(cookie.Value)
}
// GetRefreshTokenCookie to get refresh token cookie
func GetRefreshTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token")
if err != nil { if err != nil {
return "", err return "", err
} }
return cookie.Value, nil
}
// GetFingerPrintCookie to get fingerprint cookie
func GetFingerPrintCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint")
if err != nil {
return "", err
}
// cookie escapes special characters like $
// hence we need to unescape before comparing
decodedValue, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil return decodedValue, nil
} }
// DeleteCookie sets response cookies to expire
func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly)
}

View File

@ -0,0 +1,89 @@
package cookie
import (
"net/http"
"net/url"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/gin-gonic/gin"
)
// SetMfaSession sets the mfa session cookie in the response
func SetMfaSession(gc *gin.Context, sessionID string) {
appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
if err != nil {
log.Debug("Error while getting app cookie secure from env variable: %v", err)
appCookieSecure = true
}
secure := appCookieSecure
httpOnly := appCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
// Since app cookie can come from cross site it becomes important to set this in lax mode when insecure.
// Example person using custom UI on their app domain and making request to authorizer domain.
// For more information check:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
// https://github.com/gin-gonic/gin/blob/master/context.go#L86
// TODO add ability to sameSite = none / strict from dashboard
if !appCookieSecure {
gc.SetSameSite(http.SameSiteLaxMode)
} else {
gc.SetSameSite(http.SameSiteNoneMode)
}
// TODO allow configuring from dashboard
age := 60
gc.SetCookie(constants.MfaCookieName+"_session", sessionID, age, "/", host, secure, httpOnly)
gc.SetCookie(constants.MfaCookieName+"_session_domain", sessionID, age, "/", domain, secure, httpOnly)
}
// DeleteMfaSession deletes the mfa session cookies to expire
func DeleteMfaSession(gc *gin.Context) {
appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
if err != nil {
log.Debug("Error while getting app cookie secure from env variable: %v", err)
appCookieSecure = true
}
secure := appCookieSecure
httpOnly := appCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.MfaCookieName+"_session", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(constants.MfaCookieName+"_session_domain", "", -1, "/", domain, secure, httpOnly)
}
// GetMfaSession gets the mfa session cookie from context
func GetMfaSession(gc *gin.Context) (string, error) {
var cookie *http.Cookie
var err error
cookie, err = gc.Request.Cookie(constants.MfaCookieName + "_session")
if err != nil {
cookie, err = gc.Request.Cookie(constants.MfaCookieName + "_session_domain")
if err != nil {
return "", err
}
}
decodedValue, err := url.PathUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil
}

View File

@ -1,37 +1,66 @@
package utils package crypto
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/base64"
"encoding/json"
"io" "io"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/memorystore"
"golang.org/x/crypto/bcrypt"
) )
// EncryptB64 encrypts data into base64 string var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 0o5}
func EncryptB64(text string) string {
return base64.StdEncoding.EncodeToString([]byte(text))
}
// DecryptB64 decrypts from base64 string to readable string // EncryptAES method is to encrypt or hide any classified text
func DecryptB64(s string) (string, error) { func EncryptAES(text string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s) k, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)
if err != nil { if err != nil {
return "", err return "", err
} }
return string(data), nil key := []byte(k)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
plainText := []byte(text)
cfb := cipher.NewCFBEncrypter(block, bytes)
cipherText := make([]byte, len(plainText))
cfb.XORKeyStream(cipherText, plainText)
return EncryptB64(string(cipherText)), nil
} }
// EncryptAES encrypts data using AES algorithm // DecryptAES method is to extract back the encrypted text
func EncryptAES(text []byte) ([]byte, error) { func DecryptAES(text string) (string, error) {
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) k, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)
c, err := aes.NewCipher(key) if err != nil {
return "", err
}
key := []byte(k)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
cipherText, err := DecryptB64(text)
if err != nil {
return "", err
}
cfb := cipher.NewCFBDecrypter(block, bytes)
plainText := make([]byte, len(cipherText))
cfb.XORKeyStream(plainText, []byte(cipherText))
return string(plainText), nil
}
// EncryptAESEnv encrypts data using AES algorithm
// kept for the backward compatibility of env data encryption
func EncryptAESEnv(text []byte) ([]byte, error) {
var res []byte 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 { if err != nil {
return res, err return res, err
} }
@ -62,10 +91,15 @@ func EncryptAES(text []byte) ([]byte, error) {
} }
// DecryptAES decrypts data using AES algorithm // DecryptAES decrypts data using AES algorithm
func DecryptAES(ciphertext []byte) ([]byte, error) { // Kept for the backward compatibility of env data decryption
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) func DecryptAESEnv(ciphertext []byte) ([]byte, error) {
c, err := aes.NewCipher(key)
var res []byte 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 { if err != nil {
return res, err return res, err
} }
@ -88,39 +122,3 @@ func DecryptAES(ciphertext []byte) ([]byte, error) {
return plaintext, nil return plaintext, nil
} }
// EncryptEnvData is used to encrypt the env data
func EncryptEnvData(data envstore.Store) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
envStoreObj := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
err = json.Unmarshal(jsonBytes, &envStoreObj)
if err != nil {
return "", err
}
configData, err := json.Marshal(envStoreObj)
if err != nil {
return "", err
}
encryptedConfig, err := EncryptAES(configData)
if err != nil {
return "", err
}
return EncryptB64(string(encryptedConfig)), nil
}
// EncryptPassword is used for encrypting password
func EncryptPassword(password string) (string, error) {
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pw), nil
}

17
server/crypto/b64.go Normal file
View File

@ -0,0 +1,17 @@
package crypto
import "encoding/base64"
// EncryptB64 encrypts data into base64 string
func EncryptB64(text string) string {
return base64.StdEncoding.EncodeToString([]byte(text))
}
// DecryptB64 decrypts from base64 string to readable string
func DecryptB64(s string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(data), nil
}

153
server/crypto/common.go Normal file
View File

@ -0,0 +1,153 @@
package crypto
import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/json"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"golang.org/x/crypto/bcrypt"
"gopkg.in/square/go-jose.v2"
)
// GetPubJWK returns JWK for given keys
func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) {
jwk := &jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{
{
Algorithm: algo,
Key: publicKey,
Use: "sig",
KeyID: keyID,
Certificates: []*x509.Certificate{},
CertificateThumbprintSHA1: []uint8{},
CertificateThumbprintSHA256: []uint8{},
},
},
}
jwkPublicKey, err := jwk.Keys[0].MarshalJSON()
if err != nil {
return "", err
}
return string(jwkPublicKey), nil
}
// GenerateJWKBasedOnEnv generates JWK based on env
// make sure clientID, jwtType, jwtSecret / public & private key pair is set
// this is called while initializing app / when env is updated
func GenerateJWKBasedOnEnv() (string, error) {
jwk := ""
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
}
// check if jwt secret is provided
if IsHMACA(algo) {
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(jwtPublicKey)
if err != nil {
return "", err
}
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
if err != nil {
return "", err
}
}
if IsECDSA(algo) {
jwtPublicKey, err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
if err != nil {
return jwk, err
}
publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(jwtPublicKey)
if err != nil {
return "", err
}
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
if err != nil {
return "", err
}
}
return jwk, nil
}
// EncryptEnvData is used to encrypt the env data
func EncryptEnvData(data map[string]interface{}) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
storeData, err := memorystore.Provider.GetEnvStore()
if err != nil {
return "", err
}
err = json.Unmarshal(jsonBytes, &storeData)
if err != nil {
return "", err
}
configData, err := json.Marshal(storeData)
if err != nil {
return "", err
}
encryptedConfig, err := EncryptAESEnv(configData)
if err != nil {
return "", err
}
return EncryptB64(string(encryptedConfig)), nil
}
// getSHA256 calculates the SHA-256 hash of a string
func getSHA256(input string) string {
hash := sha256.New()
hash.Write([]byte(input))
return hex.EncodeToString(hash.Sum(nil))
}
// VerifyPassword compares a stored hashed password with a user-provided password
func VerifyPassword(storedHashedPassword, userProvidedPassword string) error {
// CompareHashAndPassword returns nil on success
passwordSHA256 := getSHA256(userProvidedPassword)
err := bcrypt.CompareHashAndPassword([]byte(storedHashedPassword), []byte(passwordSHA256))
return err
}
// EncryptPassword is used for encrypting password
func EncryptPassword(password string) (string, error) {
passwordSHA256 := getSHA256(password)
pw, err := bcrypt.GenerateFromPassword([]byte(passwordSHA256), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pw), nil
}

154
server/crypto/ecdsa.go Normal file
View File

@ -0,0 +1,154 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewECDSAKey to generate new ECDSA Key if env is not set
// returns key instance, private key string, public key string, jwk string, error
func NewECDSAKey(algo, keyID string) (*ecdsa.PrivateKey, string, string, string, error) {
var curve elliptic.Curve
switch algo {
case "ES256":
curve = elliptic.P256()
case "ES384":
curve = elliptic.P384()
case "ES512":
curve = elliptic.P521()
default:
return nil, "", "", "", errors.New("Invalid algo")
}
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, "", "", "", err
}
privateKey, publicKey, err := AsECDSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
return key, privateKey, publicKey, string(jwkPublicKey), err
}
// IsECDSA checks if given string is valid ECDSA algo
func IsECDSA(algo string) bool {
switch algo {
case "ES256", "ES384", "ES512":
return true
default:
return false
}
}
// ExportEcdsaPrivateKeyAsPemStr to get ECDSA private key as pem string
func ExportEcdsaPrivateKeyAsPemStr(privkey *ecdsa.PrivateKey) (string, error) {
privkeyBytes, err := x509.MarshalECPrivateKey(privkey)
if err != nil {
return "", err
}
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem), nil
}
// ExportEcdsaPublicKeyAsPemStr to get ECDSA public key as pem string
func ExportEcdsaPublicKeyAsPemStr(pubkey *ecdsa.PublicKey) (string, error) {
pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return "", err
}
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem), nil
}
// ParseEcdsaPrivateKeyFromPemStr to parse ECDSA private key from pem string
func ParseEcdsaPrivateKeyFromPemStr(privPEM string) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseEcdsaPublicKeyFromPemStr to parse ECDSA public key from pem string
func ParseEcdsaPublicKeyFromPemStr(pubPEM string) (*ecdsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *ecdsa.PublicKey:
return pub, nil
default:
break // fall through
}
return nil, errors.New("Key type is not ECDSA")
}
// AsECDSAStr returns private, public key string or error
func AsECDSAStr(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem, err := ExportEcdsaPrivateKeyAsPemStr(privateKey)
if err != nil {
return "", "", err
}
pubPem, err := ExportEcdsaPublicKeyAsPemStr(publicKey)
if err != nil {
return "", "", err
}
// Import the keys from pem string
privParsed, err := ParseEcdsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseEcdsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem, err := ExportEcdsaPrivateKeyAsPemStr(privParsed)
if err != nil {
return "", "", err
}
pubParsedPem, err := ExportEcdsaPublicKeyAsPemStr(pubParsed)
if err != nil {
return "", "", err
}
return privParsedPem, pubParsedPem, nil
}

26
server/crypto/hmac.go Normal file
View File

@ -0,0 +1,26 @@
package crypto
import (
"github.com/google/uuid"
)
// NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo
// returns key, string, error
func NewHMACKey(algo, keyID string) (string, string, error) {
key := uuid.New().String()
jwkPublicKey, err := GetPubJWK(algo, keyID, []byte(key))
if err != nil {
return "", "", err
}
return key, string(jwkPublicKey), nil
}
// IsHMACValid checks if given string is valid HMCA algo
func IsHMACA(algo string) bool {
switch algo {
case "HS256", "HS384", "HS512":
return true
default:
return false
}
}

141
server/crypto/rsa.go Normal file
View File

@ -0,0 +1,141 @@
package crypto
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
)
// NewRSAKey to generate new RSA Key if env is not set
// returns key instance, private key string, public key string, jwk string, error
func NewRSAKey(algo, keyID string) (*rsa.PrivateKey, string, string, string, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, "", "", "", err
}
privateKey, publicKey, err := AsRSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
return key, privateKey, publicKey, string(jwkPublicKey), err
}
// IsRSA checks if given string is valid RSA algo
func IsRSA(algo string) bool {
switch algo {
case "RS256", "RS384", "RS512":
return true
default:
return false
}
}
// ExportRsaPrivateKeyAsPemStr to get RSA private key as pem string
func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey)
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem)
}
// ExportRsaPublicKeyAsPemStr to get RSA public key as pem string
func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) string {
pubkeyBytes := x509.MarshalPKCS1PublicKey(pubkey)
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem)
}
// ParseRsaPrivateKeyFromPemStr to parse RSA private key from pem string
func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseRsaPublicKeyFromPemStr to parse RSA public key from pem string
func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, err
}
return pub, nil
}
// AsRSAStr returns private, public key string or error
func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem := ExportRsaPrivateKeyAsPemStr(privateKey)
pubPem := ExportRsaPublicKeyAsPemStr(publickKey)
// Import the keys from pem string
privParsed, err := ParseRsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseRsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem := ExportRsaPrivateKeyAsPemStr(privParsed)
pubParsedPem := ExportRsaPublicKeyAsPemStr(pubParsed)
return privParsedPem, pubParsedPem, nil
}
func EncryptRSA(message string, key rsa.PublicKey) (string, error) {
label := []byte("OAEP Encrypted")
rng := rand.Reader
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rng, &key, []byte(message), label)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func DecryptRSA(cipherText string, privateKey rsa.PrivateKey) (string, error) {
ct, _ := base64.StdEncoding.DecodeString(cipherText)
label := []byte("OAEP Encrypted")
rng := rand.Reader
plaintext, err := rsa.DecryptOAEP(sha256.New(), rng, &privateKey, ct, label)
if err != nil {
return "", err
}
return string(plaintext), nil
}

View File

@ -1,44 +1,86 @@
package db package db
import ( import (
"log" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/providers" "github.com/authorizerdev/authorizer/server/db/providers"
"github.com/authorizerdev/authorizer/server/db/providers/arangodb" "github.com/authorizerdev/authorizer/server/db/providers/arangodb"
"github.com/authorizerdev/authorizer/server/db/providers/cassandradb"
"github.com/authorizerdev/authorizer/server/db/providers/couchbase"
"github.com/authorizerdev/authorizer/server/db/providers/dynamodb"
"github.com/authorizerdev/authorizer/server/db/providers/mongodb" "github.com/authorizerdev/authorizer/server/db/providers/mongodb"
"github.com/authorizerdev/authorizer/server/db/providers/sql" "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 // Provider returns the current database provider
var Provider providers.Provider var Provider providers.Provider
func InitDB() { func InitDB() error {
var err error var err error
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv()
isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB && envs.DatabaseType != constants.DbTypeDynamoDB && envs.DatabaseType != constants.DbTypeCouchbaseDB
isArangoDB := envs.DatabaseType == constants.DbTypeArangodb
isMongoDB := envs.DatabaseType == constants.DbTypeMongodb
isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB || envs.DatabaseType == constants.DbTypeScyllaDB
isDynamoDB := envs.DatabaseType == constants.DbTypeDynamoDB
isCouchbaseDB := envs.DatabaseType == constants.DbTypeCouchbaseDB
if isSQL { if isSQL {
log.Info("Initializing SQL Driver for: ", envs.DatabaseType)
Provider, err = sql.NewProvider() Provider, err = sql.NewProvider()
if err != nil { if err != nil {
log.Fatal("=> error setting sql provider:", err) log.Fatal("Failed to initialize SQL driver: ", err)
return err
} }
} }
if isArangoDB { if isArangoDB {
log.Info("Initializing ArangoDB Driver")
Provider, err = arangodb.NewProvider() Provider, err = arangodb.NewProvider()
if err != nil { if err != nil {
log.Fatal("=> error setting arangodb provider:", err) log.Fatal("Failed to initialize ArangoDB driver: ", err)
return err
} }
} }
if isMongoDB { if isMongoDB {
log.Info("Initializing MongoDB Driver")
Provider, err = mongodb.NewProvider() Provider, err = mongodb.NewProvider()
if err != nil { if err != nil {
log.Fatal("=> error setting arangodb provider:", err) log.Fatal("Failed to initialize MongoDB driver: ", err)
return err
} }
} }
if isCassandra {
log.Info("Initializing CassandraDB Driver")
Provider, err = cassandradb.NewProvider()
if err != nil {
log.Fatal("Failed to initialize CassandraDB driver: ", err)
return err
}
}
if isDynamoDB {
log.Info("Initializing DynamoDB Driver for: ", envs.DatabaseType)
Provider, err = dynamodb.NewProvider()
if err != nil {
log.Fatal("Failed to initialize DynamoDB driver: ", err)
return err
}
}
if isCouchbaseDB {
log.Info("Initializing CouchbaseDB Driver for: ", envs.DatabaseType)
Provider, err = couchbase.NewProvider()
if err != nil {
log.Fatal("Failed to initialize Couchbase driver: ", err)
return err
}
}
return nil
} }

View File

@ -0,0 +1,16 @@
package models
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
// Authenticators model for db
type Authenticator struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" index:"user_id,hash"`
Method string `json:"method" bson:"method" cql:"method" dynamo:"method"`
Secret string `json:"secret" bson:"secret" cql:"secret" dynamo:"secret"`
RecoveryCodes *string `json:"recovery_codes" bson:"recovery_codes" cql:"recovery_codes" dynamo:"recovery_codes"`
VerifiedAt *int64 `json:"verified_at" bson:"verified_at" cql:"verified_at" dynamo:"verified_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

View File

@ -0,0 +1,37 @@
package models
import (
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/refs"
)
// EmailTemplate model for database
type EmailTemplate struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
Subject string `json:"subject" bson:"subject" cql:"subject" dynamo:"subject"`
Template string `json:"template" bson:"template" cql:"template" dynamo:"template"`
Design string `json:"design" bson:"design" cql:"design" dynamo:"design"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}
// AsAPIEmailTemplate to return email template as graphql response object
func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
id := e.ID
if strings.Contains(id, Collections.EmailTemplate+"/") {
id = strings.TrimPrefix(id, Collections.EmailTemplate+"/")
}
return &model.EmailTemplate{
ID: id,
EventName: e.EventName,
Subject: e.Subject,
Template: e.Template,
Design: e.Design,
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
}
}

View File

@ -1,11 +1,14 @@
package models package models
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
// Env model for db // Env model for db
type Env struct { type Env struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"` ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
EnvData string `gorm:"type:text" json:"env" bson:"env"` EnvData string `json:"env" bson:"env" cql:"env" dynamo:"env"`
Hash string `gorm:"type:text" json:"hash" bson:"hash"` Hash string `json:"hash" bson:"hash" cql:"hash" dynamo:"hash"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"` EncryptionKey string `json:"encryption_key" bson:"encryption_key" cql:"encryption_key" dynamo:"encryption_key"` // couchbase has "hash" as reserved keyword so we cannot use it. This will be empty for other dbs.
CreatedAt int64 `json:"created_at" bson:"created_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
} }

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