Compare commits

..

406 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
280 changed files with 20284 additions and 8653 deletions

View File

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

View File

@ -7,4 +7,9 @@ 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**)
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`
5. 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`
7. Build App `make build-app`
8. Build Server `make clean && make`
4. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
5. Build Dashboard `make build-dashboard`
6. Build App `make build-app`
7. Build Server `make clean && make`
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command. 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
@ -87,145 +105,145 @@ For manually testing using graphql playground, you can paste following queries a
```gql
mutation Signup {
signup(
params: {
email: "lakhan@yopmail.com"
password: "test"
confirm_password: "test"
given_name: "lakhan"
}
) {
message
user {
id
family_name
given_name
email
email_verified
}
}
signup(
params: {
email: "lakhan@yopmail.com"
password: "test"
confirm_password: "test"
given_name: "lakhan"
}
) {
message
user {
id
family_name
given_name
email
email_verified
}
}
}
mutation ResendEamil {
resend_verify_email(
params: { email: "lakhan@yopmail.com", identifier: "basic_auth_signup" }
) {
message
}
resend_verify_email(
params: { email: "lakhan@yopmail.com", identifier: "basic_auth_signup" }
) {
message
}
}
query GetVerifyRequests {
_verification_requests {
id
token
expires
identifier
}
_verification_requests {
id
token
expires
identifier
}
}
mutation VerifyEmail {
verify_email(params: { token: "" }) {
access_token
expires_at
user {
id
email
given_name
email_verified
}
}
verify_email(params: { token: "" }) {
access_token
expires_at
user {
id
email
given_name
email_verified
}
}
}
mutation Login {
login(params: { email: "lakhan@yopmail.com", password: "test" }) {
access_token
expires_at
user {
id
family_name
given_name
email
}
}
login(params: { email: "lakhan@yopmail.com", password: "test" }) {
access_token
expires_at
user {
id
family_name
given_name
email
}
}
}
query GetSession {
session {
access_token
expires_at
user {
id
given_name
family_name
email
email_verified
signup_methods
created_at
updated_at
}
}
session {
access_token
expires_at
user {
id
given_name
family_name
email
email_verified
signup_methods
created_at
updated_at
}
}
}
mutation ForgotPassword {
forgot_password(params: { email: "lakhan@yopmail.com" }) {
message
}
forgot_password(params: { email: "lakhan@yopmail.com" }) {
message
}
}
mutation ResetPassword {
reset_password(
params: { token: "", password: "test", confirm_password: "test" }
) {
message
}
reset_password(
params: { token: "", password: "test", confirm_password: "test" }
) {
message
}
}
mutation UpdateProfile {
update_profile(params: { family_name: "samani" }) {
message
}
update_profile(params: { family_name: "samani" }) {
message
}
}
query GetUsers {
_users {
id
email
email_verified
given_name
family_name
picture
signup_methods
phone_number
}
_users {
id
email
email_verified
given_name
family_name
picture
signup_methods
phone_number
}
}
mutation MagicLinkLogin {
magic_link_login(params: { email: "test@yopmail.com" }) {
message
}
magic_link_login(params: { email: "test@yopmail.com" }) {
message
}
}
mutation Logout {
logout {
message
}
logout {
message
}
}
mutation UpdateUser {
_update_user(
params: {
id: "dafc9400-d603-4ade-997c-83fcd54bbd67"
roles: ["user", "admin"]
}
) {
email
roles
}
_update_user(
params: {
id: "dafc9400-d603-4ade-997c-83fcd54bbd67"
roles: ["user", "admin"]
}
) {
email
roles
}
}
mutation DeleteUser {
_delete_user(params: { email: "signup.test134523@yopmail.com" }) {
message
}
_delete_user(params: { email: "signup.test134523@yopmail.com" }) {
message
}
}
```

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

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

View File

@ -62,12 +62,14 @@ jobs:
run: |
make clean && \
make build && \
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 && \
mkdir -p authorizer-${VERSION}-darwin-amd64/build authorizer-${VERSION}-darwin-amd64/app authorizer-${VERSION}-darwin-amd64/dashboard && cp build/darwin/amd64/server authorizer-${VERSION}-darwin-amd64/build/ && cp .env authorizer-${VERSION}-darwin-amd64/.env && cp -rf app/build authorizer-${VERSION}-darwin-amd64/app/build && cp -rf templates authorizer-${VERSION}-darwin-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-darwin-amd64/dashboard/build && tar cvfz authorizer-${VERSION}-darwin-amd64.tar.gz authorizer-${VERSION}-darwin-amd64 && \
mkdir -p authorizer-${VERSION}-linux-amd64/build authorizer-${VERSION}-linux-amd64/app authorizer-${VERSION}-linux-amd64/dashboard && cp build/linux/amd64/server authorizer-${VERSION}-linux-amd64/build/ && cp .env authorizer-${VERSION}-linux-amd64/.env && cp -rf app/build authorizer-${VERSION}-linux-amd64/app/build && cp -rf templates authorizer-${VERSION}-linux-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-linux-amd64/dashboard/build && tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz authorizer-${VERSION}-linux-amd64 && \
mkdir -p authorizer-${VERSION}-linux-arm64/build authorizer-${VERSION}-linux-arm64/app authorizer-${VERSION}-linux-arm64/dashboard && cp build/linux/arm64/server authorizer-${VERSION}-linux-arm64/build/ && cp .env authorizer-${VERSION}-linux-arm64/.env && cp -rf app/build authorizer-${VERSION}-linux-arm64/app/build && cp -rf templates authorizer-${VERSION}-linux-arm64/ && cp -rf dashboard/build authorizer-${VERSION}-linux-arm64/dashboard/build && tar cvfz authorizer-${VERSION}-linux-arm64.tar.gz authorizer-${VERSION}-linux-arm64 && \
mkdir -p authorizer-${VERSION}-windows-amd64/build authorizer-${VERSION}-windows-amd64/app authorizer-${VERSION}-windows-amd64/dashboard && cp build/windows/amd64/server.exe authorizer-${VERSION}-windows-amd64/build/ && cp .env authorizer-${VERSION}-windows-amd64/.env && cp -rf app/build authorizer-${VERSION}-windows-amd64/app/build && cp -rf templates authorizer-${VERSION}-windows-amd64/ && cp -rf dashboard/build authorizer-${VERSION}-windows-amd64/dashboard/build && zip -vr authorizer-${VERSION}-windows-amd64.zip authorizer-${VERSION}-windows-amd64
- name: Upload assets
run: |
github-assets-uploader -f authorizer-${VERSION}-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-arm64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}

2
.gitignore vendored
View File

@ -18,3 +18,5 @@ yalc.lock
certs/
*-shm
*-wal
.idea
*.iml

View File

@ -1,4 +1,4 @@
FROM golang:1.19.1-alpine as go-builder
FROM golang:1.21.3-alpine3.18 AS go-builder
WORKDIR /authorizer
COPY server server
COPY Makefile .
@ -11,7 +11,7 @@ RUN apk add build-base &&\
make clean && make && \
chmod 777 build/server
FROM node:17-alpine3.12 as node-builder
FROM node:20-alpine3.18 AS node-builder
WORKDIR /authorizer
COPY app app
COPY dashboard dashboard
@ -20,7 +20,7 @@ RUN apk add build-base &&\
make build-app && \
make build-dashboard
FROM alpine:latest
FROM alpine:3.18
RUN adduser -D -h /authorizer -u 1000 -k /dev/null authorizer
WORKDIR /authorizer
RUN mkdir app dashboard

View File

@ -5,7 +5,7 @@ cmd:
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
build:
cd server && gox \
-osarch="linux/amd64 linux/arm64 darwin/amd64 windows/amd64" \
-osarch="linux/amd64 linux/arm64 darwin/arm64 darwin/amd64 windows/amd64" \
-ldflags "-w -X main.VERSION=$(VERSION)" \
-output="../build/{{.OS}}/{{.Arch}}/server" \
./...
@ -26,23 +26,34 @@ test-scylladb:
cd server && go clean --testcache && TEST_DBS="scylladb" go test -p 1 -v ./test
docker rm -vf authorizer_scylla_db
test-arangodb:
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.10.3
cd server && go clean --testcache && TEST_DBS="arangodb" go test -p 1 -v ./test
docker rm -vf authorizer_arangodb
test-dynamodb:
docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest
cd server && go clean --testcache && TEST_DBS="dynamodb" go test -p 1 -v ./test
docker rm -vf dynamodb-local-test
test-couchbase:
docker run -d --name couchbase-local-test -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase:latest
sh scripts/couchbase-test.sh
cd server && go clean --testcache && TEST_DBS="couchbase" go test -p 1 -v ./test
docker rm -vf couchbase-local-test
test-all-db:
rm -rf server/test/test.db server/test/test.db-shm server/test/test.db-wal && rm -rf test.db test.db-shm test.db-wal
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
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
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb,dynamodb" go test -p 1 -v ./test
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
generate:
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' {} \;

View File

@ -68,6 +68,8 @@ Deploy production ready Authorizer instance using one click deployment options a
| 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

959
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

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

@ -27,13 +27,12 @@ export default function App() {
if (redirectURL) {
urlProps.redirectURL = redirectURL;
} else {
urlProps.redirectURL = window.location.origin + '/app';
urlProps.redirectURL = window.location.href;
}
const globalState: Record<string, string> = {
...window['__authorizer__'],
...urlProps,
};
return (
<div
style={{
@ -54,7 +53,7 @@ export default function App() {
<img
src={`${globalState.organizationLogo}`}
alt="logo"
style={{ height: 60, width: 60, objectFit: 'cover' }}
style={{ height: 60, objectFit: 'cover' }}
/>
<h1>{globalState.organizationName}</h1>
</div>

View File

@ -59,7 +59,9 @@ export default function Root({
useEffect(() => {
if (token) {
let redirectURL = config.redirectURL || '/app';
let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
// 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}`;

View File

@ -32,29 +32,35 @@ const FooterContent = styled.div`
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 (
<Fragment>
{view === VIEW_TYPES.LOGIN && (
<Fragment>
<h1 style={{ textAlign: 'center' }}>Login</h1>
<br />
<AuthorizerSocialLogin urlProps={urlProps} />
{config.is_basic_authentication_enabled &&
<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} />
)}
<Footer>
<Link
to="#"
onClick={() => setView(VIEW_TYPES.FORGOT_PASSWORD)}
style={{ marginBottom: 10 }}
>
Forgot Password?
</Link>
</Footer>
{(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 && (
@ -65,6 +71,9 @@ export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
...urlProps,
redirect_uri: `${window.location.origin}/app/reset-password`,
}}
onPasswordReset={() => {
setView(VIEW_TYPES.LOGIN);
}}
/>
<Footer>
<Link
@ -81,7 +90,7 @@ export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
!config.is_magic_link_login_enabled &&
config.is_sign_up_enabled && (
<FooterContent>
Don't have an account? <Link to="/app/signup"> Sign Up</Link>
Don't have an account? &nbsp; <Link to="/app/signup"> Sign Up</Link>
</FooterContent>
)}
</Fragment>

View File

@ -1,5 +1,5 @@
import React, { Fragment } from 'react';
import { AuthorizerSignup } from '@authorizerdev/authorizer-react';
import { AuthorizerSignup, AuthorizerSocialLogin } from '@authorizerdev/authorizer-react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
@ -19,6 +19,7 @@ export default function SignUp({
<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>

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"

File diff suppressed because it is too large Load Diff

BIN
dashboard/public/roblox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

View File

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

View File

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

View File

@ -104,14 +104,14 @@ const EditUserModal = ({
title: 'User data update failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (res.data?._update_user?.id) {
toast({
title: 'User data update successful',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
onClose();

View File

@ -126,6 +126,22 @@ const EmailConfigurations = ({
/>
</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>
);

View File

@ -8,105 +8,154 @@ const Features = ({ variables, setVariables }: any) => {
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Disable Features
Features
</Text>
<Stack spacing={6}>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Login Page:</Text>
<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">Disable Email Verification:</Text>
<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">Disable Magic Login Link:</Text>
<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">Disable Basic Authentication:</Text>
<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">Disable Sign Up:</Text>
<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">Disable Strong Password:</Text>
<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">
Disable Multi Factor Authentication (MFA):
</Text>
<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>
</Stack>
<Divider paddingY={5} />
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
Enable Features
</Text>
<Stack spacing={6}>
{!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">
@ -125,6 +174,19 @@ const Features = ({ variables, setVariables }: any) => {
/>
</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}>

View File

@ -43,7 +43,7 @@ const JSTConfigurations = ({
title: `JWT config copied successfully`,
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
} catch (err) {
console.error({
@ -54,14 +54,13 @@ const JSTConfigurations = ({
title: `Failed to copy JWT config`,
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
}
};
return (
<div>
{' '}
<Flex
borderRadius={5}
width="100%"

View File

@ -16,8 +16,17 @@ import {
FaLinkedin,
FaApple,
FaTwitter,
FaMicrosoft,
FaTwitch,
FaDiscord,
} from 'react-icons/fa';
import { TextInputType, HiddenInputType } from '../../constants';
import {
TextInputType,
HiddenInputType,
ResponseModes,
ResponseTypes,
SelectInputType,
} from '../../constants';
const OAuthConfig = ({
envVariables,
@ -69,6 +78,42 @@ const OAuthConfig = ({
/>
</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}>
@ -265,6 +310,44 @@ const OAuthConfig = ({
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<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'}
@ -303,6 +386,136 @@ const OAuthConfig = ({
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaMicrosoft />
</Center>
<Center
w={isNotSmallerScreen ? '35%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID}
placeholder="Microsoft Active Directory TenantID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '35%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.MICROSOFT_CLIENT_ID}
placeholder="Microsoft Client ID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.MICROSOFT_CLIENT_SECRET}
placeholder="Microsoft Client Secret"
/>
</Center>
</Flex>
<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>

View File

@ -73,7 +73,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
title: 'Error occurred generating jwt keys',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
closeHandler();
} else {
@ -107,7 +107,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
title: 'Error occurred setting jwt keys',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
return;
@ -116,7 +116,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
title: 'JWT keys updated successfully',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
closeHandler();
};

View File

@ -48,6 +48,8 @@ const InputField = ({
fieldVisibility,
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
}: any) => {
const props = {
@ -398,7 +400,9 @@ const InputField = ({
</Text>
<Switch
size="md"
isChecked={variables[inputType]}
isChecked={
hasReversedValue ? !variables[inputType] : variables[inputType]
}
onChange={() => {
setVariables({
...variables,

View File

@ -105,7 +105,7 @@ const InviteMembersModal = ({
title: 'Invites sent successfully!',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
setLoading(false);
updateUserList();
@ -117,7 +117,7 @@ const InviteMembersModal = ({
title: error?.message || 'Error occurred, try again!',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
setLoading(false);
}

View File

@ -173,7 +173,7 @@ const UpdateEmailTemplate = ({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (
res.data?._add_email_template ||
@ -186,7 +186,7 @@ const UpdateEmailTemplate = ({
),
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
setTemplateData({
...initTemplateData,

View File

@ -63,6 +63,7 @@ interface headersValidatorDataType {
interface selecetdWebhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
@ -86,6 +87,7 @@ const initHeadersValidatorData: headersValidatorDataType = {
interface webhookDataType {
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]: headersDataType[];
@ -98,6 +100,7 @@ interface validatorDataType {
const initWebhookData: webhookDataType = {
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames['User login'],
[WebhookInputDataFields.EVENT_DESCRIPTION]: '',
[WebhookInputDataFields.ENDPOINT]: '',
[WebhookInputDataFields.ENABLED]: true,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
@ -144,6 +147,9 @@ const UpdateWebhookModal = ({
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({
@ -246,6 +252,8 @@ const UpdateWebhookModal = ({
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],
@ -290,7 +298,7 @@ const UpdateWebhookModal = ({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (res.data?._add_webhook || res.data?._update_webhook) {
toast({
@ -299,7 +307,7 @@ const UpdateWebhookModal = ({
),
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
setWebhook({
...initWebhookData,
@ -402,7 +410,9 @@ const UpdateWebhookModal = ({
<Flex flex="3">
<Select
size="md"
value={webhook[WebhookInputDataFields.EVENT_NAME]}
value={
webhook[WebhookInputDataFields.EVENT_NAME].split('-')[0]
}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.EVENT_NAME,
@ -420,6 +430,30 @@ const UpdateWebhookModal = ({
</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"

View File

@ -9,7 +9,12 @@ export const TextInputType = {
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',
REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST',
@ -17,6 +22,7 @@ export const TextInputType = {
SMTP_USERNAME: 'SMTP_USERNAME',
SMTP_LOCAL_NAME: 'SMTP_LOCAL_NAME',
SENDER_EMAIL: 'SENDER_EMAIL',
SENDER_NAME: 'SENDER_NAME',
ORGANIZATION_NAME: 'ORGANIZATION_NAME',
ORGANIZATION_LOGO: 'ORGANIZATION_LOGO',
DATABASE_NAME: 'DATABASE_NAME',
@ -37,7 +43,11 @@ export const HiddenInputType = {
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',
SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET',
@ -54,6 +64,8 @@ export const ArrayInputType = {
export const SelectInputType = {
JWT_TYPE: 'JWT_TYPE',
GENDER: 'gender',
DEFAULT_AUTHORIZE_RESPONSE_TYPE: 'DEFAULT_AUTHORIZE_RESPONSE_TYPE',
DEFAULT_AUTHORIZE_RESPONSE_MODE: 'DEFAULT_AUTHORIZE_RESPONSE_MODE',
};
export const MultiSelectInputType = {
@ -73,11 +85,15 @@ export const SwitchInputType = {
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
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 = {
@ -118,8 +134,17 @@ export interface envVarTypes {
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] | [];
@ -135,6 +160,7 @@ export interface envVarTypes {
SMTP_PASSWORD: string;
SMTP_LOCAL_NAME: string;
SENDER_EMAIL: string;
SENDER_NAME: string;
ALLOWED_ORIGINS: [string] | [];
ORGANIZATION_NAME: string;
ORGANIZATION_LOGO: string;
@ -146,6 +172,7 @@ export interface envVarTypes {
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;
@ -155,6 +182,11 @@ export interface envVarTypes {
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 = {
@ -173,6 +205,7 @@ export const envSubViews = {
export enum WebhookInputDataFields {
ID = 'id',
EVENT_DESCRIPTION = 'event_description',
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
@ -207,6 +240,7 @@ export const webhookEventNames = {
'User deleted': 'user.deleted',
'User access enabled': 'user.access_enabled',
'User access revoked': 'user.access_revoked',
'User deactivated': 'user.deactivated',
};
export const emailTemplateEventNames = {
@ -342,3 +376,16 @@ 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

@ -30,8 +30,17 @@ export const EnvVariablesQuery = `
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
DEFAULT_ROLES
PROTECTED_ROLES
ROLES
@ -47,6 +56,7 @@ export const EnvVariablesQuery = `
SMTP_PASSWORD
SMTP_LOCAL_NAME
SENDER_EMAIL
SENDER_NAME
ALLOWED_ORIGINS
ORGANIZATION_NAME
ORGANIZATION_LOGO
@ -57,6 +67,7 @@ export const EnvVariablesQuery = `
DISABLE_MAGIC_LINK_LOGIN
DISABLE_EMAIL_VERIFICATION
DISABLE_BASIC_AUTHENTICATION
DISABLE_MOBILE_BASIC_AUTHENTICATION
DISABLE_SIGN_UP
DISABLE_STRONG_PASSWORD
DISABLE_REDIS_FOR_ENV
@ -67,6 +78,11 @@ export const EnvVariablesQuery = `
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
}
}
`;
@ -84,6 +100,7 @@ export const UserDetailsQuery = `
id
email
email_verified
phone_number_verified
given_name
family_name
middle_name
@ -115,6 +132,7 @@ export const WebhooksDataQuery = `
_webhooks(params: $params){
webhooks{
id
event_description
event_name
endpoint
enabled

View File

@ -57,7 +57,7 @@ export default function Auth() {
title: capitalizeFirstLetter(error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
});
}

View File

@ -50,8 +50,17 @@ const Environment = () => {
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: [],
DEFAULT_ROLES: [],
PROTECTED_ROLES: [],
@ -67,6 +76,7 @@ const Environment = () => {
SMTP_PASSWORD: '',
SMTP_LOCAL_NAME: '',
SENDER_EMAIL: '',
SENDER_NAME: '',
ALLOWED_ORIGINS: [],
ORGANIZATION_NAME: '',
ORGANIZATION_LOGO: '',
@ -78,6 +88,7 @@ const Environment = () => {
DISABLE_MAGIC_LINK_LOGIN: false,
DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_MOBILE_BASIC_AUTHENTICATION: false,
DISABLE_SIGN_UP: false,
DISABLE_STRONG_PASSWORD: false,
OLD_ADMIN_SECRET: '',
@ -87,6 +98,11 @@ const Environment = () => {
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<
@ -97,7 +113,9 @@ const Environment = () => {
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,
SMTP_PASSWORD: false,
ADMIN_SECRET: false,
@ -203,7 +221,7 @@ const Environment = () => {
} variables`,
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
};

View File

@ -165,14 +165,25 @@ export default function Users() {
};
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
.mutation(UpdateUser, {
params: {
id,
email,
email_verified: true,
},
params,
})
.toPromise();
if (res.error) {
@ -180,14 +191,14 @@ export default function Users() {
title: 'User verification failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (res.data?._update_user?.id) {
toast({
title: 'User verification successful',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
updateUserList();
@ -211,14 +222,14 @@ export default function Users() {
title: 'User access enable failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else {
toast({
title: 'User access enabled successfully',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
updateUserList();
@ -236,14 +247,14 @@ export default function Users() {
title: 'User access revoke failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else {
toast({
title: 'User access revoked successfully',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
updateUserList();
@ -268,7 +279,7 @@ export default function Users() {
} for user`,
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
updateUserList();
return;
@ -277,7 +288,7 @@ export default function Users() {
title: 'Multi factor authentication update failed for user',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
};
@ -298,7 +309,7 @@ export default function Users() {
<Table variant="simple">
<Thead>
<Tr>
<Th>Email</Th>
<Th>Email / Phone</Th>
<Th>Created At</Th>
<Th>Signup Methods</Th>
<Th>Roles</Th>
@ -314,10 +325,15 @@ export default function Users() {
</Thead>
<Tbody>
{userList.map((user: userDataTypes) => {
const { email_verified, created_at, ...rest }: any = user;
const {
email_verified,
phone_number_verified,
created_at,
...rest
}: any = user;
return (
<Tr key={user.id} style={{ fontSize: 14 }}>
<Td maxW="300">{user.email}</Td>
<Td maxW="300">{user.email || user.phone_number}</Td>
<Td>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
</Td>
@ -327,9 +343,15 @@ export default function Users() {
<Tag
size="sm"
variant="outline"
colorScheme={user.email_verified ? 'green' : 'yellow'}
colorScheme={
user.email_verified || user.phone_number_verified
? 'green'
: 'yellow'
}
>
{user.email_verified.toString()}
{(
user.email_verified || user.phone_number_verified
).toString()}
</Tag>
</Td>
<Td>
@ -368,13 +390,14 @@ export default function Users() {
</Flex>
</MenuButton>
<MenuList>
{!user.email_verified && (
<MenuItem
onClick={() => userVerificationHandler(user)}
>
Verify User
</MenuItem>
)}
{!user.email_verified &&
!user.phone_number_verified && (
<MenuItem
onClick={() => userVerificationHandler(user)}
>
Verify User
</MenuItem>
)}
<EditUserModal
user={rest}
updateUserList={updateUserList}

View File

@ -56,6 +56,7 @@ interface paginationPropTypes {
interface webhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
@ -134,6 +135,7 @@ const Webhooks = () => {
<Thead>
<Tr>
<Th>Event Name</Th>
<Th>Event Description</Th>
<Th>Endpoint</Th>
<Th>Enabled</Th>
<Th>Headers</Th>
@ -147,7 +149,10 @@ const Webhooks = () => {
style={{ fontSize: 14 }}
>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_NAME]}
{webhook[WebhookInputDataFields.EVENT_NAME].split('-')[0]}
</Td>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
</Td>
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
<Td>
@ -264,7 +269,7 @@ const Webhooks = () => {
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<Text>Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}

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=

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
}

View File

@ -3,8 +3,12 @@ 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
@ -15,6 +19,14 @@ const (
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

@ -5,4 +5,6 @@ const (
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"
// DbTypeSqlite is the sqlite database type
DbTypeSqlite = "sqlite"
// DbTypeLibSQL is the libsql / Turso database type
DbTypeLibSQL = "libsql"
// DbTypeMysql is the mysql database type
DbTypeMysql = "mysql"
// DbTypeSqlserver is the sqlserver database type
@ -27,6 +29,6 @@ const (
DbTypePlanetScaleDB = "planetscale"
// DbTypeDynamoDB is the Dynamo database type
DbTypeDynamoDB = "dynamodb"
// DbTypeSurrealDB is the SurrealDB database type
DbTypeSurrealDB = "surrealdb"
// DbTypeCouchbaseDB is the Couchbase database type
DbTypeCouchbaseDB = "couchbase"
)

View File

@ -43,6 +43,13 @@ const (
EnvKeyDatabaseCertKey = "DATABASE_CERT_KEY"
// EnvKeyDatabaseCACert key for env variable DATABASE_CA_CERT
EnvKeyDatabaseCACert = "DATABASE_CA_CERT"
// EnvCouchbaseBucket key for env variable COUCHBASE_BUCKET
EnvCouchbaseBucket = "COUCHBASE_BUCKET"
// EnvCouchbaseBucketRAMQuotaMB key for env variable COUCHBASE_BUCKET_RAM_QUOTA
// This value should be parsed as number
EnvCouchbaseBucketRAMQuotaMB = "COUCHBASE_BUCKET_RAM_QUOTA"
// EnvCouchbaseBucket key for env variable COUCHBASE_SCOPE
EnvCouchbaseScope = "COUCHBASE_SCOPE"
// EnvKeySmtpHost key for env variable SMTP_HOST
EnvKeySmtpHost = "SMTP_HOST"
// EnvKeySmtpPort key for env variable SMTP_PORT
@ -55,8 +62,12 @@ const (
EnvKeySmtpLocalName = "SMTP_LOCAL_NAME"
// EnvKeySenderEmail key for env variable 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
@ -97,10 +108,28 @@ const (
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 = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
@ -125,6 +154,8 @@ const (
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
EnvKeyDisableBasicAuthentication = "DISABLE_BASIC_AUTHENTICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_MOBILE_BASIC_AUTH
EnvKeyDisableMobileBasicAuthentication = "DISABLE_MOBILE_BASIC_AUTHENTICATION"
// EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
@ -141,6 +172,18 @@ const (
// 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
@ -151,4 +194,22 @@ const (
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

@ -16,4 +16,7 @@ const (
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

@ -16,4 +16,12 @@ const (
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
TwitterUserInfoURL = "https://api.twitter.com/2/users/me?user.fields=id,name,profile_image_url,username"
// 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

@ -15,4 +15,6 @@ const (
UserAccessEnabledWebhookEvent = `user.access_enabled`
// UserDeletedWebhookEvent name for user deleted event
UserDeletedWebhookEvent = `user.deleted`
// UserDeactivatedWebhookEvent name for user deactivated event
UserDeactivatedWebhookEvent = `user.deactivated`
)

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,7 +1,9 @@
package crypto
import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/json"
"github.com/authorizerdev/authorizer/server/constants"
@ -125,12 +127,27 @@ func EncryptEnvData(data map[string]interface{}) (string, error) {
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) {
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
passwordSHA256 := getSHA256(password)
pw, err := bcrypt.GenerateFromPassword([]byte(passwordSHA256), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pw), nil
}

View File

@ -3,7 +3,9 @@ package crypto
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
)
@ -116,3 +118,24 @@ func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, st
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

@ -7,10 +7,10 @@ import (
"github.com/authorizerdev/authorizer/server/db/providers"
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
"github.com/authorizerdev/authorizer/server/db/providers/cassandradb"
"github.com/authorizerdev/authorizer/server/db/providers/couchbase"
"github.com/authorizerdev/authorizer/server/db/providers/dynamodb"
"github.com/authorizerdev/authorizer/server/db/providers/mongodb"
"github.com/authorizerdev/authorizer/server/db/providers/sql"
"github.com/authorizerdev/authorizer/server/db/providers/surrealdb"
"github.com/authorizerdev/authorizer/server/memorystore"
)
@ -22,12 +22,12 @@ func InitDB() error {
envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv()
isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB && envs.DatabaseType != constants.DbTypeDynamoDB && envs.DatabaseType != constants.DbTypeSurrealDB
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
isSurrealDB := envs.DatabaseType == constants.DbTypeSurrealDB
isCouchbaseDB := envs.DatabaseType == constants.DbTypeCouchbaseDB
if isSQL {
log.Info("Initializing SQL Driver for: ", envs.DatabaseType)
@ -37,7 +37,6 @@ func InitDB() error {
return err
}
}
if isArangoDB {
log.Info("Initializing ArangoDB Driver")
Provider, err = arangodb.NewProvider()
@ -74,11 +73,11 @@ func InitDB() error {
}
}
if isSurrealDB {
log.Info("Initializing Surreal Driver")
Provider, err = surrealdb.NewProvider()
if isCouchbaseDB {
log.Info("Initializing CouchbaseDB Driver for: ", envs.DatabaseType)
Provider, err = couchbase.NewProvider()
if err != nil {
log.Fatal("Failed to initialize Surreal driver: ", err)
log.Fatal("Failed to initialize Couchbase driver: ", err)
return err
}
}

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

@ -1,7 +1,6 @@
package models
import (
"encoding/json"
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
@ -36,10 +35,3 @@ func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
}
}
func (e *EmailTemplate) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(e) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@ -1,22 +1,14 @@
package models
import "encoding/json"
// 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
type Env 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"`
EnvData string `json:"env" bson:"env" cql:"env" dynamo:"env"`
Hash string `json:"hash" bson:"hash" cql:"hash" dynamo:"hash"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
}
func (env *Env) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(env) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
EnvData string `json:"env" bson:"env" cql:"env" dynamo:"env"`
Hash string `json:"hash" bson:"hash" cql:"hash" dynamo:"hash"`
EncryptionKey string `json:"encryption_key" bson:"encryption_key" cql:"encryption_key" dynamo:"encryption_key"` // couchbase has "hash" as reserved keyword so we cannot use it. This will be empty for other dbs.
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
}

View File

@ -1,33 +1,33 @@
package models
// Collections / Tables available for authorizer in the database
// CollectionList / Tables available for authorizer in the database
type CollectionList struct {
User string
VerificationRequest string
Session string
Env string
Webhook string
WebhookLog string
EmailTemplate string
OTP string
User string
VerificationRequest string
Session string
Env string
Webhook string
WebhookLog string
EmailTemplate string
OTP string
SMSVerificationRequest string
Authenticators string
}
var (
// DB/Namespace
DBNamespace = "authorizer"
// Identifier field used for surreal db
SurrealDbIdentifier = "identifier"
// Prefix for table name / collection names
Prefix = "authorizer_"
// Collections / Tables available for authorizer in the database (used for dbs other than gorm)
Collections = CollectionList{
User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Env: Prefix + "env",
Webhook: Prefix + "webhooks",
WebhookLog: Prefix + "webhook_logs",
EmailTemplate: Prefix + "email_templates",
OTP: Prefix + "otps",
User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Env: Prefix + "env",
Webhook: Prefix + "webhooks",
WebhookLog: Prefix + "webhook_logs",
EmailTemplate: Prefix + "email_templates",
OTP: Prefix + "otps",
SMSVerificationRequest: Prefix + "sms_verification_requests",
Authenticators: Prefix + "authenticators",
}
)

View File

@ -1,25 +1,24 @@
package models
import "encoding/json"
const (
// FieldName email is the field name for email
FieldNameEmail = "email"
// FieldNamePhoneNumber is the field name for phone number
FieldNamePhoneNumber = "phone_number"
)
// OTP model for database
type OTP 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"`
Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
Email string `gorm:"index" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
PhoneNumber string `gorm:"index" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}
type Paging struct {
ID string `json:"id,omitempty" dynamo:"id,hash"`
}
func (o *OTP) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(o) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@ -1,7 +1,5 @@
package models
import "encoding/json"
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
// Session model for db
@ -14,10 +12,3 @@ type Session struct {
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}
func (s *Session) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(s) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@ -15,7 +15,7 @@ type User 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"`
Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
Email *string `gorm:"index" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at" dynamo:"email_verified_at"`
Password *string `json:"password" bson:"password" cql:"password" dynamo:"password"`
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods" dynamo:"signup_methods"`
@ -33,12 +33,14 @@ type User struct {
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled" dynamo:"is_multi_factor_auth_enabled"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
AppData *string `json:"app_data" bson:"app_data" cql:"app_data" dynamo:"app_data"`
}
func (user *User) AsAPIUser() *model.User {
isEmailVerified := user.EmailVerifiedAt != nil
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
appDataMap := make(map[string]interface{})
json.Unmarshal([]byte(refs.StringValue(user.AppData)), &appDataMap)
// id := user.ID
// if strings.Contains(id, Collections.User+"/") {
// id = strings.TrimPrefix(id, Collections.User+"/")
@ -52,7 +54,7 @@ func (user *User) AsAPIUser() *model.User {
FamilyName: user.FamilyName,
MiddleName: user.MiddleName,
Nickname: user.Nickname,
PreferredUsername: refs.NewStringRef(user.Email),
PreferredUsername: user.Email,
Gender: user.Gender,
Birthdate: user.Birthdate,
PhoneNumber: user.PhoneNumber,
@ -63,6 +65,7 @@ func (user *User) AsAPIUser() *model.User {
IsMultiFactorAuthEnabled: user.IsMultiFactorAuthEnabled,
CreatedAt: refs.NewInt64Ref(user.CreatedAt),
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
AppData: appDataMap,
}
}

View File

@ -1,7 +1,6 @@
package models
import (
"encoding/json"
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
@ -42,10 +41,3 @@ func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequ
UpdatedAt: refs.NewInt64Ref(v.UpdatedAt),
}
}
func (v *VerificationRequest) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(v) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@ -10,42 +10,42 @@ import (
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
// Event name has been kept unique as per initial design. But later on decided that we can have
// multiple hooks for same event so will be in a pattern `event_name-TIMESTAMP`
// Webhook model for db
type Webhook 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"`
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
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"`
EventDescription string `json:"event_description" bson:"event_description" cql:"event_description" dynamo:"event_description"`
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}
// AsAPIWebhook to return webhook as graphql response object
func (w *Webhook) AsAPIWebhook() *model.Webhook {
headersMap := make(map[string]interface{})
json.Unmarshal([]byte(w.Headers), &headersMap)
id := w.ID
if strings.Contains(id, Collections.Webhook+"/") {
id = strings.TrimPrefix(id, Collections.Webhook+"/")
}
// set default title to event name without dot(.)
if w.EventDescription == "" {
w.EventDescription = strings.Join(strings.Split(w.EventName, "."), " ")
}
return &model.Webhook{
ID: id,
EventName: refs.NewStringRef(w.EventName),
Endpoint: refs.NewStringRef(w.EndPoint),
Headers: headersMap,
Enabled: refs.NewBoolRef(w.Enabled),
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
ID: id,
EventName: refs.NewStringRef(w.EventName),
EventDescription: refs.NewStringRef(w.EventDescription),
Endpoint: refs.NewStringRef(w.EndPoint),
Headers: headersMap,
Enabled: refs.NewBoolRef(w.Enabled),
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
}
}
func (w *Webhook) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(w) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@ -1,7 +1,6 @@
package models
import (
"encoding/json"
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
@ -38,10 +37,3 @@ func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog {
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
}
}
func (w *WebhookLog) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(w) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@ -0,0 +1,78 @@
package arangodb
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
)
func (p *provider) AddAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
exists, _ := p.GetAuthenticatorDetailsByUserId(ctx, authenticators.UserID, authenticators.Method)
if exists != nil {
return authenticators, nil
}
if authenticators.ID == "" {
authenticators.ID = uuid.New().String()
}
authenticators.Key = authenticators.ID
authenticators.CreatedAt = time.Now().Unix()
authenticators.UpdatedAt = time.Now().Unix()
authenticatorsCollection, _ := p.db.Collection(ctx, models.Collections.Authenticators)
meta, err := authenticatorsCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), authenticators)
if err != nil {
return nil, err
}
authenticators.Key = meta.Key
authenticators.ID = meta.ID.String()
return authenticators, nil
}
func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
authenticators.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(ctx, models.Collections.Authenticators)
meta, err := collection.UpdateDocument(ctx, authenticators.Key, authenticators)
if err != nil {
return nil, err
}
authenticators.Key = meta.Key
authenticators.ID = meta.ID.String()
return authenticators, nil
}
func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
var authenticators *models.Authenticator
query := fmt.Sprintf("FOR d in %s FILTER d.user_id == @user_id AND d.method == @method LIMIT 1 RETURN d", models.Collections.Authenticators)
bindVars := map[string]interface{}{
"user_id": userId,
"method": authenticatorType,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if authenticators == nil {
return authenticators, fmt.Errorf("authenticator not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &authenticators)
if err != nil {
return nil, err
}
}
return authenticators, nil
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
@ -13,16 +12,14 @@ import (
)
// AddEmailTemplate to add EmailTemplate
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
if emailTemplate.ID == "" {
emailTemplate.ID = uuid.New().String()
emailTemplate.Key = emailTemplate.ID
}
emailTemplate.Key = emailTemplate.ID
emailTemplate.CreatedAt = time.Now().Unix()
emailTemplate.UpdatedAt = time.Now().Unix()
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
_, err := emailTemplateCollection.CreateDocument(ctx, emailTemplate)
if err != nil {
@ -32,74 +29,63 @@ func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.Em
}
// UpdateEmailTemplate to update EmailTemplate
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
emailTemplate.UpdatedAt = time.Now().Unix()
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
meta, err := emailTemplateCollection.UpdateDocument(ctx, emailTemplate.Key, emailTemplate)
if err != nil {
return nil, err
}
emailTemplate.Key = meta.Key
emailTemplate.ID = meta.ID.String()
return emailTemplate.AsAPIEmailTemplate(), nil
}
// ListEmailTemplates to list EmailTemplate
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
func (p *provider) ListEmailTemplate(ctx context.Context, pagination *model.Pagination) (*model.EmailTemplates, error) {
emailTemplates := []*model.EmailTemplate{}
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.EmailTemplate, pagination.Offset, pagination.Limit)
sctx := driver.WithQueryFullCount(ctx)
sctx := arangoDriver.WithQueryFullCount(ctx)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var emailTemplate models.EmailTemplate
var emailTemplate *models.EmailTemplate
meta, err := cursor.ReadDocument(ctx, &emailTemplate)
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return nil, err
}
if meta.Key != "" {
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
}
}
return &model.EmailTemplates{
Pagination: &paginationClone,
Pagination: paginationClone,
EmailTemplates: emailTemplates,
}, nil
}
// GetEmailTemplateByID to get EmailTemplate by id
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
var emailTemplate models.EmailTemplate
var emailTemplate *models.EmailTemplate
query := fmt.Sprintf("FOR d in %s FILTER d._key == @email_template_id RETURN d", models.Collections.EmailTemplate)
bindVars := map[string]interface{}{
"email_template_id": emailTemplateID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if emailTemplate.Key == "" {
if emailTemplate == nil {
return nil, fmt.Errorf("email template not found")
}
break
@ -114,21 +100,19 @@ func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID str
// GetEmailTemplateByEventName to get EmailTemplate by event_name
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
var emailTemplate models.EmailTemplate
var emailTemplate *models.EmailTemplate
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.EmailTemplate)
bindVars := map[string]interface{}{
"event_name": eventName,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if emailTemplate.Key == "" {
if emailTemplate == nil {
return nil, fmt.Errorf("email template not found")
}
break

View File

@ -12,7 +12,7 @@ import (
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
env.Key = env.ID
@ -23,7 +23,7 @@ func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, erro
configCollection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), env)
if err != nil {
return env, err
return nil, err
}
env.Key = meta.Key
env.ID = meta.ID.String()
@ -31,12 +31,12 @@ func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, erro
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
env.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := collection.UpdateDocument(ctx, env.Key, env)
if err != nil {
return env, err
return nil, err
}
env.Key = meta.Key
@ -45,26 +45,24 @@ func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, e
}
// GetEnv to get environment information from database
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
var env *models.Env
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env)
cursor, err := p.db.Query(ctx, query, nil)
if err != nil {
return env, err
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if env.Key == "" {
if env == nil {
return env, fmt.Errorf("config not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &env)
if err != nil {
return env, err
return nil, err
}
}

View File

@ -2,6 +2,7 @@ package arangodb
import (
"context"
"errors"
"fmt"
"time"
@ -12,27 +13,39 @@ import (
// UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email)
// check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false
if otp == nil {
id := uuid.NewString()
otp = &models.OTP{
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
}
shouldCreate = true
} else {
otp.Otp = otpParam.Otp
otp.ExpiresAt = otpParam.ExpiresAt
}
otp.UpdatedAt = time.Now().Unix()
otpCollection, _ := p.db.Collection(ctx, models.Collections.OTP)
var meta driver.DocumentMeta
var err error
if shouldCreate {
@ -40,11 +53,9 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
} else {
meta, err = otpCollection.UpdateDocument(ctx, otp.Key, otp)
}
if err != nil {
return nil, err
}
otp.Key = meta.Key
otp.ID = meta.ID.String()
return otp, nil
@ -52,22 +63,20 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
// GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
var otp models.OTP
var otp *models.OTP
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.OTP)
bindVars := map[string]interface{}{
"email": emailAddress,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if otp.Key == "" {
return nil, fmt.Errorf("email template not found")
if otp == nil {
return nil, fmt.Errorf("otp with given email not found")
}
break
}
@ -76,8 +85,34 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod
return nil, err
}
}
return otp, nil
}
return &otp, nil
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
var otp *models.OTP
query := fmt.Sprintf("FOR d in %s FILTER d.phone_number == @phone_number RETURN d", models.Collections.OTP)
bindVars := map[string]interface{}{
"phone_number": phoneNumber,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if otp == nil {
return nil, fmt.Errorf("otp with given phone_number not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &otp)
if err != nil {
return nil, err
}
}
return otp, nil
}
// DeleteOTP to delete otp
@ -87,6 +122,5 @@ func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
if err != nil {
return err
}
return nil
}

View File

@ -2,8 +2,11 @@ package arangodb
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/http"
"github.com/authorizerdev/authorizer/server/db/models"
@ -22,44 +25,73 @@ type provider struct {
func NewProvider() (*provider, error) {
ctx := context.Background()
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
conn, err := http.NewConnection(http.ConnectionConfig{
dbUsername := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseUsername
dbPassword := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePassword
dbCACertificate := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseCACert
httpConfig := http.ConnectionConfig{
Endpoints: []string{dbURL},
})
}
// If ca certificate if present, create tls config
if dbCACertificate != "" {
caCert, err := base64.StdEncoding.DecodeString(dbCACertificate)
if err != nil {
return nil, err
}
// Prepare TLS Config
tlsConfig := &tls.Config{}
certPool := x509.NewCertPool()
if success := certPool.AppendCertsFromPEM(caCert); !success {
return nil, fmt.Errorf("invalid certificate")
}
tlsConfig.RootCAs = certPool
httpConfig.TLSConfig = tlsConfig
}
// Create new http connection
conn, err := http.NewConnection(httpConfig)
if err != nil {
return nil, err
}
arangoClient, err := arangoDriver.NewClient(arangoDriver.ClientConfig{
clientConfig := arangoDriver.ClientConfig{
Connection: conn,
})
}
if dbUsername != "" && dbPassword != "" {
clientConfig.Authentication = arangoDriver.BasicAuthentication(dbUsername, dbPassword)
}
arangoClient, err := arangoDriver.NewClient(clientConfig)
if err != nil {
return nil, err
}
var arangodb driver.Database
var arangodb arangoDriver.Database
dbName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseName
arangodb_exists, err := arangoClient.DatabaseExists(nil, dbName)
arangodb_exists, err := arangoClient.DatabaseExists(ctx, dbName)
if err != nil {
return nil, err
}
if arangodb_exists {
arangodb, err = arangoClient.Database(nil, dbName)
arangodb, err = arangoClient.Database(ctx, dbName)
if err != nil {
return nil, err
}
} else {
arangodb, err = arangoClient.CreateDatabase(nil, dbName, nil)
arangodb, err = arangoClient.CreateDatabase(ctx, dbName, nil)
if err != nil {
return nil, err
}
}
userCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.User)
if err != nil {
return nil, err
}
if !userCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.User, nil)
if err != nil {
return nil, err
}
}
userCollection, _ := arangodb.Collection(nil, models.Collections.User)
userCollection, err := arangodb.Collection(ctx, models.Collections.User)
if err != nil {
return nil, err
}
userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
@ -70,14 +102,19 @@ func NewProvider() (*provider, error) {
})
verificationRequestCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.VerificationRequest)
if err != nil {
return nil, err
}
if !verificationRequestCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.VerificationRequest, nil)
if err != nil {
return nil, err
}
}
verificationRequestCollection, _ := arangodb.Collection(nil, models.Collections.VerificationRequest)
verificationRequestCollection, err := arangodb.Collection(ctx, models.Collections.VerificationRequest)
if err != nil {
return nil, err
}
verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
@ -87,81 +124,126 @@ func NewProvider() (*provider, error) {
})
sessionCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Session)
if err != nil {
return nil, err
}
if !sessionCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.Session, nil)
if err != nil {
return nil, err
}
}
sessionCollection, _ := arangodb.Collection(nil, models.Collections.Session)
sessionCollection, err := arangodb.Collection(ctx, models.Collections.Session)
if err != nil {
return nil, err
}
sessionCollection.EnsureHashIndex(ctx, []string{"user_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
configCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Env)
if !configCollectionExists {
envCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Env)
if err != nil {
return nil, err
}
if !envCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.Env, nil)
if err != nil {
return nil, err
}
}
webhookCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Webhook)
if err != nil {
return nil, err
}
if !webhookCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.Webhook, nil)
if err != nil {
return nil, err
}
}
webhookCollection, _ := arangodb.Collection(nil, models.Collections.Webhook)
webhookCollection, err := arangodb.Collection(ctx, models.Collections.Webhook)
if err != nil {
return nil, err
}
webhookCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
webhookLogCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.WebhookLog)
if err != nil {
return nil, err
}
if !webhookLogCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.WebhookLog, nil)
if err != nil {
return nil, err
}
}
webhookLogCollection, _ := arangodb.Collection(nil, models.Collections.WebhookLog)
webhookLogCollection, err := arangodb.Collection(ctx, models.Collections.WebhookLog)
if err != nil {
return nil, err
}
webhookLogCollection.EnsureHashIndex(ctx, []string{"webhook_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate)
if err != nil {
return nil, err
}
if !emailTemplateCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.EmailTemplate, nil)
if err != nil {
return nil, err
}
}
emailTemplateCollection, _ := arangodb.Collection(nil, models.Collections.EmailTemplate)
emailTemplateCollection, err := arangodb.Collection(ctx, models.Collections.EmailTemplate)
if err != nil {
return nil, err
}
emailTemplateCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
otpCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.OTP)
if err != nil {
return nil, err
}
if !otpCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.OTP, nil)
if err != nil {
return nil, err
}
}
otpCollection, _ := arangodb.Collection(nil, models.Collections.OTP)
otpCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
otpCollection, err := arangodb.Collection(ctx, models.Collections.OTP)
if err != nil {
return nil, err
}
otpCollection.EnsureHashIndex(ctx, []string{models.FieldNameEmail, models.FieldNamePhoneNumber}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
//authenticators table define
authenticatorsCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Authenticators)
if err != nil {
return nil, err
}
if !authenticatorsCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.Authenticators, nil)
if err != nil {
return nil, err
}
}
authenticatorsCollection, err := arangodb.Collection(ctx, models.Collections.Authenticators)
if err != nil {
return nil, err
}
authenticatorsCollection.EnsureHashIndex(ctx, []string{"user_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
return &provider{
db: arangodb,
}, err

View File

@ -9,12 +9,11 @@ import (
)
// AddSession to save session information in database
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
session.Key = session.ID
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection, _ := p.db.Collection(ctx, models.Collections.Session)
@ -24,3 +23,8 @@ func (p *provider) AddSession(ctx context.Context, session models.Session) error
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
return nil
}

View File

@ -7,7 +7,6 @@ import (
"strings"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/google/uuid"
@ -15,10 +14,11 @@ import (
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
)
// AddUser to save user information in database
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
user.Key = user.ID
@ -27,17 +27,27 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return user, err
return nil, err
}
user.Roles = defaultRoles
}
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given phone number already exists")
}
} else if user.Email != nil && strings.TrimSpace(refs.StringValue(user.Email)) != "" {
if u, _ := p.GetUserByEmail(ctx, refs.StringValue(user.Email)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given email already exists")
}
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
userCollection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), user)
if err != nil {
return user, err
return nil, err
}
user.Key = meta.Key
user.ID = meta.ID.String()
@ -46,12 +56,13 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.User, error) {
user.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := collection.UpdateDocument(ctx, user.Key, user)
if err != nil {
return user, err
return nil, err
}
user.Key = meta.Key
@ -60,13 +71,12 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
func (p *provider) DeleteUser(ctx context.Context, user *models.User) error {
collection, _ := p.db.Collection(ctx, models.Collections.User)
_, err := collection.RemoveDocument(ctx, user.Key)
if err != nil {
return err
}
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @user_id REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
bindVars := map[string]interface{}{
"user_id": user.Key,
@ -76,106 +86,91 @@ func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
return err
}
defer cursor.Close()
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination) (*model.Users, error) {
var users []*model.User
sctx := driver.WithQueryFullCount(ctx)
sctx := arangoDriver.WithQueryFullCount(ctx)
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.User, pagination.Offset, pagination.Limit)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var user models.User
var user *models.User
meta, err := cursor.ReadDocument(ctx, &user)
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return nil, err
}
if meta.Key != "" {
users = append(users, user.AsAPIUser())
}
}
return &model.Users{
Pagination: &paginationClone,
Pagination: paginationClone,
Users: users,
}, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
var user models.User
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user *models.User
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.User)
bindVars := map[string]interface{}{
"email": email,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return user, err
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user.Key == "" {
return user, fmt.Errorf("user not found")
if user == nil {
return nil, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return user, err
return nil, err
}
}
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
var user models.User
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
var user *models.User
query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", models.Collections.User)
bindVars := map[string]interface{}{
"id": id,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return user, err
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user.Key == "" {
return user, fmt.Errorf("user not found")
if user == nil {
return nil, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return user, err
return nil, err
}
}
return user, nil
}
@ -184,14 +179,12 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, err
func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error {
// set updated_at time for all users
data["updated_at"] = time.Now().Unix()
userInfoBytes, err := json.Marshal(data)
if err != nil {
return err
}
query := ""
if ids != nil && len(ids) > 0 {
if len(ids) > 0 {
keysArray := ""
for _, id := range ids {
keysArray += fmt.Sprintf("'%s', ", id)
@ -202,12 +195,36 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
} else {
query = fmt.Sprintf("FOR u IN %s UPDATE u._key with %s IN %s", models.Collections.User, string(userInfoBytes), models.Collections.User)
}
_, err = p.db.Query(ctx, query, nil)
if err != nil {
return err
}
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user *models.User
query := fmt.Sprintf("FOR d in %s FILTER d.phone_number == @phone_number RETURN d", models.Collections.User)
bindVars := map[string]interface{}{
"phone_number": phoneNumber,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user == nil {
return nil, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return nil, err
}
}
return user, nil
}

View File

@ -5,114 +5,102 @@ import (
"fmt"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) (*models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
verificationRequest.Key = verificationRequest.ID
}
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
verificationRequestRequestCollection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest)
meta, err := verificationRequestRequestCollection.CreateDocument(ctx, verificationRequest)
if err != nil {
return verificationRequest, err
return nil, err
}
verificationRequest.Key = meta.Key
verificationRequest.ID = meta.ID.String()
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (*models.VerificationRequest, error) {
var verificationRequest *models.VerificationRequest
query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", models.Collections.VerificationRequest)
bindVars := map[string]interface{}{
"token": token,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return verificationRequest, err
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if verificationRequest.Key == "" {
if verificationRequest == nil {
return verificationRequest, fmt.Errorf("verification request not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil {
return verificationRequest, err
return nil, err
}
}
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (*models.VerificationRequest, error) {
var verificationRequest *models.VerificationRequest
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", models.Collections.VerificationRequest)
bindVars := map[string]interface{}{
"email": email,
"identifier": identifier,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return verificationRequest, err
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if verificationRequest.Key == "" {
if verificationRequest == nil {
return verificationRequest, fmt.Errorf("verification request not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil {
return verificationRequest, err
return nil, err
}
}
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
func (p *provider) ListVerificationRequests(ctx context.Context, pagination *model.Pagination) (*model.VerificationRequests, error) {
var verificationRequests []*model.VerificationRequest
sctx := driver.WithQueryFullCount(ctx)
sctx := arangoDriver.WithQueryFullCount(ctx)
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.VerificationRequest, pagination.Offset, pagination.Limit)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var verificationRequest models.VerificationRequest
var verificationRequest *models.VerificationRequest
meta, err := cursor.ReadDocument(ctx, &verificationRequest)
if driver.IsNoMoreDocuments(err) {
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return nil, err
@ -123,17 +111,16 @@ func (p *provider) ListVerificationRequests(ctx context.Context, pagination mode
}
}
return &model.VerificationRequests{
VerificationRequests: verificationRequests,
Pagination: &paginationClone,
Pagination: paginationClone,
}, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
collection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) error {
collection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest)
_, err := collection.RemoveDocument(ctx, verificationRequest.Key)
if err != nil {
return err
}

View File

@ -3,6 +3,7 @@ package arangodb
import (
"context"
"fmt"
"strings"
"time"
"github.com/arangodb/go-driver"
@ -13,13 +14,14 @@ import (
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
func (p *provider) AddWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
webhook.Key = webhook.ID
}
webhook.Key = webhook.ID
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
@ -31,39 +33,37 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
func (p *provider) UpdateWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
meta, err := webhookCollection.UpdateDocument(ctx, webhook.Key, webhook)
if err != nil {
return nil, err
}
webhook.Key = meta.Key
webhook.ID = meta.ID.String()
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
func (p *provider) ListWebhook(ctx context.Context, pagination *model.Pagination) (*model.Webhooks, error) {
webhooks := []*model.Webhook{}
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.Webhook, pagination.Offset, pagination.Limit)
sctx := driver.WithQueryFullCount(ctx)
sctx := arangoDriver.WithQueryFullCount(ctx)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var webhook models.Webhook
var webhook *models.Webhook
meta, err := cursor.ReadDocument(ctx, &webhook)
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
@ -76,28 +76,26 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
}
return &model.Webhooks{
Pagination: &paginationClone,
Pagination: paginationClone,
Webhooks: webhooks,
}, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
var webhook *models.Webhook
query := fmt.Sprintf("FOR d in %s FILTER d._key == @webhook_id RETURN d", models.Collections.Webhook)
bindVars := map[string]interface{}{
"webhook_id": webhookID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if webhook.Key == "" {
if webhook == nil {
return nil, fmt.Errorf("webhook not found")
}
break
@ -111,32 +109,28 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.Webhook)
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
query := fmt.Sprintf("FOR d in %s FILTER d.event_name LIKE @event_name RETURN d", models.Collections.Webhook)
bindVars := map[string]interface{}{
"event_name": eventName,
"event_name": eventName + "%",
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
webhooks := []*model.Webhook{}
for {
if !cursor.HasMore() {
if webhook.Key == "" {
return nil, fmt.Errorf("webhook not found")
}
var webhook *models.Webhook
if _, err := cursor.ReadDocument(ctx, &webhook); driver.IsNoMoreDocuments(err) {
// We're done
break
}
_, err := cursor.ReadDocument(ctx, &webhook)
if err != nil {
} else if err != nil {
return nil, err
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
return webhook.AsAPIWebhook(), nil
return webhooks, nil
}
// DeleteWebhook to delete webhook
@ -146,17 +140,14 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
if err != nil {
return err
}
query := fmt.Sprintf("FOR d IN %s FILTER d.webhook_id == @webhook_id REMOVE { _key: d._key } IN %s", models.Collections.WebhookLog, models.Collections.WebhookLog)
bindVars := map[string]interface{}{
"webhook_id": webhook.ID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return err
}
defer cursor.Close()
return nil
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
@ -13,12 +12,11 @@ import (
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog *models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
webhookLog.Key = webhookLog.ID
}
webhookLog.Key = webhookLog.ID
webhookLog.CreatedAt = time.Now().Unix()
webhookLog.UpdatedAt = time.Now().Unix()
@ -31,46 +29,38 @@ func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookL
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
func (p *provider) ListWebhookLogs(ctx context.Context, pagination *model.Pagination, webhookID string) (*model.WebhookLogs, error) {
webhookLogs := []*model.WebhookLog{}
bindVariables := map[string]interface{}{}
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
if webhookID != "" {
query = fmt.Sprintf("FOR d in %s FILTER d.webhook_id == @webhook_id SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
bindVariables = map[string]interface{}{
"webhook_id": webhookID,
}
}
sctx := driver.WithQueryFullCount(ctx)
sctx := arangoDriver.WithQueryFullCount(ctx)
cursor, err := p.db.Query(sctx, query, bindVariables)
if err != nil {
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var webhookLog models.WebhookLog
var webhookLog *models.WebhookLog
meta, err := cursor.ReadDocument(ctx, &webhookLog)
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return nil, err
}
if meta.Key != "" {
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
}
}
return &model.WebhookLogs{
Pagination: &paginationClone,
Pagination: paginationClone,
WebhookLogs: webhookLogs,
}, nil
}

View File

@ -0,0 +1,133 @@
package cassandradb
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/gocql/gocql"
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/db/models"
)
func (p *provider) AddAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
exists, _ := p.GetAuthenticatorDetailsByUserId(ctx, authenticators.UserID, authenticators.Method)
if exists != nil {
return authenticators, nil
}
if authenticators.ID == "" {
authenticators.ID = uuid.New().String()
}
authenticators.CreatedAt = time.Now().Unix()
authenticators.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(authenticators)
if err != nil {
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
decoder.UseNumber()
authenticatorsMap := map[string]interface{}{}
err = decoder.Decode(&authenticatorsMap)
if err != nil {
return nil, err
}
fields := "("
values := "("
for key, value := range authenticatorsMap {
if value != nil {
if key == "_id" {
fields += "id,"
} else {
fields += key + ","
}
valueType := reflect.TypeOf(value)
if valueType.Name() == "string" {
values += fmt.Sprintf("'%s',", value.(string))
} else {
values += fmt.Sprintf("%v,", value)
}
}
}
fields = fields[:len(fields)-1] + ")"
values = values[:len(values)-1] + ")"
query := fmt.Sprintf("INSERT INTO %s %s VALUES %s IF NOT EXISTS", KeySpace+"."+models.Collections.Authenticators, fields, values)
err = p.db.Query(query).Exec()
if err != nil {
return nil, err
}
return authenticators, nil
}
func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
authenticators.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(authenticators)
if err != nil {
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
decoder.UseNumber()
authenticatorsMap := map[string]interface{}{}
err = decoder.Decode(&authenticatorsMap)
if err != nil {
return nil, err
}
updateFields := ""
for key, value := range authenticatorsMap {
if key == "_id" {
continue
}
if key == "_key" {
continue
}
if value == nil {
updateFields += fmt.Sprintf("%s = null, ", key)
continue
}
valueType := reflect.TypeOf(value)
if valueType.Name() == "string" {
updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string))
} else {
updateFields += fmt.Sprintf("%s = %v, ", key, value)
}
}
updateFields = strings.Trim(updateFields, " ")
updateFields = strings.TrimSuffix(updateFields, ",")
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Authenticators, updateFields, authenticators.ID)
err = p.db.Query(query).Exec()
if err != nil {
return nil, err
}
return authenticators, nil
}
func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
var authenticators models.Authenticator
query := fmt.Sprintf("SELECT id, user_id, method, secret, recovery_codes, verified_at, created_at, updated_at FROM %s WHERE user_id = '%s' AND method = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.Authenticators, userId, authenticatorType)
err := p.db.Query(query).Consistency(gocql.One).Scan(&authenticators.ID, &authenticators.UserID, &authenticators.Method, &authenticators.Secret, &authenticators.RecoveryCodes, &authenticators.VerifiedAt, &authenticators.CreatedAt, &authenticators.UpdatedAt)
if err != nil {
return nil, err
}
return &authenticators, nil
}

View File

@ -15,33 +15,28 @@ import (
)
// AddEmailTemplate to add EmailTemplate
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
if emailTemplate.ID == "" {
emailTemplate.ID = uuid.New().String()
}
emailTemplate.Key = emailTemplate.ID
emailTemplate.CreatedAt = time.Now().Unix()
emailTemplate.UpdatedAt = time.Now().Unix()
existingEmailTemplate, _ := p.GetEmailTemplateByEventName(ctx, emailTemplate.EventName)
if existingEmailTemplate != nil {
return nil, fmt.Errorf("Email template with %s event_name already exists", emailTemplate.EventName)
}
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, subject, design, template, created_at, updated_at) VALUES ('%s', '%s', '%s','%s','%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Subject, emailTemplate.Design, emailTemplate.Template, emailTemplate.CreatedAt, emailTemplate.UpdatedAt)
err := p.db.Query(insertQuery).Exec()
if err != nil {
return nil, err
}
return emailTemplate.AsAPIEmailTemplate(), nil
}
// UpdateEmailTemplate to update EmailTemplate
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
emailTemplate.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(emailTemplate)
if err != nil {
return nil, err
@ -54,22 +49,18 @@ func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models
if err != nil {
return nil, err
}
updateFields := ""
for key, value := range emailTemplateMap {
if key == "_id" {
continue
}
if key == "_key" {
continue
}
if value == nil {
updateFields += fmt.Sprintf("%s = null,", key)
continue
}
valueType := reflect.TypeOf(value)
if valueType.Name() == "string" {
updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string))
@ -90,7 +81,7 @@ func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models
}
// ListEmailTemplates to list EmailTemplate
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
func (p *provider) ListEmailTemplate(ctx context.Context, pagination *model.Pagination) (*model.EmailTemplates, error) {
emailTemplates := []*model.EmailTemplate{}
paginationClone := pagination
@ -120,7 +111,7 @@ func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagin
}
return &model.EmailTemplates{
Pagination: &paginationClone,
Pagination: paginationClone,
EmailTemplates: emailTemplates,
}, nil
}

View File

@ -11,43 +11,39 @@ import (
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
insertEnvQuery := fmt.Sprintf("INSERT INTO %s (id, env, hash, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.Env, env.ID, env.EnvData, env.Hash, env.CreatedAt, env.UpdatedAt)
err := p.db.Query(insertEnvQuery).Exec()
if err != nil {
return env, err
return nil, err
}
return env, nil
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
env.UpdatedAt = time.Now().Unix()
updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID)
err := p.db.Query(updateEnvQuery).Exec()
if err != nil {
return env, err
return nil, err
}
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
var env models.Env
query := fmt.Sprintf("SELECT id, env, hash, created_at, updated_at FROM %s LIMIT 1", KeySpace+"."+models.Collections.Env)
err := p.db.Query(query).Consistency(gocql.One).Scan(&env.ID, &env.EnvData, &env.Hash, &env.CreatedAt, &env.UpdatedAt)
if err != nil {
return env, err
return nil, err
}
return env, nil
return &env, nil
}

View File

@ -2,6 +2,7 @@ package cassandradb
import (
"context"
"errors"
"fmt"
"time"
@ -12,17 +13,31 @@ import (
// UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email)
// check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false
if otp == nil {
shouldCreate = true
otp = &models.OTP{
ID: uuid.NewString(),
Otp: otpParam.Otp,
Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
ID: uuid.NewString(),
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
} else {
otp.Otp = otpParam.Otp
@ -32,7 +47,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
otp.UpdatedAt = time.Now().Unix()
query := ""
if shouldCreate {
query = fmt.Sprintf(`INSERT INTO %s (id, email, otp, expires_at, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d, %d)`, KeySpace+"."+models.Collections.OTP, otp.ID, otp.Email, otp.Otp, otp.ExpiresAt, otp.CreatedAt, otp.UpdatedAt)
query = fmt.Sprintf(`INSERT INTO %s (id, email, phone_number, otp, expires_at, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)`, KeySpace+"."+models.Collections.OTP, otp.ID, otp.Email, otp.PhoneNumber, otp.Otp, otp.ExpiresAt, otp.CreatedAt, otp.UpdatedAt)
} else {
query = fmt.Sprintf(`UPDATE %s SET otp = '%s', expires_at = %d, updated_at = %d WHERE id = '%s'`, KeySpace+"."+models.Collections.OTP, otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID)
}
@ -48,8 +63,19 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
// GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
var otp models.OTP
query := fmt.Sprintf(`SELECT id, email, otp, expires_at, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, emailAddress)
err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt)
query := fmt.Sprintf(`SELECT id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, emailAddress)
err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.PhoneNumber, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt)
if err != nil {
return nil, err
}
return &otp, nil
}
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
var otp models.OTP
query := fmt.Sprintf(`SELECT id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s WHERE phone_number = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, phoneNumber)
err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.PhoneNumber, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt)
if err != nil {
return nil, err
}

View File

@ -161,6 +161,12 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
userPhoneNumberIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_user_phone_number ON %s.%s (phone_number)", KeySpace, models.Collections.User)
err = session.Query(userPhoneNumberIndexQuery).Exec()
if err != nil {
return nil, err
}
// add is_multi_factor_auth_enabled on users table
userTableAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD is_multi_factor_auth_enabled boolean`, KeySpace, models.Collections.User)
err = session.Query(userTableAlterQuery).Exec()
@ -201,6 +207,13 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
// add event_description to webhook table
webhookAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (event_description text);`, KeySpace, models.Collections.Webhook)
err = session.Query(webhookAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter table as column exists: ", err)
// continue
}
webhookLogCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, http_status bigint, response text, request text, webhook_id text,updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.WebhookLog)
err = session.Query(webhookLogCollectionQuery).Exec()
@ -241,6 +254,32 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
// Add phone_number column to otp table
otpAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (phone_number text);`, KeySpace, models.Collections.OTP)
err = session.Query(otpAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter table as column exists: ", err)
// continue
}
// Add app_data column to users table
appDataAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (app_data text);`, KeySpace, models.Collections.User)
err = session.Query(appDataAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter user table as app_data column exists: ", err)
// continue
}
// Add phone number index
otpIndexQueryPhoneNumber := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_otp_phone_number ON %s.%s (phone_number)", KeySpace, models.Collections.OTP)
err = session.Query(otpIndexQueryPhoneNumber).Exec()
if err != nil {
return nil, err
}
// add authenticators table
totpCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, user_id text, method text, secret text, recovery_codes text, verified_at bigint, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.Authenticators)
err = session.Query(totpCollectionQuery).Exec()
if err != nil {
return nil, err
}
return &provider{
db: session,

View File

@ -10,14 +10,12 @@ import (
)
// AddSession to save session information in database
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
insertSessionQuery := fmt.Sprintf("INSERT INTO %s (id, user_id, user_agent, ip, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.Session, session.ID, session.UserID, session.UserAgent, session.IP, session.CreatedAt, session.UpdatedAt)
err := p.db.Query(insertSessionQuery).Exec()
if err != nil {
@ -25,3 +23,8 @@ func (p *provider) AddSession(ctx context.Context, session models.Session) error
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
return nil
}

View File

@ -12,12 +12,13 @@ import (
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/gocql/gocql"
"github.com/google/uuid"
)
// AddUser to save user information in database
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
@ -25,17 +26,27 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return user, err
return nil, err
}
user.Roles = defaultRoles
}
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given phone number already exists")
}
} else if user.Email != nil && strings.TrimSpace(refs.StringValue(user.Email)) != "" {
if u, _ := p.GetUserByEmail(ctx, refs.StringValue(user.Email)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given email already exists")
}
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(user)
if err != nil {
return user, err
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
@ -44,7 +55,7 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
userMap := map[string]interface{}{}
err = decoder.Decode(&userMap)
if err != nil {
return user, err
return nil, err
}
fields := "("
@ -70,22 +81,22 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
values = values[:len(values)-1] + ")"
query := fmt.Sprintf("INSERT INTO %s %s VALUES %s IF NOT EXISTS", KeySpace+"."+models.Collections.User, fields, values)
err = p.db.Query(query).Exec()
if err != nil {
return user, err
return nil, err
}
return user, nil
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.User, error) {
user.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(user)
if err != nil {
return user, err
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
@ -93,7 +104,7 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
userMap := map[string]interface{}{}
err = decoder.Decode(&userMap)
if err != nil {
return user, err
return nil, err
}
updateFields := ""
@ -124,20 +135,19 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, updateFields, user.ID)
err = p.db.Query(query).Exec()
if err != nil {
return user, err
return nil, err
}
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
func (p *provider) DeleteUser(ctx context.Context, user *models.User) error {
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, user.ID)
err := p.db.Query(query).Exec()
if err != nil {
return err
}
getSessionsQuery := fmt.Sprintf("SELECT id FROM %s WHERE user_id = '%s' ALLOW FILTERING", KeySpace+"."+models.Collections.Session, user.ID)
scanner := p.db.Query(getSessionsQuery).Iter().Scanner()
sessionIDs := ""
@ -160,7 +170,7 @@ func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination) (*model.Users, error) {
responseUsers := []*model.User{}
paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.User)
@ -172,14 +182,17 @@ func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (
// there is no offset in cassandra
// so we fetch till limit + offset
// and return the results from offset to limit
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.User, pagination.Limit+pagination.Offset)
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.User,
pagination.Limit+pagination.Offset)
scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0)
for scanner.Next() {
if counter >= pagination.Offset {
var user models.User
err := scanner.Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
err := scanner.Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods,
&user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber,
&user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled,
&user.AppData, &user.CreatedAt, &user.UpdatedAt)
if err != nil {
return nil, err
}
@ -188,31 +201,31 @@ func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (
counter++
}
return &model.Users{
Pagination: paginationClone,
Users: responseUsers,
Pagination: &paginationClone,
}, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.AppData, &user.CreatedAt, &user.UpdatedAt)
if err != nil {
return user, err
return nil, err
}
return user, nil
return &user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.AppData, &user.CreatedAt, &user.UpdatedAt)
if err != nil {
return user, err
return nil, err
}
return user, nil
return &user, nil
}
// UpdateUsers to update multiple users, with parameters of user IDs slice
@ -245,9 +258,8 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
}
updateFields = strings.Trim(updateFields, " ")
updateFields = strings.TrimSuffix(updateFields, ",")
query := ""
if ids != nil && len(ids) > 0 {
if len(ids) > 0 {
idsString := ""
for _, id := range ids {
idsString += fmt.Sprintf("'%s', ", id)
@ -294,8 +306,17 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
return err
}
}
}
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s WHERE phone_number = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, phoneNumber)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.AppData, &user.CreatedAt, &user.UpdatedAt)
if err != nil {
return nil, err
}
return &user, nil
}

View File

@ -12,7 +12,7 @@ import (
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) (*models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
@ -23,47 +23,45 @@ func (p *provider) AddVerificationRequest(ctx context.Context, verificationReque
query := fmt.Sprintf("INSERT INTO %s (id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, '%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.VerificationRequest, verificationRequest.ID, verificationRequest.Token, verificationRequest.Identifier, verificationRequest.ExpiresAt, verificationRequest.Email, verificationRequest.Nonce, verificationRequest.RedirectURI, verificationRequest.CreatedAt, verificationRequest.UpdatedAt)
err := p.db.Query(query).Exec()
if err != nil {
return verificationRequest, err
return nil, err
}
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (*models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE jwt_token = '%s' LIMIT 1`, KeySpace+"."+models.Collections.VerificationRequest, token)
err := p.db.Query(query).Consistency(gocql.One).Scan(&verificationRequest.ID, &verificationRequest.Token, &verificationRequest.Identifier, &verificationRequest.ExpiresAt, &verificationRequest.Email, &verificationRequest.Nonce, &verificationRequest.RedirectURI, &verificationRequest.CreatedAt, &verificationRequest.UpdatedAt)
if err != nil {
return verificationRequest, err
return nil, err
}
return verificationRequest, nil
return &verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (*models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE email = '%s' AND identifier = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.VerificationRequest, email, identifier)
err := p.db.Query(query).Consistency(gocql.One).Scan(&verificationRequest.ID, &verificationRequest.Token, &verificationRequest.Identifier, &verificationRequest.ExpiresAt, &verificationRequest.Email, &verificationRequest.Nonce, &verificationRequest.RedirectURI, &verificationRequest.CreatedAt, &verificationRequest.UpdatedAt)
if err != nil {
return verificationRequest, err
return nil, err
}
return verificationRequest, nil
return &verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
func (p *provider) ListVerificationRequests(ctx context.Context, pagination *model.Pagination) (*model.VerificationRequests, error) {
var verificationRequests []*model.VerificationRequest
paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.VerificationRequest)
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
if err != nil {
return nil, err
}
// there is no offset in cassandra
// so we fetch till limit + offset
// and return the results from offset to limit
@ -85,12 +83,12 @@ func (p *provider) ListVerificationRequests(ctx context.Context, pagination mode
return &model.VerificationRequests{
VerificationRequests: verificationRequests,
Pagination: &paginationClone,
Pagination: paginationClone,
}, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) error {
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.VerificationRequest, verificationRequest.ID)
err := p.db.Query(query).Exec()
if err != nil {

View File

@ -15,33 +15,30 @@ import (
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
func (p *provider) AddWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
}
webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
existingHook, _ := p.GetWebhookByEventName(ctx, webhook.EventName)
if existingHook != nil {
return nil, fmt.Errorf("Webhook with %s event_name already exists", webhook.EventName)
}
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventDescription, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
err := p.db.Query(insertQuery).Exec()
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
func (p *provider) UpdateWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
bytes, err := json.Marshal(webhook)
if err != nil {
return nil, err
@ -54,22 +51,18 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
if err != nil {
return nil, err
}
updateFields := ""
for key, value := range webhookMap {
if key == "_id" {
continue
}
if key == "_key" {
continue
}
if value == nil {
updateFields += fmt.Sprintf("%s = null,", key)
continue
}
valueType := reflect.TypeOf(value)
if valueType.Name() == "string" {
updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string))
@ -79,7 +72,6 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
}
updateFields = strings.Trim(updateFields, " ")
updateFields = strings.TrimSuffix(updateFields, ",")
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, updateFields, webhook.ID)
err = p.db.Query(query).Exec()
if err != nil {
@ -89,27 +81,24 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
func (p *provider) ListWebhook(ctx context.Context, pagination *model.Pagination) (*model.Webhooks, error) {
webhooks := []*model.Webhook{}
paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.Webhook)
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
if err != nil {
return nil, err
}
// there is no offset in cassandra
// so we fetch till limit + offset
// and return the results from offset to limit
query := fmt.Sprintf("SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.Webhook, pagination.Limit+pagination.Offset)
query := fmt.Sprintf("SELECT id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.Webhook, pagination.Limit+pagination.Offset)
scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0)
for scanner.Next() {
if counter >= pagination.Offset {
var webhook models.Webhook
err := scanner.Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
err := scanner.Scan(&webhook.ID, &webhook.EventDescription, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
}
@ -119,7 +108,7 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
}
return &model.Webhooks{
Pagination: &paginationClone,
Pagination: paginationClone,
Webhooks: webhooks,
}, nil
}
@ -127,8 +116,8 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.Webhook, webhookID)
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
query := fmt.Sprintf(`SELECT id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.Webhook, webhookID)
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventDescription, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
}
@ -136,14 +125,19 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName)
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
query := fmt.Sprintf(`SELECT id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name LIKE '%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName+"%")
scanner := p.db.Query(query).Iter().Scanner()
webhooks := []*model.Webhook{}
for scanner.Next() {
var webhook models.Webhook
err := scanner.Scan(&webhook.ID, &webhook.EventDescription, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
return webhook.AsAPIWebhook(), nil
return webhooks, nil
}
// DeleteWebhook to delete webhook

View File

@ -12,7 +12,7 @@ import (
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog *models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
}
@ -30,7 +30,7 @@ func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookL
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
func (p *provider) ListWebhookLogs(ctx context.Context, pagination *model.Pagination, webhookID string) (*model.WebhookLogs, error) {
webhookLogs := []*model.WebhookLog{}
paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.WebhookLog)
@ -38,7 +38,6 @@ func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Paginat
// so we fetch till limit + offset
// and return the results from offset to limit
query := fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.WebhookLog, pagination.Limit+pagination.Offset)
if webhookID != "" {
totalCountQuery = fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE webhook_id='%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.WebhookLog, webhookID)
query = fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s WHERE webhook_id = '%s' LIMIT %d ALLOW FILTERING", KeySpace+"."+models.Collections.WebhookLog, webhookID, pagination.Limit+pagination.Offset)
@ -64,7 +63,7 @@ func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Paginat
}
return &model.WebhookLogs{
Pagination: &paginationClone,
Pagination: paginationClone,
WebhookLogs: webhookLogs,
}, nil
}

View File

@ -0,0 +1,81 @@
package couchbase
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/db/models"
)
func (p *provider) AddAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
exists, _ := p.GetAuthenticatorDetailsByUserId(ctx, authenticators.UserID, authenticators.Method)
if exists != nil {
return authenticators, nil
}
if authenticators.ID == "" {
authenticators.ID = uuid.New().String()
}
authenticators.Key = authenticators.ID
authenticators.CreatedAt = time.Now().Unix()
authenticators.UpdatedAt = time.Now().Unix()
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.Authenticators).Insert(authenticators.ID, authenticators, &insertOpt)
if err != nil {
return nil, err
}
return authenticators, nil
}
func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
authenticators.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(authenticators)
if err != nil {
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
decoder.UseNumber()
authenticator := map[string]interface{}{}
err = decoder.Decode(&authenticator)
if err != nil {
return nil, err
}
updateFields, params := GetSetFields(authenticator)
query := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id = '%s'", p.scopeName, models.Collections.Authenticators, updateFields, authenticators.ID)
_, err = p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params,
})
if err != nil {
return nil, err
}
return authenticators, nil
}
func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
var authenticators *models.Authenticator
query := fmt.Sprintf("SELECT _id, user_id, method, secret, recovery_code, verified_at, created_at, updated_at FROM %s.%s WHERE user_id = $1 AND method = $2 LIMIT 1", p.scopeName, models.Collections.Authenticators)
q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx,
PositionalParameters: []interface{}{userId, authenticatorType},
})
if err != nil {
return nil, err
}
err = q.One(&authenticators)
if err != nil {
return nil, err
}
return authenticators, nil
}

View File

@ -0,0 +1,158 @@
package couchbase
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// AddEmailTemplate to add EmailTemplate
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
if emailTemplate.ID == "" {
emailTemplate.ID = uuid.New().String()
}
emailTemplate.Key = emailTemplate.ID
emailTemplate.CreatedAt = time.Now().Unix()
emailTemplate.UpdatedAt = time.Now().Unix()
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.EmailTemplate).Insert(emailTemplate.ID, emailTemplate, &insertOpt)
if err != nil {
return emailTemplate.AsAPIEmailTemplate(), err
}
return emailTemplate.AsAPIEmailTemplate(), nil
}
// UpdateEmailTemplate to update EmailTemplate
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
bytes, err := json.Marshal(emailTemplate)
if err != nil {
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
decoder.UseNumber()
emailTemplateMap := map[string]interface{}{}
err = decoder.Decode(&emailTemplateMap)
if err != nil {
return nil, err
}
updateFields, params := GetSetFields(emailTemplateMap)
params["emailId"] = emailTemplate.ID
query := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id = $emailId", p.scopeName, models.Collections.EmailTemplate, updateFields)
_, err = p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
NamedParameters: params,
})
if err != nil {
return nil, err
}
return emailTemplate.AsAPIEmailTemplate(), nil
}
// ListEmailTemplates to list EmailTemplate
func (p *provider) ListEmailTemplate(ctx context.Context, pagination *model.Pagination) (*model.EmailTemplates, error) {
emailTemplates := []*model.EmailTemplate{}
paginationClone := pagination
total, err := p.GetTotalDocs(ctx, models.Collections.EmailTemplate)
if err != nil {
return nil, err
}
paginationClone.Total = total
userQuery := fmt.Sprintf("SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s ORDER BY _id OFFSET $1 LIMIT $2", p.scopeName, models.Collections.EmailTemplate)
queryResult, err := p.db.Query(userQuery, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit},
})
if err != nil {
return nil, err
}
for queryResult.Next() {
var emailTemplate *models.EmailTemplate
err := queryResult.Row(&emailTemplate)
if err != nil {
log.Fatal(err)
}
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
}
if err := queryResult.Err(); err != nil {
return nil, err
}
return &model.EmailTemplates{
Pagination: paginationClone,
EmailTemplates: emailTemplates,
}, nil
}
// GetEmailTemplateByID to get EmailTemplate by id
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
var emailTemplate *models.EmailTemplate
query := fmt.Sprintf(`SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1`, p.scopeName, models.Collections.EmailTemplate)
q, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{emailTemplateID},
})
if err != nil {
return nil, err
}
err = q.One(&emailTemplate)
if err != nil {
return nil, err
}
return emailTemplate.AsAPIEmailTemplate(), nil
}
// GetEmailTemplateByEventName to get EmailTemplate by event_name
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
var emailTemplate models.EmailTemplate
query := fmt.Sprintf("SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s WHERE event_name=$1 LIMIT 1", p.scopeName, models.Collections.EmailTemplate)
q, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{eventName},
})
if err != nil {
return nil, err
}
err = q.One(&emailTemplate)
if err != nil {
return nil, err
}
return emailTemplate.AsAPIEmailTemplate(), nil
}
// DeleteEmailTemplate to delete EmailTemplate
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
removeOpt := gocb.RemoveOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.EmailTemplate).Remove(emailTemplate.ID, &removeOpt)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,66 @@
package couchbase
import (
"context"
"fmt"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
env.Key = env.ID
env.EncryptionKey = env.Hash
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.Env).Insert(env.ID, env, &insertOpt)
if err != nil {
return nil, err
}
return env, nil
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
env.UpdatedAt = time.Now().Unix()
env.EncryptionKey = env.Hash
updateEnvQuery := fmt.Sprintf("UPDATE %s.%s SET env = $1, updated_at = $2 WHERE _id = $3", p.scopeName, models.Collections.Env)
_, err := p.db.Query(updateEnvQuery, &gocb.QueryOptions{
Context: ctx,
PositionalParameters: []interface{}{env.EnvData, env.UpdatedAt, env.UpdatedAt, env.ID},
})
if err != nil {
return nil, err
}
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
var env *models.Env
query := fmt.Sprintf("SELECT _id, env, encryption_key, created_at, updated_at FROM %s.%s LIMIT 1", p.scopeName, models.Collections.Env)
q, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
})
if err != nil {
return nil, err
}
err = q.One(&env)
if err != nil {
return nil, err
}
env.Hash = env.EncryptionKey
return env, nil
}

View File

@ -0,0 +1,113 @@
package couchbase
import (
"context"
"errors"
"fmt"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
// check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false
if otp == nil {
shouldCreate = true
otp = &models.OTP{
ID: uuid.NewString(),
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
} else {
otp.Otp = otpParam.Otp
otp.ExpiresAt = otpParam.ExpiresAt
}
otp.UpdatedAt = time.Now().Unix()
if shouldCreate {
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.OTP).Insert(otp.ID, otp, &insertOpt)
if err != nil {
return nil, err
}
} else {
query := fmt.Sprintf(`UPDATE %s.%s SET otp=$1, expires_at=$2, updated_at=$3 WHERE _id=$4`, p.scopeName, models.Collections.OTP)
_, err := p.db.Query(query, &gocb.QueryOptions{
PositionalParameters: []interface{}{otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID},
})
if err != nil {
return nil, err
}
}
return otp, nil
}
// GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
otp := models.OTP{}
query := fmt.Sprintf(`SELECT _id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1`, p.scopeName, models.Collections.OTP)
q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{emailAddress},
})
if err != nil {
return nil, err
}
err = q.One(&otp)
if err != nil {
return nil, err
}
return &otp, nil
}
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
otp := models.OTP{}
query := fmt.Sprintf(`SELECT _id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s.%s WHERE phone_number = $1 LIMIT 1`, p.scopeName, models.Collections.OTP)
q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{phoneNumber},
})
if err != nil {
return nil, err
}
err = q.One(&otp)
if err != nil {
return nil, err
}
return &otp, nil
}
// DeleteOTP to delete otp
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
removeOpt := gocb.RemoveOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.OTP).Remove(otp.ID, &removeOpt)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,174 @@
package couchbase
import (
"context"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/couchbase/gocb/v2"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/memorystore"
)
const (
defaultBucketName = "authorizer"
defaultScope = "_default"
)
type provider struct {
db *gocb.Scope
scopeName string
}
// NewProvider returns a new Couchbase provider
func NewProvider() (*provider, error) {
bucketName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().CouchbaseBucket
scopeName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().CouchbaseScope
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
userName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseUsername
password := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePassword
opts := gocb.ClusterOptions{
Username: userName,
Password: password,
}
if bucketName == "" {
bucketName = defaultBucketName
}
if scopeName == "" {
scopeName = defaultScope
}
cluster, err := gocb.Connect(dbURL, opts)
if err != nil {
return nil, err
}
// To create the bucket and scope if not exist
bucket, err := CreateBucketAndScope(cluster, bucketName, scopeName)
if err != nil {
return nil, err
}
scope := bucket.Scope(scopeName)
scopeIdentifier := fmt.Sprintf("%s.%s", bucketName, scopeName)
v := reflect.ValueOf(models.Collections)
for i := 0; i < v.NumField(); i++ {
collectionName := v.Field(i)
user := gocb.CollectionSpec{
Name: collectionName.String(),
ScopeName: scopeName,
}
collectionOpts := gocb.CreateCollectionOptions{
Context: context.TODO(),
}
err = bucket.Collections().CreateCollection(user, &collectionOpts)
if err != nil && !errors.Is(err, gocb.ErrCollectionExists) {
return nil, err
}
// TODO: find how to fix this sleep time.
// Add wait time for successful collection creation
time.Sleep(5 * time.Second)
indexQuery := fmt.Sprintf("CREATE PRIMARY INDEX ON %s.%s", scopeIdentifier, collectionName.String())
_, err = scope.Query(indexQuery, nil)
if err != nil && !strings.Contains(err.Error(), "The index #primary already exists") {
return nil, err
}
}
indices := GetIndex(scopeIdentifier)
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
for _, indexQuery := range indices[field.String()] {
scope.Query(indexQuery, nil)
}
}
return &provider{
db: scope,
scopeName: scopeIdentifier,
}, nil
}
func CreateBucketAndScope(cluster *gocb.Cluster, bucketName string, scopeName string) (*gocb.Bucket, error) {
bucketRAMQuotaMB := memorystore.RequiredEnvStoreObj.GetRequiredEnv().CouchbaseBucketRAMQuotaMB
if bucketRAMQuotaMB == "" {
bucketRAMQuotaMB = "1000"
}
bucketRAMQuota, err := strconv.ParseInt(bucketRAMQuotaMB, 10, 64)
if err != nil {
return nil, err
}
settings := gocb.BucketSettings{
Name: bucketName,
RAMQuotaMB: uint64(bucketRAMQuota),
BucketType: gocb.CouchbaseBucketType,
EvictionPolicy: gocb.EvictionPolicyTypeValueOnly,
FlushEnabled: true,
CompressionMode: gocb.CompressionModeActive,
}
shouldCreateBucket := false
// check if bucket exists
_, err = cluster.Buckets().GetBucket(bucketName, nil)
if err != nil {
// bucket not found
shouldCreateBucket = true
}
if shouldCreateBucket {
err = cluster.Buckets().CreateBucket(gocb.CreateBucketSettings{
BucketSettings: settings,
ConflictResolutionType: gocb.ConflictResolutionTypeSequenceNumber,
}, nil)
if err != nil {
return nil, err
}
}
bucket := cluster.Bucket(bucketName)
if scopeName != defaultScope {
err = bucket.Collections().CreateScope(scopeName, nil)
if err != nil && !errors.Is(err, gocb.ErrScopeExists) {
return nil, err
}
}
return bucket, nil
}
func GetIndex(scopeName string) map[string][]string {
indices := make(map[string][]string)
// User Index
userIndex1 := fmt.Sprintf("CREATE INDEX userEmailIndex ON %s.%s(email)", scopeName, models.Collections.User)
userIndex2 := fmt.Sprintf("CREATE INDEX userPhoneIndex ON %s.%s(phone_number)", scopeName, models.Collections.User)
indices[models.Collections.User] = []string{userIndex1, userIndex2}
// VerificationRequest
verificationIndex1 := fmt.Sprintf("CREATE INDEX verificationRequestTokenIndex ON %s.%s(token)", scopeName, models.Collections.VerificationRequest)
verificationIndex2 := fmt.Sprintf("CREATE INDEX verificationRequestEmailAndIdentifierIndex ON %s.%s(email,identifier)", scopeName, models.Collections.VerificationRequest)
indices[models.Collections.VerificationRequest] = []string{verificationIndex1, verificationIndex2}
// Session index
sessionIndex1 := fmt.Sprintf("CREATE INDEX SessionUserIdIndex ON %s.%s(user_id)", scopeName, models.Collections.Session)
indices[models.Collections.Session] = []string{sessionIndex1}
// Webhook index
webhookIndex1 := fmt.Sprintf("CREATE INDEX webhookEventNameIndex ON %s.%s(event_name)", scopeName, models.Collections.Webhook)
indices[models.Collections.Webhook] = []string{webhookIndex1}
// WebhookLog index
webhookLogIndex1 := fmt.Sprintf("CREATE INDEX webhookLogIdIndex ON %s.%s(webhook_id)", scopeName, models.Collections.WebhookLog)
indices[models.Collections.Webhook] = []string{webhookLogIndex1}
// WebhookLog index
emailTempIndex1 := fmt.Sprintf("CREATE INDEX EmailTemplateEventNameIndex ON %s.%s(event_name)", scopeName, models.Collections.EmailTemplate)
indices[models.Collections.EmailTemplate] = []string{emailTempIndex1}
// OTP index
otpIndex1 := fmt.Sprintf("CREATE INDEX OTPEmailIndex ON %s.%s(email)", scopeName, models.Collections.OTP)
indices[models.Collections.OTP] = []string{otpIndex1}
// OTP index
otpIndex2 := fmt.Sprintf("CREATE INDEX OTPPhoneNumberIndex ON %s.%s(phone_number)", scopeName, models.Collections.OTP)
indices[models.Collections.OTP] = []string{otpIndex2}
return indices
}

View File

@ -1,21 +1,28 @@
package surrealdb
package couchbase
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// AddSession to save session information in database
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.Session).Insert(session.ID, session, &insertOpt)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,57 @@
package couchbase
import (
"context"
"fmt"
"reflect"
"strings"
"github.com/couchbase/gocb/v2"
)
func GetSetFields(webhookMap map[string]interface{}) (string, map[string]interface{}) {
params := make(map[string]interface{}, 1)
updateFields := ""
for key, value := range webhookMap {
if key == "_id" {
continue
}
if key == "_key" {
continue
}
if value == nil {
updateFields += fmt.Sprintf("%s=$%s,", key, key)
params[key] = "null"
continue
}
valueType := reflect.TypeOf(value)
if valueType.Name() == "string" {
updateFields += fmt.Sprintf("%s = $%s, ", key, key)
params[key] = value.(string)
} else {
updateFields += fmt.Sprintf("%s = $%s, ", key, key)
params[key] = value
}
}
updateFields = strings.Trim(updateFields, " ")
updateFields = strings.TrimSuffix(updateFields, ",")
return updateFields, params
}
func (p *provider) GetTotalDocs(ctx context.Context, collection string) (int64, error) {
totalDocs := TotalDocs{}
countQuery := fmt.Sprintf("SELECT COUNT(*) as Total FROM %s.%s", p.scopeName, collection)
queryRes, err := p.db.Query(countQuery, &gocb.QueryOptions{
Context: ctx,
})
queryRes.One(&totalDocs)
if err != nil {
return 0, err
}
return totalDocs.Total, nil
}
type TotalDocs struct {
Total int64
}

View File

@ -0,0 +1,204 @@
package couchbase
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// AddUser to save user information in database
func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
return nil, err
}
user.Roles = defaultRoles
}
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given phone number already exists")
}
} else if user.Email != nil && strings.TrimSpace(refs.StringValue(user.Email)) != "" {
if u, _ := p.GetUserByEmail(ctx, refs.StringValue(user.Email)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given email already exists")
}
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.User).Insert(user.ID, user, &insertOpt)
if err != nil {
return nil, err
}
return user, nil
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.User, error) {
user.UpdatedAt = time.Now().Unix()
upsertOpt := gocb.UpsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.User).Upsert(user.ID, user, &upsertOpt)
if err != nil {
return nil, err
}
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(ctx context.Context, user *models.User) error {
removeOpt := gocb.RemoveOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.User).Remove(user.ID, &removeOpt)
if err != nil {
return err
}
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination) (*model.Users, error) {
users := []*model.User{}
paginationClone := pagination
userQuery := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s.%s ORDER BY id OFFSET $1 LIMIT $2", p.scopeName, models.Collections.User)
queryResult, err := p.db.Query(userQuery, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx,
PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit},
})
if err != nil {
return nil, err
}
total, err := p.GetTotalDocs(ctx, models.Collections.User)
if err != nil {
return nil, err
}
paginationClone.Total = total
for queryResult.Next() {
var user models.User
err := queryResult.Row(&user)
if err != nil {
log.Fatal(err)
}
users = append(users, user.AsAPIUser())
}
if err := queryResult.Err(); err != nil {
return nil, err
}
return &model.Users{
Pagination: paginationClone,
Users: users,
}, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.User)
q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx,
PositionalParameters: []interface{}{email},
})
if err != nil {
return nil, err
}
err = q.One(&user)
if err != nil {
return nil, err
}
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.User)
q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx,
PositionalParameters: []interface{}{id},
})
if err != nil {
return nil, err
}
err = q.One(&user)
if err != nil {
return nil, err
}
return user, nil
}
// UpdateUsers to update multiple users, with parameters of user IDs slice
// If ids set to nil / empty all the users will be updated
func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error {
// set updated_at time for all users
data["updated_at"] = time.Now().Unix()
updateFields, params := GetSetFields(data)
if len(ids) > 0 {
for _, id := range ids {
params["id"] = id
userQuery := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id = $id", p.scopeName, models.Collections.User, updateFields)
_, err := p.db.Query(userQuery, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx,
NamedParameters: params,
})
if err != nil {
return err
}
}
} else {
userQuery := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id IS NOT NULL", p.scopeName, models.Collections.User, updateFields)
_, err := p.db.Query(userQuery, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx,
NamedParameters: params,
})
if err != nil {
return err
}
}
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, app_data, created_at, updated_at FROM %s.%s WHERE phone_number = $1 LIMIT 1", p.scopeName, models.Collections.User)
q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx,
PositionalParameters: []interface{}{phoneNumber},
})
if err != nil {
return nil, err
}
err = q.One(&user)
if err != nil {
return nil, err
}
return user, nil
}

View File

@ -0,0 +1,123 @@
package couchbase
import (
"context"
"fmt"
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) (*models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
verificationRequest.Key = verificationRequest.ID
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.VerificationRequest).Insert(verificationRequest.ID, verificationRequest, &insertOpt)
if err != nil {
return nil, err
}
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (*models.VerificationRequest, error) {
var verificationRequest *models.VerificationRequest
params := make(map[string]interface{}, 1)
params["token"] = token
query := fmt.Sprintf("SELECT _id, token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s.%s WHERE token=$1 LIMIT 1", p.scopeName, models.Collections.VerificationRequest)
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
PositionalParameters: []interface{}{token},
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
})
if err != nil {
return nil, err
}
err = queryResult.One(&verificationRequest)
if err != nil {
return nil, err
}
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (*models.VerificationRequest, error) {
query := fmt.Sprintf("SELECT _id, identifier, token, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s.%s WHERE email=$1 AND identifier=$2 LIMIT 1", p.scopeName, models.Collections.VerificationRequest)
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
PositionalParameters: []interface{}{email, identifier},
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
})
if err != nil {
return nil, err
}
var verificationRequest *models.VerificationRequest
err = queryResult.One(&verificationRequest)
if err != nil {
return nil, err
}
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(ctx context.Context, pagination *model.Pagination) (*model.VerificationRequests, error) {
var verificationRequests []*model.VerificationRequest
paginationClone := pagination
total, err := p.GetTotalDocs(ctx, models.Collections.VerificationRequest)
if err != nil {
return nil, err
}
paginationClone.Total = total
query := fmt.Sprintf("SELECT _id, env, created_at, updated_at FROM %s.%s OFFSET $1 LIMIT $2", p.scopeName, models.Collections.VerificationRequest)
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit},
})
if err != nil {
return nil, err
}
for queryResult.Next() {
var verificationRequest models.VerificationRequest
err := queryResult.Row(&verificationRequest)
if err != nil {
log.Fatal(err)
}
verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest())
}
if err := queryResult.Err(); err != nil {
return nil, err
}
return &model.VerificationRequests{
VerificationRequests: verificationRequests,
Pagination: paginationClone,
}, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) error {
removeOpt := gocb.RemoveOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.VerificationRequest).Remove(verificationRequest.ID, &removeOpt)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,178 @@
package couchbase
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
}
webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.Webhook).Insert(webhook.ID, webhook, &insertOpt)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
bytes, err := json.Marshal(webhook)
if err != nil {
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
decoder.UseNumber()
webhookMap := map[string]interface{}{}
err = decoder.Decode(&webhookMap)
if err != nil {
return nil, err
}
updateFields, params := GetSetFields(webhookMap)
query := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE _id='%s'`, p.scopeName, models.Collections.Webhook, updateFields, webhook.ID)
_, err = p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params,
})
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination *model.Pagination) (*model.Webhooks, error) {
webhooks := []*model.Webhook{}
paginationClone := pagination
params := make(map[string]interface{}, 1)
params["offset"] = paginationClone.Offset
params["limit"] = paginationClone.Limit
total, err := p.GetTotalDocs(ctx, models.Collections.Webhook)
if err != nil {
return nil, err
}
paginationClone.Total = total
query := fmt.Sprintf("SELECT _id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.Webhook)
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params,
})
if err != nil {
return nil, err
}
for queryResult.Next() {
var webhook models.Webhook
err := queryResult.Row(&webhook)
if err != nil {
log.Fatal(err)
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
if err := queryResult.Err(); err != nil {
return nil, err
}
return &model.Webhooks{
Pagination: paginationClone,
Webhooks: webhooks,
}, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook *models.Webhook
params := make(map[string]interface{}, 1)
params["_id"] = webhookID
query := fmt.Sprintf(`SELECT _id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE _id=$_id LIMIT 1`, p.scopeName, models.Collections.Webhook)
q, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params,
})
if err != nil {
return nil, err
}
err = q.One(&webhook)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
params := make(map[string]interface{}, 1)
// params["event_name"] = eventName + "%"
query := fmt.Sprintf(`SELECT _id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE event_name LIKE '%s'`, p.scopeName, models.Collections.Webhook, eventName+"%")
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params,
})
if err != nil {
return nil, err
}
webhooks := []*model.Webhook{}
for queryResult.Next() {
var webhook *models.Webhook
err := queryResult.Row(&webhook)
if err != nil {
log.Fatal(err)
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
if err := queryResult.Err(); err != nil {
return nil, err
}
return webhooks, nil
}
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
params := make(map[string]interface{}, 1)
params["webhook_id"] = webhook.ID
removeOpt := gocb.RemoveOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.Webhook).Remove(webhook.ID, &removeOpt)
if err != nil {
return err
}
query := fmt.Sprintf(`DELETE FROM %s.%s WHERE webhook_id=$webhook_id`, p.scopeName, models.Collections.WebhookLog)
_, err = p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params,
})
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,77 @@
package couchbase
import (
"context"
"fmt"
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/couchbase/gocb/v2"
"github.com/google/uuid"
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog *models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
}
webhookLog.Key = webhookLog.ID
webhookLog.CreatedAt = time.Now().Unix()
webhookLog.UpdatedAt = time.Now().Unix()
insertOpt := gocb.InsertOptions{
Context: ctx,
}
_, err := p.db.Collection(models.Collections.WebhookLog).Insert(webhookLog.ID, webhookLog, &insertOpt)
if err != nil {
return nil, err
}
return webhookLog.AsAPIWebhookLog(), nil
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination *model.Pagination, webhookID string) (*model.WebhookLogs, error) {
var query string
var err error
webhookLogs := []*model.WebhookLog{}
params := make(map[string]interface{}, 1)
paginationClone := pagination
params["webhookID"] = webhookID
params["offset"] = paginationClone.Offset
params["limit"] = paginationClone.Limit
total, err := p.GetTotalDocs(ctx, models.Collections.WebhookLog)
if err != nil {
return nil, err
}
paginationClone.Total = total
if webhookID != "" {
query = fmt.Sprintf(`SELECT _id, http_status, response, request, webhook_id, created_at, updated_at FROM %s.%s WHERE webhook_id=$webhookID`, p.scopeName, models.Collections.WebhookLog)
} else {
query = fmt.Sprintf("SELECT _id, http_status, response, request, webhook_id, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.WebhookLog)
}
queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params,
})
if err != nil {
return nil, err
}
for queryResult.Next() {
var webhookLog models.WebhookLog
err := queryResult.Row(&webhookLog)
if err != nil {
log.Fatal(err)
}
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
}
if err := queryResult.Err(); err != nil {
return nil, err
}
return &model.WebhookLogs{
Pagination: paginationClone,
WebhookLogs: webhookLogs,
}, nil
}

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