Compare commits

...

261 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
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
catusax
e7652db89c
add comments 2023-07-23 13:02:14 +08:00
catusax
5018462559
feat: add mfa session to secure otp login 2023-07-20 15:11:39 +08:00
177 changed files with 10716 additions and 8020 deletions

View File

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

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

@ -62,12 +62,14 @@ jobs:
run: | run: |
make clean && \ make clean && \
make build && \ 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}-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-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}-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 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 - name: Upload assets
run: | 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}-darwin-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
github-assets-uploader -f authorizer-${VERSION}-linux-arm64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} github-assets-uploader -f authorizer-${VERSION}-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/ certs/
*-shm *-shm
*-wal *-wal
.idea
*.iml

View File

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

View File

@ -5,7 +5,7 @@ cmd:
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server' cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
build: build:
cd server && gox \ 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)" \ -ldflags "-w -X main.VERSION=$(VERSION)" \
-output="../build/{{.OS}}/{{.Arch}}/server" \ -output="../build/{{.OS}}/{{.Arch}}/server" \
./... ./...
@ -46,7 +46,7 @@ test-all-db:
docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest
docker run -d --name couchbase-local-test -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase:latest 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 sh scripts/couchbase-test.sh
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb,dynamodb" go test -p 1 -v ./test 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_scylla_db
docker rm -vf authorizer_mongodb_db docker rm -vf authorizer_mongodb_db
docker rm -vf authorizer_arangodb docker rm -vf authorizer_arangodb

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) | | Railway.app | <a href="https://railway.app/new/template/nwXp1C?referralCode=FEF4uT"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) | | Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
| Render | [![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) | | 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 ### Deploy Authorizer Using Source Code

875
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { AuthorizerSignup } from '@authorizerdev/authorizer-react'; import { AuthorizerSignup, AuthorizerSocialLogin } from '@authorizerdev/authorizer-react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -19,6 +19,7 @@ export default function SignUp({
<Fragment> <Fragment>
<h1 style={{ textAlign: 'center' }}>Sign Up</h1> <h1 style={{ textAlign: 'center' }}>Sign Up</h1>
<br /> <br />
<AuthorizerSocialLogin urlProps={urlProps} />
<AuthorizerSignup urlProps={urlProps} /> <AuthorizerSignup urlProps={urlProps} />
<FooterContent> <FooterContent>
Already have an account? <Link to="/app"> Login</Link> Already have an account? <Link to="/app"> Login</Link>

File diff suppressed because it is too large Load Diff

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

@ -24,6 +24,7 @@ const Features = ({ variables, setVariables }: any) => {
/> />
</Flex> </Flex>
</Flex> </Flex>
<Flex> <Flex>
<Flex w="100%" justifyContent="start" alignItems="center"> <Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Email Verification:</Text> <Text fontSize="sm">Email Verification:</Text>
@ -52,7 +53,7 @@ const Features = ({ variables, setVariables }: any) => {
</Flex> </Flex>
<Flex> <Flex>
<Flex w="100%" justifyContent="start" alignItems="center"> <Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Basic Authentication:</Text> <Text fontSize="sm">Email Basic Authentication:</Text>
</Flex> </Flex>
<Flex justifyContent="start"> <Flex justifyContent="start">
<InputField <InputField
@ -63,6 +64,19 @@ const Features = ({ variables, setVariables }: any) => {
/> />
</Flex> </Flex>
</Flex> </Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Mobile Basic Authentication:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_MOBILE_BASIC_AUTHENTICATION}
hasReversedValue
/>
</Flex>
</Flex>
<Flex> <Flex>
<Flex w="100%" justifyContent="start" alignItems="center"> <Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Sign Up:</Text> <Text fontSize="sm">Sign Up:</Text>
@ -97,6 +111,7 @@ const Features = ({ variables, setVariables }: any) => {
also ignore the user MFA setting. also ignore the user MFA setting.
</Text> </Text>
</Flex> </Flex>
<Flex justifyContent="start" mb={3}> <Flex justifyContent="start" mb={3}>
<InputField <InputField
variables={variables} variables={variables}
@ -106,6 +121,41 @@ const Features = ({ variables, setVariables }: any) => {
/> />
</Flex> </Flex>
</Flex> </Flex>
{!variables.DISABLE_MULTI_FACTOR_AUTHENTICATION && (
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">Time Based OTP (TOTP):</Text>
<Text fontSize="x-small">Note: to enable totp mfa</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_TOTP_LOGIN}
hasReversedValue
/>
</Flex>
</Flex>
)}
{!variables.DISABLE_MULTI_FACTOR_AUTHENTICATION && (
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">EMAIL OTP:</Text>
<Text fontSize="x-small">Note: to enable email otp mfa</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_MAIL_OTP_LOGIN}
hasReversedValue
/>
</Flex>
</Flex>
)}
<Flex alignItems="center"> <Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column"> <Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm"> <Text fontSize="sm">
@ -124,6 +174,19 @@ const Features = ({ variables, setVariables }: any) => {
/> />
</Flex> </Flex>
</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> </Stack>
<Divider paddingY={5} /> <Divider paddingY={5} />
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}> <Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>

View File

@ -17,6 +17,8 @@ import {
FaApple, FaApple,
FaTwitter, FaTwitter,
FaMicrosoft, FaMicrosoft,
FaTwitch,
FaDiscord,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { import {
TextInputType, TextInputType,
@ -308,6 +310,44 @@ const OAuthConfig = ({
/> />
</Center> </Center>
</Flex> </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'}> <Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center <Center
w={isNotSmallerScreen ? '55px' : '35px'} w={isNotSmallerScreen ? '55px' : '35px'}
@ -397,6 +437,85 @@ const OAuthConfig = ({
/> />
</Center> </Center>
</Flex> </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> </Stack>
</Box> </Box>
</div> </div>

View File

@ -9,9 +9,12 @@ export const TextInputType = {
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID', FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID', LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID', APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
DISCORD_CLIENT_ID: 'DISCORD_CLIENT_ID',
TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID', TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID',
MICROSOFT_CLIENT_ID: 'MICROSOFT_CLIENT_ID', MICROSOFT_CLIENT_ID: 'MICROSOFT_CLIENT_ID',
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: 'MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID', MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: 'MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID',
TWITCH_CLIENT_ID: 'TWITCH_CLIENT_ID',
ROBLOX_CLIENT_ID: 'ROBLOX_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM', JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL', REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST', SMTP_HOST: 'SMTP_HOST',
@ -40,8 +43,11 @@ export const HiddenInputType = {
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET', FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET', LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET', APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
DISCORD_CLIENT_SECRET: 'DISCORD_CLIENT_SECRET',
TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET', TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET',
MICROSOFT_CLIENT_SECRET: 'MICROSOFT_CLIENT_SECRET', MICROSOFT_CLIENT_SECRET: 'MICROSOFT_CLIENT_SECRET',
TWITCH_CLIENT_SECRET: 'TWITCH_CLIENT_SECRET',
ROBLOX_CLIENT_SECRET: 'ROBLOX_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET', JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD', SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET', ADMIN_SECRET: 'ADMIN_SECRET',
@ -79,11 +85,15 @@ export const SwitchInputType = {
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN', DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION', DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION', DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
DISABLE_MOBILE_BASIC_AUTHENTICATION: 'DISABLE_MOBILE_BASIC_AUTHENTICATION',
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP', DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV', DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD', DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
DISABLE_MULTI_FACTOR_AUTHENTICATION: 'DISABLE_MULTI_FACTOR_AUTHENTICATION', DISABLE_MULTI_FACTOR_AUTHENTICATION: 'DISABLE_MULTI_FACTOR_AUTHENTICATION',
ENFORCE_MULTI_FACTOR_AUTHENTICATION: 'ENFORCE_MULTI_FACTOR_AUTHENTICATION', ENFORCE_MULTI_FACTOR_AUTHENTICATION: 'ENFORCE_MULTI_FACTOR_AUTHENTICATION',
DISABLE_PLAYGROUND: 'DISABLE_PLAYGROUND',
DISABLE_TOTP_LOGIN: 'DISABLE_TOTP_LOGIN',
DISABLE_MAIL_OTP_LOGIN: 'DISABLE_MAIL_OTP_LOGIN',
}; };
export const DateInputType = { export const DateInputType = {
@ -124,11 +134,17 @@ export interface envVarTypes {
LINKEDIN_CLIENT_SECRET: string; LINKEDIN_CLIENT_SECRET: string;
APPLE_CLIENT_ID: string; APPLE_CLIENT_ID: string;
APPLE_CLIENT_SECRET: string; APPLE_CLIENT_SECRET: string;
DISCORD_CLIENT_ID: string;
DISCORD_CLIENT_SECRET: string;
TWITTER_CLIENT_ID: string; TWITTER_CLIENT_ID: string;
TWITTER_CLIENT_SECRET: string; TWITTER_CLIENT_SECRET: string;
MICROSOFT_CLIENT_ID: string; MICROSOFT_CLIENT_ID: string;
MICROSOFT_CLIENT_SECRET: string; MICROSOFT_CLIENT_SECRET: string;
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: 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] | []; ROLES: [string] | [];
DEFAULT_ROLES: [string] | []; DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | []; PROTECTED_ROLES: [string] | [];
@ -156,6 +172,7 @@ export interface envVarTypes {
DISABLE_MAGIC_LINK_LOGIN: boolean; DISABLE_MAGIC_LINK_LOGIN: boolean;
DISABLE_EMAIL_VERIFICATION: boolean; DISABLE_EMAIL_VERIFICATION: boolean;
DISABLE_BASIC_AUTHENTICATION: boolean; DISABLE_BASIC_AUTHENTICATION: boolean;
DISABLE_MOBILE_BASIC_AUTHENTICATION: boolean;
DISABLE_SIGN_UP: boolean; DISABLE_SIGN_UP: boolean;
DISABLE_STRONG_PASSWORD: boolean; DISABLE_STRONG_PASSWORD: boolean;
OLD_ADMIN_SECRET: string; OLD_ADMIN_SECRET: string;
@ -167,6 +184,9 @@ export interface envVarTypes {
ENFORCE_MULTI_FACTOR_AUTHENTICATION: boolean; ENFORCE_MULTI_FACTOR_AUTHENTICATION: boolean;
DEFAULT_AUTHORIZE_RESPONSE_TYPE: string; DEFAULT_AUTHORIZE_RESPONSE_TYPE: string;
DEFAULT_AUTHORIZE_RESPONSE_MODE: string; DEFAULT_AUTHORIZE_RESPONSE_MODE: string;
DISABLE_PLAYGROUND: boolean;
DISABLE_TOTP_LOGIN: boolean;
DISABLE_MAIL_OTP_LOGIN: boolean;
} }
export const envSubViews = { export const envSubViews = {
@ -220,6 +240,7 @@ export const webhookEventNames = {
'User deleted': 'user.deleted', 'User deleted': 'user.deleted',
'User access enabled': 'user.access_enabled', 'User access enabled': 'user.access_enabled',
'User access revoked': 'user.access_revoked', 'User access revoked': 'user.access_revoked',
'User deactivated': 'user.deactivated',
}; };
export const emailTemplateEventNames = { export const emailTemplateEventNames = {

View File

@ -30,11 +30,17 @@ export const EnvVariablesQuery = `
LINKEDIN_CLIENT_SECRET LINKEDIN_CLIENT_SECRET
APPLE_CLIENT_ID APPLE_CLIENT_ID
APPLE_CLIENT_SECRET APPLE_CLIENT_SECRET
DISCORD_CLIENT_ID
DISCORD_CLIENT_SECRET
TWITTER_CLIENT_ID TWITTER_CLIENT_ID
TWITTER_CLIENT_SECRET TWITTER_CLIENT_SECRET
MICROSOFT_CLIENT_ID MICROSOFT_CLIENT_ID
MICROSOFT_CLIENT_SECRET MICROSOFT_CLIENT_SECRET
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID
TWITCH_CLIENT_ID
TWITCH_CLIENT_SECRET
ROBLOX_CLIENT_ID
ROBLOX_CLIENT_SECRET
DEFAULT_ROLES DEFAULT_ROLES
PROTECTED_ROLES PROTECTED_ROLES
ROLES ROLES
@ -61,6 +67,7 @@ export const EnvVariablesQuery = `
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_SIGN_UP DISABLE_SIGN_UP
DISABLE_STRONG_PASSWORD DISABLE_STRONG_PASSWORD
DISABLE_REDIS_FOR_ENV DISABLE_REDIS_FOR_ENV
@ -73,6 +80,9 @@ export const EnvVariablesQuery = `
ENFORCE_MULTI_FACTOR_AUTHENTICATION ENFORCE_MULTI_FACTOR_AUTHENTICATION
DEFAULT_AUTHORIZE_RESPONSE_TYPE DEFAULT_AUTHORIZE_RESPONSE_TYPE
DEFAULT_AUTHORIZE_RESPONSE_MODE DEFAULT_AUTHORIZE_RESPONSE_MODE
DISABLE_PLAYGROUND
DISABLE_TOTP_LOGIN
DISABLE_MAIL_OTP_LOGIN
} }
} }
`; `;
@ -90,6 +100,7 @@ export const UserDetailsQuery = `
id id
email email
email_verified email_verified
phone_number_verified
given_name given_name
family_name family_name
middle_name middle_name

View File

@ -50,11 +50,17 @@ const Environment = () => {
LINKEDIN_CLIENT_SECRET: '', LINKEDIN_CLIENT_SECRET: '',
APPLE_CLIENT_ID: '', APPLE_CLIENT_ID: '',
APPLE_CLIENT_SECRET: '', APPLE_CLIENT_SECRET: '',
DISCORD_CLIENT_ID: '',
DISCORD_CLIENT_SECRET: '',
TWITTER_CLIENT_ID: '', TWITTER_CLIENT_ID: '',
TWITTER_CLIENT_SECRET: '', TWITTER_CLIENT_SECRET: '',
MICROSOFT_CLIENT_ID: '', MICROSOFT_CLIENT_ID: '',
MICROSOFT_CLIENT_SECRET: '', MICROSOFT_CLIENT_SECRET: '',
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: '', MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: '',
TWITCH_CLIENT_ID: '',
TWITCH_CLIENT_SECRET: '',
ROBLOX_CLIENT_ID: '',
ROBLOX_CLIENT_SECRET: '',
ROLES: [], ROLES: [],
DEFAULT_ROLES: [], DEFAULT_ROLES: [],
PROTECTED_ROLES: [], PROTECTED_ROLES: [],
@ -82,6 +88,7 @@ const Environment = () => {
DISABLE_MAGIC_LINK_LOGIN: false, DISABLE_MAGIC_LINK_LOGIN: false,
DISABLE_EMAIL_VERIFICATION: false, DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false, DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_MOBILE_BASIC_AUTHENTICATION: false,
DISABLE_SIGN_UP: false, DISABLE_SIGN_UP: false,
DISABLE_STRONG_PASSWORD: false, DISABLE_STRONG_PASSWORD: false,
OLD_ADMIN_SECRET: '', OLD_ADMIN_SECRET: '',
@ -93,6 +100,9 @@ const Environment = () => {
ENFORCE_MULTI_FACTOR_AUTHENTICATION: false, ENFORCE_MULTI_FACTOR_AUTHENTICATION: false,
DEFAULT_AUTHORIZE_RESPONSE_TYPE: '', DEFAULT_AUTHORIZE_RESPONSE_TYPE: '',
DEFAULT_AUTHORIZE_RESPONSE_MODE: '', DEFAULT_AUTHORIZE_RESPONSE_MODE: '',
DISABLE_PLAYGROUND: false,
DISABLE_TOTP_LOGIN: false,
DISABLE_MAIL_OTP_LOGIN: true,
}); });
const [fieldVisibility, setFieldVisibility] = React.useState< const [fieldVisibility, setFieldVisibility] = React.useState<
@ -103,7 +113,9 @@ const Environment = () => {
FACEBOOK_CLIENT_SECRET: false, FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false, LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false, APPLE_CLIENT_SECRET: false,
DISCORD_CLIENT_SECRET: false,
TWITTER_CLIENT_SECRET: false, TWITTER_CLIENT_SECRET: false,
TWITCH_CLIENT_SECRET: false,
JWT_SECRET: false, JWT_SECRET: false,
SMTP_PASSWORD: false, SMTP_PASSWORD: false,
ADMIN_SECRET: false, ADMIN_SECRET: false,

View File

@ -165,14 +165,25 @@ export default function Users() {
}; };
const userVerificationHandler = async (user: userDataTypes) => { const userVerificationHandler = async (user: userDataTypes) => {
const { id, email } = user; const { id, email, phone_number } = user;
let params = {};
if (email) {
params = {
id,
email,
email_verified: true,
};
}
if (phone_number) {
params = {
id,
phone_number,
phone_number_verified: true,
};
}
const res = await client const res = await client
.mutation(UpdateUser, { .mutation(UpdateUser, {
params: { params,
id,
email,
email_verified: true,
},
}) })
.toPromise(); .toPromise();
if (res.error) { if (res.error) {
@ -298,7 +309,7 @@ export default function Users() {
<Table variant="simple"> <Table variant="simple">
<Thead> <Thead>
<Tr> <Tr>
<Th>Email</Th> <Th>Email / Phone</Th>
<Th>Created At</Th> <Th>Created At</Th>
<Th>Signup Methods</Th> <Th>Signup Methods</Th>
<Th>Roles</Th> <Th>Roles</Th>
@ -314,10 +325,15 @@ export default function Users() {
</Thead> </Thead>
<Tbody> <Tbody>
{userList.map((user: userDataTypes) => { {userList.map((user: userDataTypes) => {
const { email_verified, created_at, ...rest }: any = user; const {
email_verified,
phone_number_verified,
created_at,
...rest
}: any = user;
return ( return (
<Tr key={user.id} style={{ fontSize: 14 }}> <Tr key={user.id} style={{ fontSize: 14 }}>
<Td maxW="300">{user.email}</Td> <Td maxW="300">{user.email || user.phone_number}</Td>
<Td> <Td>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')} {dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
</Td> </Td>
@ -327,9 +343,15 @@ export default function Users() {
<Tag <Tag
size="sm" size="sm"
variant="outline" 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> </Tag>
</Td> </Td>
<Td> <Td>
@ -368,13 +390,14 @@ export default function Users() {
</Flex> </Flex>
</MenuButton> </MenuButton>
<MenuList> <MenuList>
{!user.email_verified && ( {!user.email_verified &&
<MenuItem !user.phone_number_verified && (
onClick={() => userVerificationHandler(user)} <MenuItem
> onClick={() => userVerificationHandler(user)}
Verify User >
</MenuItem> Verify User
)} </MenuItem>
)}
<EditUserModal <EditUserModal
user={rest} user={rest}
updateUserList={updateUserList} updateUserList={updateUserList}

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=

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

@ -19,8 +19,14 @@ const (
AuthRecipeMethodLinkedIn = "linkedin" AuthRecipeMethodLinkedIn = "linkedin"
// AuthRecipeMethodApple is the apple auth method // AuthRecipeMethodApple is the apple auth method
AuthRecipeMethodApple = "apple" AuthRecipeMethodApple = "apple"
// AuthRecipeMethodDiscord is the discord auth method
AuthRecipeMethodDiscord = "discord"
// AuthRecipeMethodTwitter is the twitter auth method // AuthRecipeMethodTwitter is the twitter auth method
AuthRecipeMethodTwitter = "twitter" AuthRecipeMethodTwitter = "twitter"
// AuthRecipeMethodMicrosoft is the microsoft auth method // AuthRecipeMethodMicrosoft is the microsoft auth method
AuthRecipeMethodMicrosoft = "microsoft" 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" AppCookieName = "cookie"
// AdminCookieName is the name of the cookie that is used to store the admin token // AdminCookieName is the name of the cookie that is used to store the admin token
AdminCookieName = "authorizer-admin" AdminCookieName = "authorizer-admin"
// MfaCookieName is the name of the cookie that is used to store the mfa session
MfaCookieName = "mfa"
) )

View File

@ -5,6 +5,8 @@ const (
DbTypePostgres = "postgres" DbTypePostgres = "postgres"
// DbTypeSqlite is the sqlite database type // DbTypeSqlite is the sqlite database type
DbTypeSqlite = "sqlite" DbTypeSqlite = "sqlite"
// DbTypeLibSQL is the libsql / Turso database type
DbTypeLibSQL = "libsql"
// DbTypeMysql is the mysql database type // DbTypeMysql is the mysql database type
DbTypeMysql = "mysql" DbTypeMysql = "mysql"
// DbTypeSqlserver is the sqlserver database type // DbTypeSqlserver is the sqlserver database type

View File

@ -108,6 +108,10 @@ const (
EnvKeyAppleClientID = "APPLE_CLIENT_ID" EnvKeyAppleClientID = "APPLE_CLIENT_ID"
// EnvKeyAppleClientSecret key for env variable APPLE_CLIENT_SECRET // EnvKeyAppleClientSecret key for env variable APPLE_CLIENT_SECRET
EnvKeyAppleClientSecret = "APPLE_CLIENT_SECRET" EnvKeyAppleClientSecret = "APPLE_CLIENT_SECRET"
// 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 key for env variable TWITTER_CLIENT_ID
EnvKeyTwitterClientID = "TWITTER_CLIENT_ID" EnvKeyTwitterClientID = "TWITTER_CLIENT_ID"
// EnvKeyTwitterClientSecret key for env variable TWITTER_CLIENT_SECRET // EnvKeyTwitterClientSecret key for env variable TWITTER_CLIENT_SECRET
@ -118,6 +122,14 @@ const (
EnvKeyMicrosoftActiveDirectoryTenantID = "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID" EnvKeyMicrosoftActiveDirectoryTenantID = "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID"
// EnvKeyMicrosoftClientSecret key for env variable MICROSOFT_CLIENT_SECRET // EnvKeyMicrosoftClientSecret key for env variable MICROSOFT_CLIENT_SECRET
EnvKeyMicrosoftClientSecret = "MICROSOFT_CLIENT_SECRET" EnvKeyMicrosoftClientSecret = "MICROSOFT_CLIENT_SECRET"
// EnvKeyTwitchClientID key for env variable TWITCH_CLIENT_ID
EnvKeyTwitchClientID = "TWITCH_CLIENT_ID"
// EnvKeyTwitchClientSecret key for env variable TWITCH_CLIENT_SECRET
EnvKeyTwitchClientSecret = "TWITCH_CLIENT_SECRET"
// EnvKeyRobloxClientID key for env variable ROBLOX_CLIENT_ID
EnvKeyRobloxClientID = "ROBLOX_CLIENT_ID"
// EnvKeyRobloxClientSecret key for env variable ROBLOX_CLIENT_SECRET
EnvKeyRobloxClientSecret = "ROBLOX_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME // EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME" EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO // EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
@ -160,9 +172,18 @@ const (
// EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION // EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION
// this variable is used to completely disable multi factor authentication. It will have no effect on profile preference // this variable is used to completely disable multi factor authentication. It will have no effect on profile preference
EnvKeyDisableMultiFactorAuthentication = "DISABLE_MULTI_FACTOR_AUTHENTICATION" 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 // EnvKeyDisablePhoneVerification is key for env variable DISABLE_PHONE_VERIFICATION
// this variable is used to disable phone verification // this variable is used to disable phone verification
EnvKeyDisablePhoneVerification = "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 // Slice variables
// EnvKeyRoles key for env variable ROLES // EnvKeyRoles key for env variable ROLES

View File

@ -16,4 +16,7 @@ const (
ResponseTypeToken = "token" ResponseTypeToken = "token"
// For the Implicit grant of id_token, use response_type=id_token to include an identifier token. // For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
ResponseTypeIDToken = "id_token" ResponseTypeIDToken = "id_token"
// Constant indicating the "signup" screen hint for customizing authentication process and redirect to a signup page.
ScreenHintSignUp = "signup"
) )

View File

@ -17,6 +17,10 @@ const (
TwitterUserInfoURL = "https://api.twitter.com/2/users/me?user.fields=id,name,profile_image_url,username" 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. // Get microsoft user info.
// Ref: https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo // Ref: https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo
MicrosoftUserInfoURL = "https://graph.microsoft.com/oidc/userinfo" MicrosoftUserInfoURL = "https://graph.microsoft.com/oidc/userinfo"

View File

@ -15,4 +15,6 @@ const (
UserAccessEnabledWebhookEvent = `user.access_enabled` UserAccessEnabledWebhookEvent = `user.access_enabled`
// UserDeletedWebhookEvent name for user deleted event // UserDeletedWebhookEvent name for user deleted event
UserDeletedWebhookEvent = `user.deleted` 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 package crypto
import ( import (
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/hex"
"encoding/json" "encoding/json"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
@ -125,12 +127,27 @@ func EncryptEnvData(data map[string]interface{}) (string, error) {
return EncryptB64(string(encryptedConfig)), nil 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 // EncryptPassword is used for encrypting password
func EncryptPassword(password string) (string, error) { 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 { if err != nil {
return "", err return "", err
} }
return string(pw), nil return string(pw), nil
} }

View File

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

@ -37,7 +37,6 @@ func InitDB() error {
return err return err
} }
} }
if isArangoDB { if isArangoDB {
log.Info("Initializing ArangoDB Driver") log.Info("Initializing ArangoDB Driver")
Provider, err = arangodb.NewProvider() Provider, err = arangodb.NewProvider()

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,6 +1,6 @@
package models package models
// Collections / Tables available for authorizer in the database // CollectionList / Tables available for authorizer in the database
type CollectionList struct { type CollectionList struct {
User string User string
VerificationRequest string VerificationRequest string
@ -11,6 +11,7 @@ type CollectionList struct {
EmailTemplate string EmailTemplate string
OTP string OTP string
SMSVerificationRequest string SMSVerificationRequest string
Authenticators string
} }
var ( var (
@ -27,5 +28,6 @@ var (
EmailTemplate: Prefix + "email_templates", EmailTemplate: Prefix + "email_templates",
OTP: Prefix + "otps", OTP: Prefix + "otps",
SMSVerificationRequest: Prefix + "sms_verification_requests", SMSVerificationRequest: Prefix + "sms_verification_requests",
Authenticators: Prefix + "authenticators",
} }
) )

View File

@ -11,8 +11,8 @@ const (
type OTP struct { type OTP struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` 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"`
PhoneNumber string `gorm:"index:unique_index_phone_number,unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"` 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"` Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"` 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"` CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`

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

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

@ -23,7 +23,7 @@ func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, er
configCollection, _ := p.db.Collection(ctx, models.Collections.Env) configCollection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), env) meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), env)
if err != nil { if err != nil {
return env, err return nil, err
} }
env.Key = meta.Key env.Key = meta.Key
env.ID = meta.ID.String() env.ID = meta.ID.String()
@ -36,7 +36,7 @@ func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env,
collection, _ := p.db.Collection(ctx, models.Collections.Env) collection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := collection.UpdateDocument(ctx, env.Key, env) meta, err := collection.UpdateDocument(ctx, env.Key, env)
if err != nil { if err != nil {
return env, err return nil, err
} }
env.Key = meta.Key env.Key = meta.Key
@ -50,7 +50,7 @@ func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env) query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env)
cursor, err := p.db.Query(ctx, query, nil) cursor, err := p.db.Query(ctx, query, nil)
if err != nil { if err != nil {
return env, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
for { for {
@ -62,7 +62,7 @@ func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
} }
_, err := cursor.ReadDocument(ctx, &env) _, err := cursor.ReadDocument(ctx, &env)
if err != nil { if err != nil {
return env, err return nil, err
} }
} }

View File

@ -186,6 +186,7 @@ func NewProvider() (*provider, error) {
webhookLogCollection.EnsureHashIndex(ctx, []string{"webhook_id"}, &arangoDriver.EnsureHashIndexOptions{ webhookLogCollection.EnsureHashIndex(ctx, []string{"webhook_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true, Sparse: true,
}) })
emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate) emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate)
if err != nil { if err != nil {
return nil, err return nil, err
@ -204,6 +205,7 @@ func NewProvider() (*provider, error) {
Unique: true, Unique: true,
Sparse: true, Sparse: true,
}) })
otpCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.OTP) otpCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.OTP)
if err != nil { if err != nil {
return nil, err return nil, err
@ -222,6 +224,26 @@ func NewProvider() (*provider, error) {
Unique: true, Unique: true,
Sparse: 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{ return &provider{
db: arangodb, db: arangodb,
}, err }, err

View File

@ -27,7 +27,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if user.Roles == "" { if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Roles = defaultRoles user.Roles = defaultRoles
} }
@ -36,6 +36,10 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID { 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") 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.CreatedAt = time.Now().Unix()
@ -43,7 +47,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
userCollection, _ := p.db.Collection(ctx, models.Collections.User) userCollection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), user) meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), user)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Key = meta.Key user.Key = meta.Key
user.ID = meta.ID.String() user.ID = meta.ID.String()
@ -58,7 +62,7 @@ func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.U
collection, _ := p.db.Collection(ctx, models.Collections.User) collection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := collection.UpdateDocument(ctx, user.Key, user) meta, err := collection.UpdateDocument(ctx, user.Key, user)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Key = meta.Key user.Key = meta.Key
@ -125,19 +129,19 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
} }
cursor, err := p.db.Query(ctx, query, bindVars) cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil { if err != nil {
return user, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
for { for {
if !cursor.HasMore() { if !cursor.HasMore() {
if user == nil { if user == nil {
return user, fmt.Errorf("user not found") return nil, fmt.Errorf("user not found")
} }
break break
} }
_, err := cursor.ReadDocument(ctx, &user) _, err := cursor.ReadDocument(ctx, &user)
if err != nil { if err != nil {
return user, err return nil, err
} }
} }
return user, nil return user, nil
@ -152,19 +156,19 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, er
} }
cursor, err := p.db.Query(ctx, query, bindVars) cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil { if err != nil {
return user, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
for { for {
if !cursor.HasMore() { if !cursor.HasMore() {
if user == nil { if user == nil {
return user, fmt.Errorf("user not found") return nil, fmt.Errorf("user not found")
} }
break break
} }
_, err := cursor.ReadDocument(ctx, &user) _, err := cursor.ReadDocument(ctx, &user)
if err != nil { if err != nil {
return user, err return nil, err
} }
} }
return user, nil return user, nil

View File

@ -22,7 +22,7 @@ func (p *provider) AddVerificationRequest(ctx context.Context, verificationReque
verificationRequestRequestCollection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest) verificationRequestRequestCollection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest)
meta, err := verificationRequestRequestCollection.CreateDocument(ctx, verificationRequest) meta, err := verificationRequestRequestCollection.CreateDocument(ctx, verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
verificationRequest.Key = meta.Key verificationRequest.Key = meta.Key
verificationRequest.ID = meta.ID.String() verificationRequest.ID = meta.ID.String()
@ -38,7 +38,7 @@ func (p *provider) GetVerificationRequestByToken(ctx context.Context, token stri
} }
cursor, err := p.db.Query(ctx, query, bindVars) cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
for { for {
@ -50,7 +50,7 @@ func (p *provider) GetVerificationRequestByToken(ctx context.Context, token stri
} }
_, err := cursor.ReadDocument(ctx, &verificationRequest) _, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
} }
return verificationRequest, nil return verificationRequest, nil
@ -66,7 +66,7 @@ func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email stri
} }
cursor, err := p.db.Query(ctx, query, bindVars) cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
for { for {
@ -78,7 +78,7 @@ func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email stri
} }
_, err := cursor.ReadDocument(ctx, &verificationRequest) _, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
} }
return verificationRequest, nil return verificationRequest, 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

@ -20,7 +20,7 @@ func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, er
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) 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() err := p.db.Query(insertEnvQuery).Exec()
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
@ -32,7 +32,7 @@ func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env,
updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID) updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID)
err := p.db.Query(updateEnvQuery).Exec() err := p.db.Query(updateEnvQuery).Exec()
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
} }

View File

@ -261,12 +261,26 @@ func NewProvider() (*provider, error) {
log.Debug("Failed to alter table as column exists: ", err) log.Debug("Failed to alter table as column exists: ", err)
// continue // 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 // 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) 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() err = session.Query(otpIndexQueryPhoneNumber).Exec()
if err != nil { if err != nil {
return nil, err 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{ return &provider{
db: session, db: session,
}, err }, err

View File

@ -26,7 +26,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if user.Roles == "" { if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Roles = defaultRoles user.Roles = defaultRoles
} }
@ -35,6 +35,10 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID { 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") 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.CreatedAt = time.Now().Unix()
@ -42,7 +46,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
bytes, err := json.Marshal(user) bytes, err := json.Marshal(user)
if err != nil { if err != nil {
return user, err return nil, err
} }
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling // use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
@ -51,7 +55,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
userMap := map[string]interface{}{} userMap := map[string]interface{}{}
err = decoder.Decode(&userMap) err = decoder.Decode(&userMap)
if err != nil { if err != nil {
return user, err return nil, err
} }
fields := "(" fields := "("
@ -78,8 +82,9 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
query := fmt.Sprintf("INSERT INTO %s %s VALUES %s IF NOT EXISTS", KeySpace+"."+models.Collections.User, fields, values) query := fmt.Sprintf("INSERT INTO %s %s VALUES %s IF NOT EXISTS", KeySpace+"."+models.Collections.User, fields, values)
err = p.db.Query(query).Exec() err = p.db.Query(query).Exec()
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
@ -91,7 +96,7 @@ func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.U
bytes, err := json.Marshal(user) bytes, err := json.Marshal(user)
if err != nil { if err != nil {
return user, err return nil, err
} }
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling // use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes))) decoder := json.NewDecoder(strings.NewReader(string(bytes)))
@ -99,7 +104,7 @@ func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.U
userMap := map[string]interface{}{} userMap := map[string]interface{}{}
err = decoder.Decode(&userMap) err = decoder.Decode(&userMap)
if err != nil { if err != nil {
return user, err return nil, err
} }
updateFields := "" updateFields := ""
@ -130,7 +135,7 @@ func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.U
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, updateFields, user.ID) query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, updateFields, user.ID)
err = p.db.Query(query).Exec() err = p.db.Query(query).Exec()
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
@ -177,13 +182,17 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
// there is no offset in cassandra // there is no offset in cassandra
// so we fetch till limit + offset // so we fetch till limit + offset
// and return the results from offset to limit // and return the results from offset to limit
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, 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() scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0) counter := int64(0)
for scanner.Next() { for scanner.Next() {
if counter >= pagination.Offset { if counter >= pagination.Offset {
var user models.User var user models.User
err := scanner.Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.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 { if err != nil {
return nil, err return nil, err
} }
@ -200,8 +209,8 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
// GetUserByEmail to get user information from database using email address // GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) { func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user models.User var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email) query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, 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.CreatedAt, &user.UpdatedAt) err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.AppData, &user.CreatedAt, &user.UpdatedAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -211,8 +220,8 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
// GetUserByID to get user information from database using user ID // GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) { func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
var user models.User var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id) query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, 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.CreatedAt, &user.UpdatedAt) err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.AppData, &user.CreatedAt, &user.UpdatedAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -297,17 +306,15 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
return err return err
} }
} }
} }
return nil return nil
} }
// GetUserByPhoneNumber to get user information from database using phone number // GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) { func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user models.User var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE phone_number = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, phoneNumber) 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.CreatedAt, &user.UpdatedAt) err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.AppData, &user.CreatedAt, &user.UpdatedAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -23,7 +23,7 @@ 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) 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() err := p.db.Query(query).Exec()
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil
} }
@ -74,7 +74,6 @@ func (p *provider) ListVerificationRequests(ctx context.Context, pagination *mod
var verificationRequest models.VerificationRequest var verificationRequest models.VerificationRequest
err := scanner.Scan(&verificationRequest.ID, &verificationRequest.Token, &verificationRequest.Identifier, &verificationRequest.ExpiresAt, &verificationRequest.Email, &verificationRequest.Nonce, &verificationRequest.RedirectURI, &verificationRequest.CreatedAt, &verificationRequest.UpdatedAt) err := scanner.Scan(&verificationRequest.ID, &verificationRequest.Token, &verificationRequest.Identifier, &verificationRequest.ExpiresAt, &verificationRequest.Email, &verificationRequest.Nonce, &verificationRequest.RedirectURI, &verificationRequest.CreatedAt, &verificationRequest.UpdatedAt)
if err != nil { if err != nil {
fmt.Println("=> getting error here...", err)
return nil, err return nil, err
} }
verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest()) verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest())

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

@ -24,7 +24,7 @@ func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, er
} }
_, err := p.db.Collection(models.Collections.Env).Insert(env.ID, env, &insertOpt) _, err := p.db.Collection(models.Collections.Env).Insert(env.ID, env, &insertOpt)
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
} }
@ -40,7 +40,7 @@ func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env,
PositionalParameters: []interface{}{env.EnvData, env.UpdatedAt, env.UpdatedAt, env.ID}, PositionalParameters: []interface{}{env.EnvData, env.UpdatedAt, env.UpdatedAt, env.ID},
}) })
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
} }
@ -55,11 +55,11 @@ func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
}) })
if err != nil { if err != nil {
return env, err return nil, err
} }
err = q.One(&env) err = q.One(&env)
if err != nil { if err != nil {
return env, err return nil, err
} }
env.Hash = env.EncryptionKey env.Hash = env.EncryptionKey
return env, nil return env, nil

View File

@ -50,7 +50,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
} }
_, err := p.db.Collection(models.Collections.OTP).Insert(otp.ID, otp, &insertOpt) _, err := p.db.Collection(models.Collections.OTP).Insert(otp.ID, otp, &insertOpt)
if err != nil { if err != nil {
return otp, err return nil, err
} }
} else { } else {
query := fmt.Sprintf(`UPDATE %s.%s SET otp=$1, expires_at=$2, updated_at=$3 WHERE _id=$4`, p.scopeName, models.Collections.OTP) query := fmt.Sprintf(`UPDATE %s.%s SET otp=$1, expires_at=$2, updated_at=$3 WHERE _id=$4`, p.scopeName, models.Collections.OTP)
@ -58,7 +58,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
PositionalParameters: []interface{}{otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID}, PositionalParameters: []interface{}{otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID},
}) })
if err != nil { if err != nil {
return otp, err return nil, err
} }
} }
return otp, nil return otp, nil

View File

@ -127,7 +127,7 @@ func CreateBucketAndScope(cluster *gocb.Cluster, bucketName string, scopeName st
if scopeName != defaultScope { if scopeName != defaultScope {
err = bucket.Collections().CreateScope(scopeName, nil) err = bucket.Collections().CreateScope(scopeName, nil)
if err != nil && !errors.Is(err, gocb.ErrScopeExists) { if err != nil && !errors.Is(err, gocb.ErrScopeExists) {
return bucket, err return nil, err
} }
} }
return bucket, nil return bucket, nil

View File

@ -47,7 +47,7 @@ func (p *provider) GetTotalDocs(ctx context.Context, collection string) (int64,
}) })
queryRes.One(&totalDocs) queryRes.One(&totalDocs)
if err != nil { if err != nil {
return totalDocs.Total, err return 0, err
} }
return totalDocs.Total, nil return totalDocs.Total, nil
} }

View File

@ -4,12 +4,14 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/couchbase/gocb/v2" "github.com/couchbase/gocb/v2"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -23,11 +25,21 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if user.Roles == "" { if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Roles = defaultRoles 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.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
insertOpt := gocb.InsertOptions{ insertOpt := gocb.InsertOptions{
@ -35,7 +47,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
} }
_, err := p.db.Collection(models.Collections.User).Insert(user.ID, user, &insertOpt) _, err := p.db.Collection(models.Collections.User).Insert(user.ID, user, &insertOpt)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -43,12 +55,12 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
// UpdateUser to update user information in database // 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() user.UpdatedAt = time.Now().Unix()
unsertOpt := gocb.UpsertOptions{ upsertOpt := gocb.UpsertOptions{
Context: ctx, Context: ctx,
} }
_, err := p.db.Collection(models.Collections.User).Upsert(user.ID, user, &unsertOpt) _, err := p.db.Collection(models.Collections.User).Upsert(user.ID, user, &upsertOpt)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -69,7 +81,7 @@ func (p *provider) DeleteUser(ctx context.Context, user *models.User) error {
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) {
users := []*model.User{} users := []*model.User{}
paginationClone := pagination 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, created_at, updated_at FROM %s.%s ORDER BY id OFFSET $1 LIMIT $2", p.scopeName, models.Collections.User) 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{ queryResult, err := p.db.Query(userQuery, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx, Context: ctx,
@ -103,18 +115,18 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
// GetUserByEmail to get user information from database using email address // GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) { func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user *models.User var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.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{ q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx, Context: ctx,
PositionalParameters: []interface{}{email}, PositionalParameters: []interface{}{email},
}) })
if err != nil { if err != nil {
return user, err return nil, err
} }
err = q.One(&user) err = q.One(&user)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -122,18 +134,18 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
// GetUserByID to get user information from database using user ID // GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) { func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
var user *models.User var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.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{ q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx, Context: ctx,
PositionalParameters: []interface{}{id}, PositionalParameters: []interface{}{id},
}) })
if err != nil { if err != nil {
return user, err return nil, err
} }
err = q.One(&user) err = q.One(&user)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -175,18 +187,18 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
// GetUserByPhoneNumber to get user information from database using phone number // GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) { func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user *models.User var user *models.User
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE phone_number = $1 LIMIT 1", p.scopeName, models.Collections.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{ q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
Context: ctx, Context: ctx,
PositionalParameters: []interface{}{phoneNumber}, PositionalParameters: []interface{}{phoneNumber},
}) })
if err != nil { if err != nil {
return user, err return nil, err
} }
err = q.One(&user) err = q.One(&user)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }

View File

@ -25,7 +25,7 @@ func (p *provider) AddVerificationRequest(ctx context.Context, verificationReque
} }
_, err := p.db.Collection(models.Collections.VerificationRequest).Insert(verificationRequest.ID, verificationRequest, &insertOpt) _, err := p.db.Collection(models.Collections.VerificationRequest).Insert(verificationRequest.ID, verificationRequest, &insertOpt)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil
} }
@ -44,12 +44,12 @@ func (p *provider) GetVerificationRequestByToken(ctx context.Context, token stri
}) })
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
err = queryResult.One(&verificationRequest) err = queryResult.One(&verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil
} }
@ -69,7 +69,7 @@ func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email stri
var verificationRequest *models.VerificationRequest var verificationRequest *models.VerificationRequest
err = queryResult.One(&verificationRequest) err = queryResult.One(&verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil
} }

View File

@ -29,7 +29,7 @@ func (p *provider) AddWebhook(ctx context.Context, webhook *models.Webhook) (*mo
} }
_, err := p.db.Collection(models.Collections.Webhook).Insert(webhook.ID, webhook, &insertOpt) _, err := p.db.Collection(models.Collections.Webhook).Insert(webhook.ID, webhook, &insertOpt)
if err != nil { if err != nil {
return webhook.AsAPIWebhook(), err return nil, err
} }
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }

View File

@ -25,7 +25,7 @@ func (p *provider) AddWebhookLog(ctx context.Context, webhookLog *models.Webhook
} }
_, err := p.db.Collection(models.Collections.WebhookLog).Insert(webhookLog.ID, webhookLog, &insertOpt) _, err := p.db.Collection(models.Collections.WebhookLog).Insert(webhookLog.ID, webhookLog, &insertOpt)
if err != nil { if err != nil {
return webhookLog.AsAPIWebhookLog(), err return nil, err
} }
return webhookLog.AsAPIWebhookLog(), nil return webhookLog.AsAPIWebhookLog(), nil
} }

View File

@ -0,0 +1,57 @@
package dynamodb
import (
"context"
"time"
"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
}
collection := p.db.Table(models.Collections.Authenticators)
if authenticators.ID == "" {
authenticators.ID = uuid.New().String()
}
authenticators.CreatedAt = time.Now().Unix()
authenticators.UpdatedAt = time.Now().Unix()
err := collection.Put(authenticators).RunWithContext(ctx)
if err != nil {
return nil, err
}
return authenticators, nil
}
func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
collection := p.db.Table(models.Collections.Authenticators)
if authenticators.ID != "" {
authenticators.UpdatedAt = time.Now().Unix()
err := UpdateByHashKey(collection, "id", authenticators.ID, authenticators)
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
collection := p.db.Table(models.Collections.Authenticators)
iter := collection.Scan().Filter("'user_id' = ?", userId).Filter("'method' = ?", authenticatorType).Iter()
for iter.NextWithContext(ctx, &authenticators) {
return authenticators, nil
}
err := iter.Err()
if err != nil {
return nil, err
}
return authenticators, nil
}

View File

@ -21,7 +21,7 @@ func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, er
env.UpdatedAt = time.Now().Unix() env.UpdatedAt = time.Now().Unix()
err := collection.Put(env).RunWithContext(ctx) err := collection.Put(env).RunWithContext(ctx)
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
} }
@ -32,7 +32,7 @@ func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env,
env.UpdatedAt = time.Now().Unix() env.UpdatedAt = time.Now().Unix()
err := UpdateByHashKey(collection, "id", env.ID, env) err := UpdateByHashKey(collection, "id", env.ID, env)
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
} }
@ -45,7 +45,7 @@ func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
iter := collection.Scan().Limit(1).Iter() iter := collection.Scan().Limit(1).Iter()
for iter.NextWithContext(ctx, &env) { for iter.NextWithContext(ctx, &env) {
if env == nil { if env == nil {
return env, errors.New("no documets found") return nil, errors.New("no documets found")
} else { } else {
return env, nil return env, nil
} }

View File

@ -52,6 +52,7 @@ func NewProvider() (*provider, error) {
db.CreateTable(models.Collections.VerificationRequest, models.VerificationRequest{}).Wait() db.CreateTable(models.Collections.VerificationRequest, models.VerificationRequest{}).Wait()
db.CreateTable(models.Collections.Webhook, models.Webhook{}).Wait() db.CreateTable(models.Collections.Webhook, models.Webhook{}).Wait()
db.CreateTable(models.Collections.WebhookLog, models.WebhookLog{}).Wait() db.CreateTable(models.Collections.WebhookLog, models.WebhookLog{}).Wait()
db.CreateTable(models.Collections.Authenticators, models.Authenticator{}).Wait()
return &provider{ return &provider{
db: db, db: db,
}, nil }, nil

View File

@ -26,20 +26,24 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if user.Roles == "" { if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Roles = defaultRoles user.Roles = defaultRoles
} }
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" { if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil { 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") 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.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
err := collection.Put(user).RunWithContext(ctx) err := collection.Put(user).RunWithContext(ctx)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -51,12 +55,8 @@ func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.U
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
err := UpdateByHashKey(collection, "id", user.ID, user) err := UpdateByHashKey(collection, "id", user.ID, user)
if err != nil { if err != nil {
return user, err return nil, err
} }
if err != nil {
return user, err
}
} }
return user, nil return user, nil
} }
@ -126,7 +126,7 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
user = users[0] user = users[0]
return user, nil return user, nil
} else { } else {
return user, errors.New("no record found") return nil, errors.New("no record found")
} }
} }
@ -136,8 +136,8 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, er
var user *models.User var user *models.User
err := collection.Get("id", id).OneWithContext(ctx, &user) err := collection.Get("id", id).OneWithContext(ctx, &user)
if err != nil { if err != nil {
if user.Email == "" { if refs.StringValue(user.Email) == "" {
return user, errors.New("no documets found") return nil, errors.New("no documets found")
} else { } else {
return user, nil return user, nil
} }

View File

@ -19,7 +19,7 @@ func (p *provider) AddVerificationRequest(ctx context.Context, verificationReque
verificationRequest.UpdatedAt = time.Now().Unix() verificationRequest.UpdatedAt = time.Now().Unix()
err := collection.Put(verificationRequest).RunWithContext(ctx) err := collection.Put(verificationRequest).RunWithContext(ctx)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
} }
return verificationRequest, nil return verificationRequest, nil
@ -35,7 +35,7 @@ func (p *provider) GetVerificationRequestByToken(ctx context.Context, token stri
} }
err := iter.Err() err := iter.Err()
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil
} }
@ -50,7 +50,7 @@ func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email stri
} }
err := iter.Err() err := iter.Err()
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil
} }

View File

@ -91,7 +91,7 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
return nil, err return nil, err
} }
if webhook.ID == "" { if webhook.ID == "" {
return webhook.AsAPIWebhook(), errors.New("no documets found") return nil, errors.New("no documets found")
} }
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }

View File

@ -0,0 +1,52 @@
package mongodb
import (
"context"
"time"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"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()
authenticators.Key = authenticators.ID
authenticatorsCollection := p.db.Collection(models.Collections.Authenticators, options.Collection())
_, err := authenticatorsCollection.InsertOne(ctx, authenticators)
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()
authenticatorsCollection := p.db.Collection(models.Collections.Authenticators, options.Collection())
_, err := authenticatorsCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": authenticators.ID}}, bson.M{"$set": authenticators})
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
authenticatorsCollection := p.db.Collection(models.Collections.Authenticators, options.Collection())
err := authenticatorsCollection.FindOne(ctx, bson.M{"user_id": userId, "method": authenticatorType}).Decode(&authenticators)
if err != nil {
return nil, err
}
return authenticators, nil
}

View File

@ -22,7 +22,7 @@ func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, er
configCollection := p.db.Collection(models.Collections.Env, options.Collection()) configCollection := p.db.Collection(models.Collections.Env, options.Collection())
_, err := configCollection.InsertOne(ctx, env) _, err := configCollection.InsertOne(ctx, env)
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
} }
@ -33,7 +33,7 @@ func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env,
configCollection := p.db.Collection(models.Collections.Env, options.Collection()) configCollection := p.db.Collection(models.Collections.Env, options.Collection())
_, err := configCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions()) _, err := configCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions())
if err != nil { if err != nil {
return env, err return nil, err
} }
return env, nil return env, nil
} }
@ -44,13 +44,13 @@ func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
configCollection := p.db.Collection(models.Collections.Env, options.Collection()) configCollection := p.db.Collection(models.Collections.Env, options.Collection())
cursor, err := configCollection.Find(ctx, bson.M{}, options.Find()) cursor, err := configCollection.Find(ctx, bson.M{}, options.Find())
if err != nil { if err != nil {
return env, err return nil, err
} }
defer cursor.Close(ctx) defer cursor.Close(ctx)
for cursor.Next(nil) { for cursor.Next(nil) {
err := cursor.Decode(&env) err := cursor.Decode(&env)
if err != nil { if err != nil {
return env, err return nil, err
} }
} }
if env == nil { if env == nil {

View File

@ -47,8 +47,6 @@ func NewProvider() (*provider, error) {
Keys: bson.M{"email": 1}, Keys: bson.M{"email": 1},
Options: options.Index().SetUnique(true).SetSparse(true), Options: options.Index().SetUnique(true).SetSparse(true),
}, },
}, options.CreateIndexes())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
{ {
Keys: bson.M{"phone_number": 1}, Keys: bson.M{"phone_number": 1},
Options: options.Index().SetUnique(true).SetSparse(true).SetPartialFilterExpression(map[string]interface{}{ Options: options.Index().SetUnique(true).SetSparse(true).SetPartialFilterExpression(map[string]interface{}{
@ -56,7 +54,6 @@ func NewProvider() (*provider, error) {
}), }),
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
mongodb.CreateCollection(ctx, models.Collections.VerificationRequest, options.CreateCollection()) mongodb.CreateCollection(ctx, models.Collections.VerificationRequest, options.CreateCollection())
verificationRequestCollection := mongodb.Collection(models.Collections.VerificationRequest, options.Collection()) verificationRequestCollection := mongodb.Collection(models.Collections.VerificationRequest, options.Collection())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
@ -125,6 +122,15 @@ func NewProvider() (*provider, error) {
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
mongodb.CreateCollection(ctx, models.Collections.Authenticators, options.CreateCollection())
authenticatorsCollection := mongodb.Collection(models.Collections.Authenticators, options.Collection())
authenticatorsCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
{
Keys: bson.M{"user_id": 1},
Options: options.Index().SetSparse(true),
},
}, options.CreateIndexes())
return &provider{ return &provider{
db: mongodb, db: mongodb,
}, nil }, nil

View File

@ -2,12 +2,15 @@ package mongodb
import ( import (
"context" "context"
"fmt"
"strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/google/uuid" "github.com/google/uuid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
@ -23,17 +26,26 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if user.Roles == "" { if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Roles = defaultRoles 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.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
user.Key = user.ID user.Key = user.ID
userCollection := p.db.Collection(models.Collections.User, options.Collection()) userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.InsertOne(ctx, user) _, err := userCollection.InsertOne(ctx, user)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -44,7 +56,7 @@ func (p *provider) UpdateUser(ctx context.Context, user *models.User) (*models.U
userCollection := p.db.Collection(models.Collections.User, options.Collection()) userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions()) _, err := userCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -103,7 +115,7 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
userCollection := p.db.Collection(models.Collections.User, options.Collection()) userCollection := p.db.Collection(models.Collections.User, options.Collection())
err := userCollection.FindOne(ctx, bson.M{"email": email}).Decode(&user) err := userCollection.FindOne(ctx, bson.M{"email": email}).Decode(&user)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }
@ -114,7 +126,7 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, er
userCollection := p.db.Collection(models.Collections.User, options.Collection()) userCollection := p.db.Collection(models.Collections.User, options.Collection())
err := userCollection.FindOne(ctx, bson.M{"_id": id}).Decode(&user) err := userCollection.FindOne(ctx, bson.M{"_id": id}).Decode(&user)
if err != nil { if err != nil {
return user, err return nil, err
} }
return user, nil return user, nil
} }

View File

@ -22,7 +22,7 @@ func (p *provider) AddVerificationRequest(ctx context.Context, verificationReque
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection()) verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.InsertOne(ctx, verificationRequest) _, err := verificationRequestCollection.InsertOne(ctx, verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
} }
@ -36,7 +36,7 @@ func (p *provider) GetVerificationRequestByToken(ctx context.Context, token stri
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection()) verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(ctx, bson.M{"token": token}).Decode(&verificationRequest) err := verificationRequestCollection.FindOne(ctx, bson.M{"token": token}).Decode(&verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil
@ -49,7 +49,7 @@ func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email stri
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection()) verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(ctx, bson.M{"email": email, "identifier": identifier}).Decode(&verificationRequest) err := verificationRequestCollection.FindOne(ctx, bson.M{"email": email, "identifier": identifier}).Decode(&verificationRequest)
if err != nil { if err != nil {
return verificationRequest, err return nil, err
} }
return verificationRequest, nil return verificationRequest, nil

View File

@ -0,0 +1,34 @@
package provider_template
import (
"context"
"time"
"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()
return authenticators, nil
}
func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
authenticators.UpdatedAt = time.Now().Unix()
return authenticators, nil
}
func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
var authenticators *models.Authenticator
return authenticators, nil
}

View File

@ -2,12 +2,15 @@ package provider_template
import ( import (
"context" "context"
"fmt"
"strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -19,10 +22,19 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if user.Roles == "" { if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Roles = defaultRoles 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.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
return user, nil return user, nil

View File

@ -26,7 +26,7 @@ type Provider interface {
// If ids set to nil / empty all the users will be updated // If ids set to nil / empty all the users will be updated
UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error
// AddVerification to save verification request in database // AddVerificationRequest to save verification request in database
AddVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) (*models.VerificationRequest, error) AddVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) (*models.VerificationRequest, error)
// GetVerificationRequestByToken to get verification request from database using token // GetVerificationRequestByToken to get verification request from database using token
GetVerificationRequestByToken(ctx context.Context, token string) (*models.VerificationRequest, error) GetVerificationRequestByToken(ctx context.Context, token string) (*models.VerificationRequest, error)
@ -53,7 +53,7 @@ type Provider interface {
AddWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) AddWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error)
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
UpdateWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) UpdateWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error)
// ListWebhooks to list webhook // ListWebhook to list webhook
ListWebhook(ctx context.Context, pagination *model.Pagination) (*model.Webhooks, error) ListWebhook(ctx context.Context, pagination *model.Pagination) (*model.Webhooks, error)
// GetWebhookByID to get webhook by id // GetWebhookByID to get webhook by id
GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error)
@ -71,7 +71,7 @@ type Provider interface {
AddEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) AddEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error)
// UpdateEmailTemplate to update EmailTemplate // UpdateEmailTemplate to update EmailTemplate
UpdateEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) UpdateEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error)
// ListEmailTemplates to list EmailTemplate // ListEmailTemplate to list EmailTemplate
ListEmailTemplate(ctx context.Context, pagination *model.Pagination) (*model.EmailTemplates, error) ListEmailTemplate(ctx context.Context, pagination *model.Pagination) (*model.EmailTemplates, error)
// GetEmailTemplateByID to get EmailTemplate by id // GetEmailTemplateByID to get EmailTemplate by id
GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error)
@ -88,4 +88,15 @@ type Provider interface {
GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error)
// DeleteOTP to delete otp // DeleteOTP to delete otp
DeleteOTP(ctx context.Context, otp *models.OTP) error DeleteOTP(ctx context.Context, otp *models.OTP) error
// AddAuthenticator adds a new authenticator document to the database.
// If the authenticator doesn't have an ID, a new one is generated.
// The created document is returned, or an error if the operation fails.
AddAuthenticator(ctx context.Context, totp *models.Authenticator) (*models.Authenticator, error)
// UpdateAuthenticator updates an existing authenticator document in the database.
// The updated document is returned, or an error if the operation fails.
UpdateAuthenticator(ctx context.Context, totp *models.Authenticator) (*models.Authenticator, error)
// GetAuthenticatorDetailsByUserId retrieves details of an authenticator document based on user ID and authenticator type.
// If found, the authenticator document is returned, or an error if not found or an error occurs during the retrieval.
GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error)
} }

View File

@ -0,0 +1,52 @@
package sql
import (
"context"
"time"
"github.com/google/uuid"
"gorm.io/gorm/clause"
"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()
res := p.db.Clauses(
clause.OnConflict{
UpdateAll: true,
Columns: []clause.Column{{Name: "id"}},
}).Create(&authenticators)
if res.Error != nil {
return nil, res.Error
}
return authenticators, nil
}
func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
authenticators.UpdatedAt = time.Now().Unix()
result := p.db.Save(&authenticators)
if result.Error != nil {
return authenticators, result.Error
}
return authenticators, nil
}
func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
var authenticators models.Authenticator
result := p.db.Where("user_id = ?", userId).Where("method = ?", authenticatorType).First(&authenticators)
if result.Error != nil {
return nil, result.Error
}
return &authenticators, nil
}

View File

@ -7,33 +7,56 @@ import (
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm/clause"
) )
// UpsertOTP to add or update otp // UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, error) { func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
if otp.ID == "" { if otpParam.ID == "" {
otp.ID = uuid.New().String() otpParam.ID = uuid.New().String()
} }
// check if email or phone number is present // check if email or phone number is present
if otp.Email == "" && otp.PhoneNumber == "" { if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required") return nil, errors.New("email or phone_number is required")
} }
uniqueField := models.FieldNameEmail uniqueField := models.FieldNameEmail
if otp.Email == "" && otp.PhoneNumber != "" { if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber uniqueField = models.FieldNamePhoneNumber
} }
otp.Key = otp.ID var otp *models.OTP
otp.CreatedAt = time.Now().Unix() if uniqueField == models.FieldNameEmail {
otp.UpdatedAt = time.Now().Unix() otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
res := p.db.Clauses(clause.OnConflict{ } else {
Columns: []clause.Column{{Name: uniqueField}}, otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
DoUpdates: clause.AssignmentColumns([]string{"otp", "expires_at", "updated_at"}), }
}).Create(&otp) shouldCreate := false
if res.Error != nil { if otp == nil {
return nil, res.Error id := uuid.NewString()
otp = &models.OTP{
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()
if shouldCreate {
result := p.db.Create(&otp)
if result.Error != nil {
return nil, result.Error
}
} else {
result := p.db.Save(&otp)
if result.Error != nil {
return nil, result.Error
}
} }
return otp, nil return otp, nil
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
libsql "github.com/ekristen/gorm-libsql"
"github.com/glebarez/sqlite" "github.com/glebarez/sqlite"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
@ -60,6 +61,8 @@ func NewProvider() (*provider, error) {
sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig) sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig)
case constants.DbTypeSqlite: case constants.DbTypeSqlite:
sqlDB, err = gorm.Open(sqlite.Open(dbURL+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)"), ormConfig) sqlDB, err = gorm.Open(sqlite.Open(dbURL+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)"), ormConfig)
case constants.DbTypeLibSQL:
sqlDB, err = gorm.Open(libsql.Open(dbURL), ormConfig)
case constants.DbTypeMysql, constants.DbTypeMariaDB, constants.DbTypePlanetScaleDB: case constants.DbTypeMysql, constants.DbTypeMariaDB, constants.DbTypePlanetScaleDB:
sqlDB, err = gorm.Open(mysql.Open(dbURL), ormConfig) sqlDB, err = gorm.Open(mysql.Open(dbURL), ormConfig)
case constants.DbTypeSqlserver: case constants.DbTypeSqlserver:
@ -77,7 +80,7 @@ func NewProvider() (*provider, error) {
logrus.Debug("Failed to drop phone number constraint:", err) logrus.Debug("Failed to drop phone number constraint:", err)
} }
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, &models.WebhookLog{}, &models.EmailTemplate{}, &models.OTP{}) err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, &models.WebhookLog{}, &models.EmailTemplate{}, &models.OTP{}, &models.Authenticator{})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -13,7 +13,6 @@ import (
"github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/refs"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause"
) )
// AddUser to save user information in database // AddUser to save user information in database
@ -25,25 +24,25 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
if user.Roles == "" { if user.Roles == "" {
defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil { if err != nil {
return user, err return nil, err
} }
user.Roles = defaultRoles user.Roles = defaultRoles
} }
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" { if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil { 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") 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.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
user.Key = user.ID user.Key = user.ID
result := p.db.Clauses( result := p.db.Create(&user)
clause.OnConflict{
UpdateAll: true,
Columns: []clause.Column{{Name: "email"}},
}).Create(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return user, result.Error
@ -113,7 +112,7 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
var user *models.User var user *models.User
result := p.db.Where("email = ?", email).First(&user) result := p.db.Where("email = ?", email).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return nil, result.Error
} }
return user, nil return user, nil
} }
@ -123,7 +122,7 @@ func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, er
var user *models.User var user *models.User
result := p.db.Where("id = ?", id).First(&user) result := p.db.Where("id = ?", id).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return nil, result.Error
} }
return user, nil return user, nil
} }

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"os"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -72,7 +73,6 @@ func getEmailTemplate(event string, data map[string]interface{}) (*model.EmailTe
return nil, err return nil, err
} }
subjectString := buf.String() subjectString := buf.String()
return &model.EmailTemplate{ return &model.EmailTemplate{
Template: templateString, Template: templateString,
Subject: subjectString, Subject: subjectString,
@ -92,10 +92,16 @@ func SendEmail(to []string, event string, data map[string]interface{}) error {
tmp, err := getEmailTemplate(event, data) tmp, err := getEmailTemplate(event, data)
if err != nil { if err != nil {
log.Errorf("Failed to get event template: ", err) log.Error("Failed to get event template: ", err)
return err return err
} }
mailgunAPIKey := os.Getenv("MAILGUN_API_KEY")
if len(mailgunAPIKey) > 0 {
return SendMailgun(to, event, data)
}
m := gomail.NewMessage() m := gomail.NewMessage()
senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail) senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)
if err != nil { if err != nil {

View File

@ -53,13 +53,13 @@ const (
<table width="100%%" cellspacing="0" cellpadding="0"> <table width="100%%" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr> <tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td> <td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.organization.logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr> </tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;"> <tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;"> <td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p> <p>Hey there 👋</p>
<p>We have received request to verify email for <b>{{.org_name}}</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/> <p>We have received request to verify email for <b>{{.organization.name}}</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/>
<a <a
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Confirm Email</a> clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Confirm Email</a>
</td> </td>

83
server/email/mailgun.go Normal file
View File

@ -0,0 +1,83 @@
package email
import (
"context"
"fmt"
"os"
"time"
mailgun "github.com/mailgun/mailgun-go/v4"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
)
const apiURL = "https://api.mailgun.net/v3/%s/messages"
func MailgunRest(to string, data map[string]interface{}, subject string, template string) error {
var mailgunAPIKey = os.Getenv("MAILGUN_API_KEY")
var mailgunDomain = os.Getenv("MAILGUN_DOMAIN")
sender := mailgunDomain + "<noreply@" + mailgunDomain + ">"
log.Printf("%r", data)
mg := mailgun.NewMailgun(mailgunDomain, mailgunAPIKey)
m := mg.NewMessage(sender, subject, "", to)
m.SetTemplate(template)
m.AddTemplateVariable("verification_url", data["verification_url"])
userMap, ok := data["user"].(map[string]interface{})
if !ok {
log.Println("Error: Unable to retrieve user information from the data map.")
}
userName, ok := userMap["GivenName"].(string)
if ok {
m.AddTemplateVariable("username", userName)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
resp, id, err := mg.Send(ctx, m)
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %s Resp: %s\n", id, resp)
return err
}
// SendMailgun function to send
func SendMailgun(to []string, event string, data map[string]interface{}) error {
template := "authorizer_email_confirmation"
switch event {
case constants.VerificationTypeBasicAuthSignup:
template = "authorizer_email_confirmation"
case constants.VerificationTypeForgotPassword:
template = "authorizer_password_reset"
case constants.VerificationTypeInviteMember:
template = "author_invited"
case constants.VerificationTypeMagicLinkLogin:
template = "magic_link_login"
case constants.VerificationTypeOTP:
template = "one_time_password"
case constants.VerificationTypeUpdateEmail:
template = "email_update"
}
subject := "Подтверждение почты"
// TODO: language selection logic here
err := MailgunRest(to[0], data, subject, template)
// Log the response
if err != nil {
log.Printf("Error sending email: %v", err)
} else {
log.Println("Email sent successfully")
}
return err
}

85
server/env/env.go vendored
View File

@ -79,6 +79,10 @@ func InitAllEnv() error {
osMicrosoftClientID := os.Getenv(constants.EnvKeyMicrosoftClientID) osMicrosoftClientID := os.Getenv(constants.EnvKeyMicrosoftClientID)
osMicrosoftClientSecret := os.Getenv(constants.EnvKeyMicrosoftClientSecret) osMicrosoftClientSecret := os.Getenv(constants.EnvKeyMicrosoftClientSecret)
osMicrosoftActiveDirectoryTenantID := os.Getenv(constants.EnvKeyMicrosoftActiveDirectoryTenantID) osMicrosoftActiveDirectoryTenantID := os.Getenv(constants.EnvKeyMicrosoftActiveDirectoryTenantID)
osTwitchClientID := os.Getenv(constants.EnvKeyTwitchClientID)
osTwitchClientSecret := os.Getenv(constants.EnvKeyTwitchClientSecret)
osRobloxClientID := os.Getenv(constants.EnvKeyTwitchClientID)
osRobloxClientSecret := os.Getenv(constants.EnvKeyTwitchClientSecret)
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL) osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName) osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo) osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
@ -104,8 +108,12 @@ func InitAllEnv() error {
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword) osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
osEnforceMultiFactorAuthentication := os.Getenv(constants.EnvKeyEnforceMultiFactorAuthentication) osEnforceMultiFactorAuthentication := os.Getenv(constants.EnvKeyEnforceMultiFactorAuthentication)
osDisableMultiFactorAuthentication := os.Getenv(constants.EnvKeyDisableMultiFactorAuthentication) osDisableMultiFactorAuthentication := os.Getenv(constants.EnvKeyDisableMultiFactorAuthentication)
osDisableTOTPLogin := os.Getenv(constants.EnvKeyDisableTOTPLogin)
osDisableMailOTPLogin := os.Getenv(constants.EnvKeyDisableMailOTPLogin)
// phone verification var // phone verification var
osDisablePhoneVerification := os.Getenv(constants.EnvKeyDisablePhoneVerification) osDisablePhoneVerification := os.Getenv(constants.EnvKeyDisablePhoneVerification)
osDisablePlayground := os.Getenv(constants.EnvKeyDisablePlayGround)
// twilio vars // twilio vars
osTwilioApiKey := os.Getenv(constants.EnvKeyTwilioAPIKey) osTwilioApiKey := os.Getenv(constants.EnvKeyTwilioAPIKey)
osTwilioApiSecret := os.Getenv(constants.EnvKeyTwilioAPISecret) osTwilioApiSecret := os.Getenv(constants.EnvKeyTwilioAPISecret)
@ -497,6 +505,34 @@ func InitAllEnv() error {
envData[constants.EnvKeyMicrosoftActiveDirectoryTenantID] = osMicrosoftActiveDirectoryTenantID envData[constants.EnvKeyMicrosoftActiveDirectoryTenantID] = osMicrosoftActiveDirectoryTenantID
} }
if val, ok := envData[constants.EnvKeyTwitchClientID]; !ok || val == "" {
envData[constants.EnvKeyTwitchClientID] = osTwitchClientID
}
if osTwitchClientID != "" && envData[constants.EnvKeyTwitchClientID] != osTwitchClientID {
envData[constants.EnvKeyTwitchClientID] = osTwitchClientID
}
if val, ok := envData[constants.EnvKeyTwitchClientSecret]; !ok || val == "" {
envData[constants.EnvKeyTwitchClientSecret] = osTwitchClientSecret
}
if osTwitchClientSecret != "" && envData[constants.EnvKeyTwitchClientSecret] != osTwitchClientSecret {
envData[constants.EnvKeyTwitchClientSecret] = osTwitchClientSecret
}
if val, ok := envData[constants.EnvKeyRobloxClientID]; !ok || val == "" {
envData[constants.EnvKeyRobloxClientID] = osRobloxClientID
}
if osRobloxClientID != "" && envData[constants.EnvKeyRobloxClientID] != osRobloxClientID {
envData[constants.EnvKeyRobloxClientID] = osRobloxClientID
}
if val, ok := envData[constants.EnvKeyRobloxClientSecret]; !ok || val == "" {
envData[constants.EnvKeyRobloxClientSecret] = osRobloxClientSecret
}
if osRobloxClientSecret != "" && envData[constants.EnvKeyRobloxClientSecret] != osRobloxClientSecret {
envData[constants.EnvKeyRobloxClientSecret] = osRobloxClientSecret
}
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" { if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/") envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
} }
@ -687,20 +723,13 @@ func InitAllEnv() error {
envData[constants.EnvKeyDisableEmailVerification] = true envData[constants.EnvKeyDisableEmailVerification] = true
envData[constants.EnvKeyDisableMagicLinkLogin] = true envData[constants.EnvKeyDisableMagicLinkLogin] = true
envData[constants.EnvKeyIsEmailServiceEnabled] = false envData[constants.EnvKeyIsEmailServiceEnabled] = false
envData[constants.EnvKeyDisableMailOTPLogin] = true
} }
if envData[constants.EnvKeySmtpHost] != "" && envData[constants.EnvKeySmtpUsername] != "" && envData[constants.EnvKeySmtpPassword] != "" && envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" { if envData[constants.EnvKeySmtpHost] != "" && envData[constants.EnvKeySmtpUsername] != "" && envData[constants.EnvKeySmtpPassword] != "" && envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" {
envData[constants.EnvKeyIsEmailServiceEnabled] = true envData[constants.EnvKeyIsEmailServiceEnabled] = true
} }
if envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) && !envData[constants.EnvKeyIsSMSServiceEnabled].(bool) {
return errors.New("to enable multi factor authentication, please enable email service")
}
if !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) {
envData[constants.EnvKeyDisableMultiFactorAuthentication] = true
}
if envData[constants.EnvKeyDisableEmailVerification].(bool) { if envData[constants.EnvKeyDisableEmailVerification].(bool) {
envData[constants.EnvKeyDisableMagicLinkLogin] = true envData[constants.EnvKeyDisableMagicLinkLogin] = true
} }
@ -825,6 +854,46 @@ func InitAllEnv() error {
envData[constants.EnvKeyIsSMSServiceEnabled] = true envData[constants.EnvKeyIsSMSServiceEnabled] = true
} }
if _, ok := envData[constants.EnvKeyDisablePlayGround]; !ok {
envData[constants.EnvKeyDisablePlayGround] = osDisablePlayground == "true"
}
if osDisablePlayground != "" {
boolValue, err := strconv.ParseBool(osDisablePlayground)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisablePlayGround].(bool) {
envData[constants.EnvKeyDisablePlayGround] = boolValue
}
}
// TODO: remove after beta launch
envData[constants.EnvKeyDisableTOTPLogin] = true
if _, ok := envData[constants.EnvKeyDisableTOTPLogin]; !ok {
envData[constants.EnvKeyDisableTOTPLogin] = osDisableTOTPLogin == "true"
}
if osDisableTOTPLogin != "" {
boolValue, err := strconv.ParseBool(osDisableTOTPLogin)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableTOTPLogin].(bool) {
envData[constants.EnvKeyDisableTOTPLogin] = boolValue
}
}
if _, ok := envData[constants.EnvKeyDisableMailOTPLogin]; !ok {
envData[constants.EnvKeyDisableMailOTPLogin] = osDisableMailOTPLogin == "true"
}
if osDisableMailOTPLogin != "" {
boolValue, err := strconv.ParseBool(osDisableMailOTPLogin)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableMailOTPLogin].(bool) {
envData[constants.EnvKeyDisableMailOTPLogin] = boolValue
}
}
err = memorystore.Provider.UpdateEnvStore(envData) err = memorystore.Provider.UpdateEnvStore(envData)
if err != nil { if err != nil {
log.Debug("Error while updating env store: ", err) log.Debug("Error while updating env store: ", err)

View File

@ -196,7 +196,7 @@ func PersistEnv() error {
envValue := strings.TrimSpace(os.Getenv(key)) envValue := strings.TrimSpace(os.Getenv(key))
if envValue != "" { if envValue != "" {
switch key { switch key {
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyIsSMSServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification: case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyIsSMSServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification, constants.EnvKeyDisablePlayGround, constants.EnvKeyDisableTOTPLogin, constants.EnvKeyDisableMailOTPLogin:
if envValueBool, err := strconv.ParseBool(envValue); err == nil { if envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool { if value.(bool) != envValueBool {
storeData[key] = envValueBool storeData[key] = envValueBool
@ -218,15 +218,20 @@ func PersistEnv() error {
if storeData[constants.EnvKeySmtpHost] == "" || storeData[constants.EnvKeySmtpUsername] == "" || storeData[constants.EnvKeySmtpPassword] == "" || storeData[constants.EnvKeySenderEmail] == "" && storeData[constants.EnvKeySmtpPort] == "" { if storeData[constants.EnvKeySmtpHost] == "" || storeData[constants.EnvKeySmtpUsername] == "" || storeData[constants.EnvKeySmtpPassword] == "" || storeData[constants.EnvKeySenderEmail] == "" && storeData[constants.EnvKeySmtpPort] == "" {
storeData[constants.EnvKeyIsEmailServiceEnabled] = false storeData[constants.EnvKeyIsEmailServiceEnabled] = false
if !storeData[constants.EnvKeyDisableEmailVerification].(bool) { if val, ok := storeData[constants.EnvKeyDisableEmailVerification]; ok && val != nil && !val.(bool) {
storeData[constants.EnvKeyDisableEmailVerification] = true storeData[constants.EnvKeyDisableEmailVerification] = true
hasChanged = true hasChanged = true
} }
if !storeData[constants.EnvKeyDisableMagicLinkLogin].(bool) { if val, ok := storeData[constants.EnvKeyDisableMagicLinkLogin]; ok && val != nil && !val.(bool) {
storeData[constants.EnvKeyDisableMagicLinkLogin] = true storeData[constants.EnvKeyDisableMagicLinkLogin] = true
hasChanged = true hasChanged = true
} }
if val, ok := storeData[constants.EnvKeyDisableMailOTPLogin]; ok && val != nil && !val.(bool) {
storeData[constants.EnvKeyDisableMailOTPLogin] = true
hasChanged = true
}
} }
err = memorystore.Provider.UpdateEnvStore(storeData) err = memorystore.Provider.UpdateEnvStore(storeData)

View File

@ -1,42 +1,128 @@
module github.com/authorizerdev/authorizer/server module github.com/authorizerdev/authorizer/server
go 1.16 go 1.21
toolchain go1.21.4
require ( require (
github.com/99designs/gqlgen v0.17.20 github.com/99designs/gqlgen v0.17.45
github.com/arangodb/go-driver v1.2.1 github.com/arangodb/go-driver v1.6.0
github.com/aws/aws-sdk-go v1.44.298 github.com/aws/aws-sdk-go v1.47.4
github.com/coreos/go-oidc/v3 v3.1.0 github.com/coreos/go-oidc/v3 v3.6.0
github.com/couchbase/gocb/v2 v2.6.0 github.com/couchbase/gocb/v2 v2.6.4
github.com/gin-gonic/gin v1.8.1 github.com/ekristen/gorm-libsql v0.0.0-20231101204708-6e113112bcc2
github.com/glebarez/sqlite v1.5.0 github.com/gin-gonic/gin v1.9.1
github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/glebarez/sqlite v1.10.0
github.com/goccy/go-json v0.9.11 // indirect github.com/gocql/gocql v1.6.0
github.com/gocql/gocql v1.2.0 github.com/gokyle/twofactor v1.0.1
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.6.0
github.com/google/go-cmp v0.5.6 // indirect github.com/guregu/dynamo v1.20.2
github.com/google/uuid v1.3.0 github.com/joho/godotenv v1.5.1
github.com/guregu/dynamo v1.20.0 github.com/mailgun/mailgun-go/v4 v4.12.0
github.com/joho/godotenv v1.3.0 github.com/pquerna/otp v1.4.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/redis/go-redis/v9 v9.2.1
github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/robertkrimen/otto v0.2.1
github.com/redis/go-redis/v9 v9.0.3 github.com/sirupsen/logrus v1.9.3
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f github.com/stretchr/testify v1.9.0
github.com/sirupsen/logrus v1.8.1 github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d
github.com/stretchr/testify v1.8.0 github.com/twilio/twilio-go v1.14.1
github.com/twilio/twilio-go v1.7.2 github.com/vektah/gqlparser/v2 v2.5.11
github.com/vektah/gqlparser/v2 v2.5.1 go.mongodb.org/mongo-driver v1.12.1
go.mongodb.org/mongo-driver v1.8.1 golang.org/x/crypto v0.21.0
golang.org/x/crypto v0.4.0 golang.org/x/oauth2 v0.13.0
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 google.golang.org/appengine v1.6.8
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1 gopkg.in/mail.v2 v2.3.1
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
gorm.io/driver/mysql v1.4.3 gorm.io/driver/mysql v1.5.2
gorm.io/driver/postgres v1.4.7 gorm.io/driver/postgres v1.5.4
gorm.io/driver/sqlserver v1.4.1 gorm.io/driver/sqlserver v1.5.2
gorm.io/gorm v1.24.2 gorm.io/gorm v1.25.5
)
require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/couchbase/gocbcore/v10 v10.2.8 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/libsql/libsql-client-go v0.0.0-20231026052543-fce76c0f39a7 // indirect
github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 // indirect
github.com/maruel/rs v1.1.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microsoft/go-mssqldb v1.6.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sosodev/duration v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/urfave/cli/v2 v2.27.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.19.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
nhooyr.io/websocket v1.8.7 // indirect
rsc.io/qr v0.2.0 // indirect
) )

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,15 +6,15 @@ type AddEmailTemplateRequest struct {
EventName string `json:"event_name"` EventName string `json:"event_name"`
Subject string `json:"subject"` Subject string `json:"subject"`
Template string `json:"template"` Template string `json:"template"`
Design *string `json:"design"` Design *string `json:"design,omitempty"`
} }
type AddWebhookRequest struct { type AddWebhookRequest struct {
EventName string `json:"event_name"` EventName string `json:"event_name"`
EventDescription *string `json:"event_description"` EventDescription *string `json:"event_description,omitempty"`
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Headers map[string]interface{} `json:"headers"` Headers map[string]interface{} `json:"headers,omitempty"`
} }
type AdminLoginInput struct { type AdminLoginInput struct {
@ -26,14 +26,18 @@ type AdminSignupInput struct {
} }
type AuthResponse struct { type AuthResponse struct {
Message string `json:"message"` Message string `json:"message"`
ShouldShowEmailOtpScreen *bool `json:"should_show_email_otp_screen"` ShouldShowEmailOtpScreen *bool `json:"should_show_email_otp_screen,omitempty"`
ShouldShowMobileOtpScreen *bool `json:"should_show_mobile_otp_screen"` ShouldShowMobileOtpScreen *bool `json:"should_show_mobile_otp_screen,omitempty"`
AccessToken *string `json:"access_token"` ShouldShowTotpScreen *bool `json:"should_show_totp_screen,omitempty"`
IDToken *string `json:"id_token"` AccessToken *string `json:"access_token,omitempty"`
RefreshToken *string `json:"refresh_token"` IDToken *string `json:"id_token,omitempty"`
ExpiresIn *int64 `json:"expires_in"` RefreshToken *string `json:"refresh_token,omitempty"`
User *User `json:"user"` ExpiresIn *int64 `json:"expires_in,omitempty"`
User *User `json:"user,omitempty"`
AuthenticatorScannerImage *string `json:"authenticator_scanner_image,omitempty"`
AuthenticatorSecret *string `json:"authenticator_secret,omitempty"`
AuthenticatorRecoveryCodes []*string `json:"authenticator_recovery_codes,omitempty"`
} }
type DeleteEmailTemplateRequest struct { type DeleteEmailTemplateRequest struct {
@ -50,8 +54,8 @@ type EmailTemplate struct {
Template string `json:"template"` Template string `json:"template"`
Design string `json:"design"` Design string `json:"design"`
Subject string `json:"subject"` Subject string `json:"subject"`
CreatedAt *int64 `json:"created_at"` CreatedAt *int64 `json:"created_at,omitempty"`
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at,omitempty"`
} }
type EmailTemplates struct { type EmailTemplates struct {
@ -60,35 +64,36 @@ type EmailTemplates struct {
} }
type Env struct { type Env struct {
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"` AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME,omitempty"`
AdminSecret *string `json:"ADMIN_SECRET"` AdminSecret *string `json:"ADMIN_SECRET,omitempty"`
DatabaseName *string `json:"DATABASE_NAME"` DatabaseName *string `json:"DATABASE_NAME,omitempty"`
DatabaseURL *string `json:"DATABASE_URL"` DatabaseURL *string `json:"DATABASE_URL,omitempty"`
DatabaseType *string `json:"DATABASE_TYPE"` DatabaseType *string `json:"DATABASE_TYPE,omitempty"`
DatabaseUsername *string `json:"DATABASE_USERNAME"` DatabaseUsername *string `json:"DATABASE_USERNAME,omitempty"`
DatabasePassword *string `json:"DATABASE_PASSWORD"` DatabasePassword *string `json:"DATABASE_PASSWORD,omitempty"`
DatabaseHost *string `json:"DATABASE_HOST"` DatabaseHost *string `json:"DATABASE_HOST,omitempty"`
DatabasePort *string `json:"DATABASE_PORT"` DatabasePort *string `json:"DATABASE_PORT,omitempty"`
ClientID string `json:"CLIENT_ID"` ClientID string `json:"CLIENT_ID"`
ClientSecret string `json:"CLIENT_SECRET"` ClientSecret string `json:"CLIENT_SECRET"`
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"` CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT,omitempty"`
SMTPHost *string `json:"SMTP_HOST"` SMTPHost *string `json:"SMTP_HOST,omitempty"`
SMTPPort *string `json:"SMTP_PORT"` SMTPPort *string `json:"SMTP_PORT,omitempty"`
SMTPUsername *string `json:"SMTP_USERNAME"` SMTPUsername *string `json:"SMTP_USERNAME,omitempty"`
SMTPPassword *string `json:"SMTP_PASSWORD"` SMTPPassword *string `json:"SMTP_PASSWORD,omitempty"`
SMTPLocalName *string `json:"SMTP_LOCAL_NAME"` SMTPLocalName *string `json:"SMTP_LOCAL_NAME,omitempty"`
SenderEmail *string `json:"SENDER_EMAIL"` SenderEmail *string `json:"SENDER_EMAIL,omitempty"`
SenderName *string `json:"SENDER_NAME"` SenderName *string `json:"SENDER_NAME,omitempty"`
JwtType *string `json:"JWT_TYPE"` JwtType *string `json:"JWT_TYPE,omitempty"`
JwtSecret *string `json:"JWT_SECRET"` JwtSecret *string `json:"JWT_SECRET,omitempty"`
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"` JwtPrivateKey *string `json:"JWT_PRIVATE_KEY,omitempty"`
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"` JwtPublicKey *string `json:"JWT_PUBLIC_KEY,omitempty"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"` AllowedOrigins []string `json:"ALLOWED_ORIGINS,omitempty"`
AppURL *string `json:"APP_URL"` AppURL *string `json:"APP_URL,omitempty"`
RedisURL *string `json:"REDIS_URL"` RedisURL *string `json:"REDIS_URL,omitempty"`
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"` ResetPasswordURL *string `json:"RESET_PASSWORD_URL,omitempty"`
DisableEmailVerification bool `json:"DISABLE_EMAIL_VERIFICATION"` DisableEmailVerification bool `json:"DISABLE_EMAIL_VERIFICATION"`
DisableBasicAuthentication bool `json:"DISABLE_BASIC_AUTHENTICATION"` DisableBasicAuthentication bool `json:"DISABLE_BASIC_AUTHENTICATION"`
DisableMobileBasicAuthentication bool `json:"DISABLE_MOBILE_BASIC_AUTHENTICATION"`
DisableMagicLinkLogin bool `json:"DISABLE_MAGIC_LINK_LOGIN"` DisableMagicLinkLogin bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"` DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"`
DisableSignUp bool `json:"DISABLE_SIGN_UP"` DisableSignUp bool `json:"DISABLE_SIGN_UP"`
@ -96,31 +101,40 @@ type Env struct {
DisableStrongPassword bool `json:"DISABLE_STRONG_PASSWORD"` DisableStrongPassword bool `json:"DISABLE_STRONG_PASSWORD"`
DisableMultiFactorAuthentication bool `json:"DISABLE_MULTI_FACTOR_AUTHENTICATION"` DisableMultiFactorAuthentication bool `json:"DISABLE_MULTI_FACTOR_AUTHENTICATION"`
EnforceMultiFactorAuthentication bool `json:"ENFORCE_MULTI_FACTOR_AUTHENTICATION"` EnforceMultiFactorAuthentication bool `json:"ENFORCE_MULTI_FACTOR_AUTHENTICATION"`
Roles []string `json:"ROLES"` Roles []string `json:"ROLES,omitempty"`
ProtectedRoles []string `json:"PROTECTED_ROLES"` ProtectedRoles []string `json:"PROTECTED_ROLES,omitempty"`
DefaultRoles []string `json:"DEFAULT_ROLES"` DefaultRoles []string `json:"DEFAULT_ROLES,omitempty"`
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"` JwtRoleClaim *string `json:"JWT_ROLE_CLAIM,omitempty"`
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"` GoogleClientID *string `json:"GOOGLE_CLIENT_ID,omitempty"`
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"` GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET,omitempty"`
GithubClientID *string `json:"GITHUB_CLIENT_ID"` GithubClientID *string `json:"GITHUB_CLIENT_ID,omitempty"`
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"` GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET,omitempty"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"` FacebookClientID *string `json:"FACEBOOK_CLIENT_ID,omitempty"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"` FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET,omitempty"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"` LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID,omitempty"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"` LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET,omitempty"`
AppleClientID *string `json:"APPLE_CLIENT_ID"` AppleClientID *string `json:"APPLE_CLIENT_ID,omitempty"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"` AppleClientSecret *string `json:"APPLE_CLIENT_SECRET,omitempty"`
TwitterClientID *string `json:"TWITTER_CLIENT_ID"` DiscordClientID *string `json:"DISCORD_CLIENT_ID,omitempty"`
TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET"` DiscordClientSecret *string `json:"DISCORD_CLIENT_SECRET,omitempty"`
MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID"` TwitterClientID *string `json:"TWITTER_CLIENT_ID,omitempty"`
MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET"` TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET,omitempty"`
MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID"` MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID,omitempty"`
OrganizationName *string `json:"ORGANIZATION_NAME"` MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET,omitempty"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"` MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID,omitempty"`
TwitchClientID *string `json:"TWITCH_CLIENT_ID,omitempty"`
TwitchClientSecret *string `json:"TWITCH_CLIENT_SECRET,omitempty"`
RobloxClientID *string `json:"ROBLOX_CLIENT_ID,omitempty"`
RobloxClientSecret *string `json:"ROBLOX_CLIENT_SECRET,omitempty"`
OrganizationName *string `json:"ORGANIZATION_NAME,omitempty"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO,omitempty"`
AppCookieSecure bool `json:"APP_COOKIE_SECURE"` AppCookieSecure bool `json:"APP_COOKIE_SECURE"`
AdminCookieSecure bool `json:"ADMIN_COOKIE_SECURE"` AdminCookieSecure bool `json:"ADMIN_COOKIE_SECURE"`
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE"` DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE,omitempty"`
DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE"` DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE,omitempty"`
DisablePlayground bool `json:"DISABLE_PLAYGROUND"`
DisableMailOtpLogin bool `json:"DISABLE_MAIL_OTP_LOGIN"`
DisableTotpLogin bool `json:"DISABLE_TOTP_LOGIN"`
} }
type Error struct { type Error struct {
@ -129,9 +143,15 @@ type Error struct {
} }
type ForgotPasswordInput struct { type ForgotPasswordInput struct {
Email string `json:"email"` Email *string `json:"email,omitempty"`
State *string `json:"state"` PhoneNumber *string `json:"phone_number,omitempty"`
RedirectURI *string `json:"redirect_uri"` State *string `json:"state,omitempty"`
RedirectURI *string `json:"redirect_uri,omitempty"`
}
type ForgotPasswordResponse struct {
Message string `json:"message"`
ShouldShowMobileOtpScreen *bool `json:"should_show_mobile_otp_screen,omitempty"`
} }
type GenerateJWTKeysInput struct { type GenerateJWTKeysInput struct {
@ -139,19 +159,19 @@ type GenerateJWTKeysInput struct {
} }
type GenerateJWTKeysResponse struct { type GenerateJWTKeysResponse struct {
Secret *string `json:"secret"` Secret *string `json:"secret,omitempty"`
PublicKey *string `json:"public_key"` PublicKey *string `json:"public_key,omitempty"`
PrivateKey *string `json:"private_key"` PrivateKey *string `json:"private_key,omitempty"`
} }
type GetUserRequest struct { type GetUserRequest struct {
ID *string `json:"id"` ID *string `json:"id,omitempty"`
Email *string `json:"email"` Email *string `json:"email,omitempty"`
} }
type InviteMemberInput struct { type InviteMemberInput struct {
Emails []string `json:"emails"` Emails []string `json:"emails"`
RedirectURI *string `json:"redirect_uri"` RedirectURI *string `json:"redirect_uri,omitempty"`
} }
type InviteMembersResponse struct { type InviteMembersResponse struct {
@ -160,69 +180,79 @@ type InviteMembersResponse struct {
} }
type ListWebhookLogRequest struct { type ListWebhookLogRequest struct {
Pagination *PaginationInput `json:"pagination"` Pagination *PaginationInput `json:"pagination,omitempty"`
WebhookID *string `json:"webhook_id"` WebhookID *string `json:"webhook_id,omitempty"`
} }
type LoginInput struct { type LoginInput struct {
Email string `json:"email"` Email *string `json:"email,omitempty"`
Password string `json:"password"` PhoneNumber *string `json:"phone_number,omitempty"`
Roles []string `json:"roles"` Password string `json:"password"`
Scope []string `json:"scope"` Roles []string `json:"roles,omitempty"`
State *string `json:"state"` Scope []string `json:"scope,omitempty"`
State *string `json:"state,omitempty"`
} }
type MagicLinkLoginInput struct { type MagicLinkLoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Roles []string `json:"roles"` Roles []string `json:"roles,omitempty"`
Scope []string `json:"scope"` Scope []string `json:"scope,omitempty"`
State *string `json:"state"` State *string `json:"state,omitempty"`
RedirectURI *string `json:"redirect_uri"` RedirectURI *string `json:"redirect_uri,omitempty"`
} }
type Meta struct { type Meta struct {
Version string `json:"version"` Version string `json:"version"`
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"` IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"` IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"` IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"` IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"` IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
IsTwitterLoginEnabled bool `json:"is_twitter_login_enabled"` IsDiscordLoginEnabled bool `json:"is_discord_login_enabled"`
IsMicrosoftLoginEnabled bool `json:"is_microsoft_login_enabled"` IsTwitterLoginEnabled bool `json:"is_twitter_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"` IsMicrosoftLoginEnabled bool `json:"is_microsoft_login_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"` IsTwitchLoginEnabled bool `json:"is_twitch_login_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"` IsRobloxLoginEnabled bool `json:"is_roblox_login_enabled"`
IsSignUpEnabled bool `json:"is_sign_up_enabled"` IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"` IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMultiFactorAuthEnabled bool `json:"is_multi_factor_auth_enabled"` IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
IsSignUpEnabled bool `json:"is_sign_up_enabled"`
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"`
IsMultiFactorAuthEnabled bool `json:"is_multi_factor_auth_enabled"`
IsMobileBasicAuthenticationEnabled bool `json:"is_mobile_basic_authentication_enabled"`
IsPhoneVerificationEnabled bool `json:"is_phone_verification_enabled"`
} }
type MobileLoginInput struct { type MobileLoginInput struct {
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
Password string `json:"password"` Password string `json:"password"`
Roles []string `json:"roles"` Roles []string `json:"roles,omitempty"`
Scope []string `json:"scope"` Scope []string `json:"scope,omitempty"`
State *string `json:"state"` State *string `json:"state,omitempty"`
} }
type MobileSignUpInput struct { type MobileSignUpInput struct {
Email *string `json:"email"` Email *string `json:"email,omitempty"`
GivenName *string `json:"given_name"` GivenName *string `json:"given_name,omitempty"`
FamilyName *string `json:"family_name"` FamilyName *string `json:"family_name,omitempty"`
MiddleName *string `json:"middle_name"` MiddleName *string `json:"middle_name,omitempty"`
Nickname *string `json:"nickname"` Nickname *string `json:"nickname,omitempty"`
Gender *string `json:"gender"` Gender *string `json:"gender,omitempty"`
Birthdate *string `json:"birthdate"` Birthdate *string `json:"birthdate,omitempty"`
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
Picture *string `json:"picture"` Picture *string `json:"picture,omitempty"`
Password string `json:"password"` Password string `json:"password"`
ConfirmPassword string `json:"confirm_password"` ConfirmPassword string `json:"confirm_password"`
Roles []string `json:"roles"` Roles []string `json:"roles,omitempty"`
Scope []string `json:"scope"` Scope []string `json:"scope,omitempty"`
RedirectURI *string `json:"redirect_uri"` RedirectURI *string `json:"redirect_uri,omitempty"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"` IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled,omitempty"`
State *string `json:"state"` State *string `json:"state,omitempty"`
AppData map[string]interface{} `json:"app_data,omitempty"`
}
type Mutation struct {
} }
type OAuthRevokeInput struct { type OAuthRevokeInput struct {
@ -230,7 +260,7 @@ type OAuthRevokeInput struct {
} }
type PaginatedInput struct { type PaginatedInput struct {
Pagination *PaginationInput `json:"pagination"` Pagination *PaginationInput `json:"pagination,omitempty"`
} }
type Pagination struct { type Pagination struct {
@ -241,26 +271,31 @@ type Pagination struct {
} }
type PaginationInput struct { type PaginationInput struct {
Limit *int64 `json:"limit"` Limit *int64 `json:"limit,omitempty"`
Page *int64 `json:"page"` Page *int64 `json:"page,omitempty"`
}
type Query struct {
} }
type ResendOTPRequest struct { type ResendOTPRequest struct {
Email *string `json:"email"` Email *string `json:"email,omitempty"`
PhoneNumber *string `json:"phone_number"` PhoneNumber *string `json:"phone_number,omitempty"`
State *string `json:"state"` State *string `json:"state,omitempty"`
} }
type ResendVerifyEmailInput struct { type ResendVerifyEmailInput struct {
Email string `json:"email"` Email string `json:"email"`
Identifier string `json:"identifier"` Identifier string `json:"identifier"`
State *string `json:"state"` State *string `json:"state,omitempty"`
} }
type ResetPasswordInput struct { type ResetPasswordInput struct {
Token string `json:"token"` Token *string `json:"token,omitempty"`
Password string `json:"password"` Otp *string `json:"otp,omitempty"`
ConfirmPassword string `json:"confirm_password"` PhoneNumber *string `json:"phone_number,omitempty"`
Password string `json:"password"`
ConfirmPassword string `json:"confirm_password"`
} }
type Response struct { type Response struct {
@ -273,43 +308,44 @@ type SMSVerificationRequests struct {
CodeExpiresAt int64 `json:"code_expires_at"` CodeExpiresAt int64 `json:"code_expires_at"`
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at,omitempty"`
} }
type SessionQueryInput struct { type SessionQueryInput struct {
Roles []string `json:"roles"` Roles []string `json:"roles,omitempty"`
Scope []string `json:"scope"` Scope []string `json:"scope,omitempty"`
} }
type SignUpInput struct { type SignUpInput struct {
Email string `json:"email"` Email *string `json:"email,omitempty"`
GivenName *string `json:"given_name"` GivenName *string `json:"given_name,omitempty"`
FamilyName *string `json:"family_name"` FamilyName *string `json:"family_name,omitempty"`
MiddleName *string `json:"middle_name"` MiddleName *string `json:"middle_name,omitempty"`
Nickname *string `json:"nickname"` Nickname *string `json:"nickname,omitempty"`
Gender *string `json:"gender"` Gender *string `json:"gender,omitempty"`
Birthdate *string `json:"birthdate"` Birthdate *string `json:"birthdate,omitempty"`
PhoneNumber *string `json:"phone_number"` PhoneNumber *string `json:"phone_number,omitempty"`
Picture *string `json:"picture"` Picture *string `json:"picture,omitempty"`
Password string `json:"password"` Password string `json:"password"`
ConfirmPassword string `json:"confirm_password"` ConfirmPassword string `json:"confirm_password"`
Roles []string `json:"roles"` Roles []string `json:"roles,omitempty"`
Scope []string `json:"scope"` Scope []string `json:"scope,omitempty"`
RedirectURI *string `json:"redirect_uri"` RedirectURI *string `json:"redirect_uri,omitempty"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"` IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled,omitempty"`
State *string `json:"state"` State *string `json:"state,omitempty"`
AppData map[string]interface{} `json:"app_data,omitempty"`
} }
type TestEndpointRequest struct { type TestEndpointRequest struct {
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
EventName string `json:"event_name"` EventName string `json:"event_name"`
EventDescription *string `json:"event_description"` EventDescription *string `json:"event_description,omitempty"`
Headers map[string]interface{} `json:"headers"` Headers map[string]interface{} `json:"headers,omitempty"`
} }
type TestEndpointResponse struct { type TestEndpointResponse struct {
HTTPStatus *int64 `json:"http_status"` HTTPStatus *int64 `json:"http_status,omitempty"`
Response *string `json:"response"` Response *string `json:"response,omitempty"`
} }
type UpdateAccessInput struct { type UpdateAccessInput struct {
@ -318,128 +354,142 @@ type UpdateAccessInput struct {
type UpdateEmailTemplateRequest struct { type UpdateEmailTemplateRequest struct {
ID string `json:"id"` ID string `json:"id"`
EventName *string `json:"event_name"` EventName *string `json:"event_name,omitempty"`
Template *string `json:"template"` Template *string `json:"template,omitempty"`
Subject *string `json:"subject"` Subject *string `json:"subject,omitempty"`
Design *string `json:"design"` Design *string `json:"design,omitempty"`
} }
type UpdateEnvInput struct { type UpdateEnvInput struct {
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"` AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME,omitempty"`
AdminSecret *string `json:"ADMIN_SECRET"` AdminSecret *string `json:"ADMIN_SECRET,omitempty"`
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"` CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT,omitempty"`
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"` OldAdminSecret *string `json:"OLD_ADMIN_SECRET,omitempty"`
SMTPHost *string `json:"SMTP_HOST"` SMTPHost *string `json:"SMTP_HOST,omitempty"`
SMTPPort *string `json:"SMTP_PORT"` SMTPPort *string `json:"SMTP_PORT,omitempty"`
SMTPUsername *string `json:"SMTP_USERNAME"` SMTPUsername *string `json:"SMTP_USERNAME,omitempty"`
SMTPPassword *string `json:"SMTP_PASSWORD"` SMTPPassword *string `json:"SMTP_PASSWORD,omitempty"`
SMTPLocalName *string `json:"SMTP_LOCAL_NAME"` SMTPLocalName *string `json:"SMTP_LOCAL_NAME,omitempty"`
SenderEmail *string `json:"SENDER_EMAIL"` SenderEmail *string `json:"SENDER_EMAIL,omitempty"`
SenderName *string `json:"SENDER_NAME"` SenderName *string `json:"SENDER_NAME,omitempty"`
JwtType *string `json:"JWT_TYPE"` JwtType *string `json:"JWT_TYPE,omitempty"`
JwtSecret *string `json:"JWT_SECRET"` JwtSecret *string `json:"JWT_SECRET,omitempty"`
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"` JwtPrivateKey *string `json:"JWT_PRIVATE_KEY,omitempty"`
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"` JwtPublicKey *string `json:"JWT_PUBLIC_KEY,omitempty"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"` AllowedOrigins []string `json:"ALLOWED_ORIGINS,omitempty"`
AppURL *string `json:"APP_URL"` AppURL *string `json:"APP_URL,omitempty"`
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"` ResetPasswordURL *string `json:"RESET_PASSWORD_URL,omitempty"`
AppCookieSecure *bool `json:"APP_COOKIE_SECURE"` AppCookieSecure *bool `json:"APP_COOKIE_SECURE,omitempty"`
AdminCookieSecure *bool `json:"ADMIN_COOKIE_SECURE"` AdminCookieSecure *bool `json:"ADMIN_COOKIE_SECURE,omitempty"`
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"` DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION,omitempty"`
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"` DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION,omitempty"`
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"` DisableMobileBasicAuthentication *bool `json:"DISABLE_MOBILE_BASIC_AUTHENTICATION,omitempty"`
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"` DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN,omitempty"`
DisableSignUp *bool `json:"DISABLE_SIGN_UP"` DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE,omitempty"`
DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV"` DisableSignUp *bool `json:"DISABLE_SIGN_UP,omitempty"`
DisableStrongPassword *bool `json:"DISABLE_STRONG_PASSWORD"` DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV,omitempty"`
DisableMultiFactorAuthentication *bool `json:"DISABLE_MULTI_FACTOR_AUTHENTICATION"` DisableStrongPassword *bool `json:"DISABLE_STRONG_PASSWORD,omitempty"`
EnforceMultiFactorAuthentication *bool `json:"ENFORCE_MULTI_FACTOR_AUTHENTICATION"` DisableMultiFactorAuthentication *bool `json:"DISABLE_MULTI_FACTOR_AUTHENTICATION,omitempty"`
Roles []string `json:"ROLES"` EnforceMultiFactorAuthentication *bool `json:"ENFORCE_MULTI_FACTOR_AUTHENTICATION,omitempty"`
ProtectedRoles []string `json:"PROTECTED_ROLES"` Roles []string `json:"ROLES,omitempty"`
DefaultRoles []string `json:"DEFAULT_ROLES"` ProtectedRoles []string `json:"PROTECTED_ROLES,omitempty"`
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"` DefaultRoles []string `json:"DEFAULT_ROLES,omitempty"`
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"` JwtRoleClaim *string `json:"JWT_ROLE_CLAIM,omitempty"`
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"` GoogleClientID *string `json:"GOOGLE_CLIENT_ID,omitempty"`
GithubClientID *string `json:"GITHUB_CLIENT_ID"` GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET,omitempty"`
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"` GithubClientID *string `json:"GITHUB_CLIENT_ID,omitempty"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"` GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET,omitempty"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"` FacebookClientID *string `json:"FACEBOOK_CLIENT_ID,omitempty"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"` FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET,omitempty"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"` LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID,omitempty"`
AppleClientID *string `json:"APPLE_CLIENT_ID"` LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET,omitempty"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"` AppleClientID *string `json:"APPLE_CLIENT_ID,omitempty"`
TwitterClientID *string `json:"TWITTER_CLIENT_ID"` AppleClientSecret *string `json:"APPLE_CLIENT_SECRET,omitempty"`
TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET"` DiscordClientID *string `json:"DISCORD_CLIENT_ID,omitempty"`
MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID"` DiscordClientSecret *string `json:"DISCORD_CLIENT_SECRET,omitempty"`
MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET"` TwitterClientID *string `json:"TWITTER_CLIENT_ID,omitempty"`
MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID"` TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET,omitempty"`
OrganizationName *string `json:"ORGANIZATION_NAME"` MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID,omitempty"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"` MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET,omitempty"`
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE"` MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID,omitempty"`
DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE"` TwitchClientID *string `json:"TWITCH_CLIENT_ID,omitempty"`
TwitchClientSecret *string `json:"TWITCH_CLIENT_SECRET,omitempty"`
RobloxClientID *string `json:"ROBLOX_CLIENT_ID,omitempty"`
RobloxClientSecret *string `json:"ROBLOX_CLIENT_SECRET,omitempty"`
OrganizationName *string `json:"ORGANIZATION_NAME,omitempty"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO,omitempty"`
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE,omitempty"`
DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE,omitempty"`
DisablePlayground *bool `json:"DISABLE_PLAYGROUND,omitempty"`
DisableMailOtpLogin *bool `json:"DISABLE_MAIL_OTP_LOGIN,omitempty"`
DisableTotpLogin *bool `json:"DISABLE_TOTP_LOGIN,omitempty"`
} }
type UpdateProfileInput struct { type UpdateProfileInput struct {
OldPassword *string `json:"old_password"` OldPassword *string `json:"old_password,omitempty"`
NewPassword *string `json:"new_password"` NewPassword *string `json:"new_password,omitempty"`
ConfirmNewPassword *string `json:"confirm_new_password"` ConfirmNewPassword *string `json:"confirm_new_password,omitempty"`
Email *string `json:"email"` Email *string `json:"email,omitempty"`
GivenName *string `json:"given_name"` GivenName *string `json:"given_name,omitempty"`
FamilyName *string `json:"family_name"` FamilyName *string `json:"family_name,omitempty"`
MiddleName *string `json:"middle_name"` MiddleName *string `json:"middle_name,omitempty"`
Nickname *string `json:"nickname"` Nickname *string `json:"nickname,omitempty"`
Gender *string `json:"gender"` Gender *string `json:"gender,omitempty"`
Birthdate *string `json:"birthdate"` Birthdate *string `json:"birthdate,omitempty"`
PhoneNumber *string `json:"phone_number"` PhoneNumber *string `json:"phone_number,omitempty"`
Picture *string `json:"picture"` Picture *string `json:"picture,omitempty"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"` IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled,omitempty"`
AppData map[string]interface{} `json:"app_data,omitempty"`
} }
type UpdateUserInput struct { type UpdateUserInput struct {
ID string `json:"id"` ID string `json:"id"`
Email *string `json:"email"` Email *string `json:"email,omitempty"`
EmailVerified *bool `json:"email_verified"` EmailVerified *bool `json:"email_verified,omitempty"`
GivenName *string `json:"given_name"` GivenName *string `json:"given_name,omitempty"`
FamilyName *string `json:"family_name"` FamilyName *string `json:"family_name,omitempty"`
MiddleName *string `json:"middle_name"` MiddleName *string `json:"middle_name,omitempty"`
Nickname *string `json:"nickname"` Nickname *string `json:"nickname,omitempty"`
Gender *string `json:"gender"` Gender *string `json:"gender,omitempty"`
Birthdate *string `json:"birthdate"` Birthdate *string `json:"birthdate,omitempty"`
PhoneNumber *string `json:"phone_number"` PhoneNumber *string `json:"phone_number,omitempty"`
Picture *string `json:"picture"` PhoneNumberVerified *bool `json:"phone_number_verified,omitempty"`
Roles []*string `json:"roles"` Picture *string `json:"picture,omitempty"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"` Roles []*string `json:"roles,omitempty"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled,omitempty"`
AppData map[string]interface{} `json:"app_data,omitempty"`
} }
type UpdateWebhookRequest struct { type UpdateWebhookRequest struct {
ID string `json:"id"` ID string `json:"id"`
EventName *string `json:"event_name"` EventName *string `json:"event_name,omitempty"`
EventDescription *string `json:"event_description"` EventDescription *string `json:"event_description,omitempty"`
Endpoint *string `json:"endpoint"` Endpoint *string `json:"endpoint,omitempty"`
Enabled *bool `json:"enabled"` Enabled *bool `json:"enabled,omitempty"`
Headers map[string]interface{} `json:"headers"` Headers map[string]interface{} `json:"headers,omitempty"`
} }
type User struct { type User struct {
ID string `json:"id"` ID string `json:"id"`
Email string `json:"email"` Email *string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified"` EmailVerified bool `json:"email_verified"`
SignupMethods string `json:"signup_methods"` SignupMethods string `json:"signup_methods"`
GivenName *string `json:"given_name"` GivenName *string `json:"given_name,omitempty"`
FamilyName *string `json:"family_name"` FamilyName *string `json:"family_name,omitempty"`
MiddleName *string `json:"middle_name"` MiddleName *string `json:"middle_name,omitempty"`
Nickname *string `json:"nickname"` Nickname *string `json:"nickname,omitempty"`
PreferredUsername *string `json:"preferred_username"` PreferredUsername *string `json:"preferred_username,omitempty"`
Gender *string `json:"gender"` Gender *string `json:"gender,omitempty"`
Birthdate *string `json:"birthdate"` Birthdate *string `json:"birthdate,omitempty"`
PhoneNumber *string `json:"phone_number"` PhoneNumber *string `json:"phone_number,omitempty"`
PhoneNumberVerified *bool `json:"phone_number_verified"` PhoneNumberVerified *bool `json:"phone_number_verified,omitempty"`
Picture *string `json:"picture"` Picture *string `json:"picture,omitempty"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
CreatedAt *int64 `json:"created_at"` CreatedAt *int64 `json:"created_at,omitempty"`
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at,omitempty"`
RevokedTimestamp *int64 `json:"revoked_timestamp"` RevokedTimestamp *int64 `json:"revoked_timestamp,omitempty"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"` IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled,omitempty"`
AppData map[string]interface{} `json:"app_data,omitempty"`
} }
type Users struct { type Users struct {
@ -450,33 +500,34 @@ type Users struct {
type ValidateJWTTokenInput struct { type ValidateJWTTokenInput struct {
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
Token string `json:"token"` Token string `json:"token"`
Roles []string `json:"roles"` Roles []string `json:"roles,omitempty"`
} }
type ValidateJWTTokenResponse struct { type ValidateJWTTokenResponse struct {
IsValid bool `json:"is_valid"` IsValid bool `json:"is_valid"`
Claims map[string]interface{} `json:"claims"` Claims map[string]interface{} `json:"claims,omitempty"`
} }
type ValidateSessionInput struct { type ValidateSessionInput struct {
Cookie string `json:"cookie"` Cookie string `json:"cookie"`
Roles []string `json:"roles"` Roles []string `json:"roles,omitempty"`
} }
type ValidateSessionResponse struct { type ValidateSessionResponse struct {
IsValid bool `json:"is_valid"` IsValid bool `json:"is_valid"`
User *User `json:"user"`
} }
type VerificationRequest struct { type VerificationRequest struct {
ID string `json:"id"` ID string `json:"id"`
Identifier *string `json:"identifier"` Identifier *string `json:"identifier,omitempty"`
Token *string `json:"token"` Token *string `json:"token,omitempty"`
Email *string `json:"email"` Email *string `json:"email,omitempty"`
Expires *int64 `json:"expires"` Expires *int64 `json:"expires,omitempty"`
CreatedAt *int64 `json:"created_at"` CreatedAt *int64 `json:"created_at,omitempty"`
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at,omitempty"`
Nonce *string `json:"nonce"` Nonce *string `json:"nonce,omitempty"`
RedirectURI *string `json:"redirect_uri"` RedirectURI *string `json:"redirect_uri,omitempty"`
} }
type VerificationRequests struct { type VerificationRequests struct {
@ -486,35 +537,36 @@ type VerificationRequests struct {
type VerifyEmailInput struct { type VerifyEmailInput struct {
Token string `json:"token"` Token string `json:"token"`
State *string `json:"state"` State *string `json:"state,omitempty"`
} }
type VerifyOTPRequest struct { type VerifyOTPRequest struct {
Email *string `json:"email"` Email *string `json:"email,omitempty"`
PhoneNumber *string `json:"phone_number"` PhoneNumber *string `json:"phone_number,omitempty"`
Otp string `json:"otp"` Otp string `json:"otp"`
State *string `json:"state"` IsTotp *bool `json:"is_totp,omitempty"`
State *string `json:"state,omitempty"`
} }
type Webhook struct { type Webhook struct {
ID string `json:"id"` ID string `json:"id"`
EventName *string `json:"event_name"` EventName *string `json:"event_name,omitempty"`
EventDescription *string `json:"event_description"` EventDescription *string `json:"event_description,omitempty"`
Endpoint *string `json:"endpoint"` Endpoint *string `json:"endpoint,omitempty"`
Enabled *bool `json:"enabled"` Enabled *bool `json:"enabled,omitempty"`
Headers map[string]interface{} `json:"headers"` Headers map[string]interface{} `json:"headers,omitempty"`
CreatedAt *int64 `json:"created_at"` CreatedAt *int64 `json:"created_at,omitempty"`
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at,omitempty"`
} }
type WebhookLog struct { type WebhookLog struct {
ID string `json:"id"` ID string `json:"id"`
HTTPStatus *int64 `json:"http_status"` HTTPStatus *int64 `json:"http_status,omitempty"`
Response *string `json:"response"` Response *string `json:"response,omitempty"`
Request *string `json:"request"` Request *string `json:"request,omitempty"`
WebhookID *string `json:"webhook_id"` WebhookID *string `json:"webhook_id,omitempty"`
CreatedAt *int64 `json:"created_at"` CreatedAt *int64 `json:"created_at,omitempty"`
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at,omitempty"`
} }
type WebhookLogs struct { type WebhookLogs struct {

View File

@ -20,19 +20,25 @@ type Meta {
is_github_login_enabled: Boolean! is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean! is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean! is_apple_login_enabled: Boolean!
is_discord_login_enabled: Boolean!
is_twitter_login_enabled: Boolean! is_twitter_login_enabled: Boolean!
is_microsoft_login_enabled: Boolean! is_microsoft_login_enabled: Boolean!
is_twitch_login_enabled: Boolean!
is_roblox_login_enabled: Boolean!
is_email_verification_enabled: Boolean! is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean! is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean! is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean! is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean! is_strong_password_enabled: Boolean!
is_multi_factor_auth_enabled: Boolean! is_multi_factor_auth_enabled: Boolean!
is_mobile_basic_authentication_enabled: Boolean!
is_phone_verification_enabled: Boolean!
} }
type User { type User {
id: ID! id: ID!
email: String! # email or phone_number is always present
email: String
email_verified: Boolean! email_verified: Boolean!
signup_methods: String! signup_methods: String!
given_name: String given_name: String
@ -51,6 +57,7 @@ type User {
updated_at: Int64 updated_at: Int64
revoked_timestamp: Int64 revoked_timestamp: Int64
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
app_data: Map
} }
type Users { type Users {
@ -93,17 +100,30 @@ type AuthResponse {
message: String! message: String!
should_show_email_otp_screen: Boolean should_show_email_otp_screen: Boolean
should_show_mobile_otp_screen: Boolean should_show_mobile_otp_screen: Boolean
should_show_totp_screen: Boolean
access_token: String access_token: String
id_token: String id_token: String
refresh_token: String refresh_token: String
expires_in: Int64 expires_in: Int64
user: User user: User
# key for totp login
# it is a base64 image url
authenticator_scanner_image: String
# string which can be used instead of scanner image
authenticator_secret: String
# recovery codes for totp login shared with user only once
authenticator_recovery_codes: [String]
} }
type Response { type Response {
message: String! message: String!
} }
type ForgotPasswordResponse {
message: String!
should_show_mobile_otp_screen: Boolean
}
type InviteMembersResponse { type InviteMembersResponse {
message: String! message: String!
Users: [User!]! Users: [User!]!
@ -139,6 +159,7 @@ type Env {
RESET_PASSWORD_URL: String RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean! DISABLE_EMAIL_VERIFICATION: Boolean!
DISABLE_BASIC_AUTHENTICATION: Boolean! DISABLE_BASIC_AUTHENTICATION: Boolean!
DISABLE_MOBILE_BASIC_AUTHENTICATION: Boolean!
DISABLE_MAGIC_LINK_LOGIN: Boolean! DISABLE_MAGIC_LINK_LOGIN: Boolean!
DISABLE_LOGIN_PAGE: Boolean! DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean! DISABLE_SIGN_UP: Boolean!
@ -160,17 +181,26 @@ type Env {
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String APPLE_CLIENT_SECRET: String
DISCORD_CLIENT_ID: String
DISCORD_CLIENT_SECRET: String
TWITTER_CLIENT_ID: String TWITTER_CLIENT_ID: String
TWITTER_CLIENT_SECRET: String TWITTER_CLIENT_SECRET: String
MICROSOFT_CLIENT_ID: String MICROSOFT_CLIENT_ID: String
MICROSOFT_CLIENT_SECRET: String MICROSOFT_CLIENT_SECRET: String
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String
TWITCH_CLIENT_ID: String
TWITCH_CLIENT_SECRET: String
ROBLOX_CLIENT_ID: String
ROBLOX_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
APP_COOKIE_SECURE: Boolean! APP_COOKIE_SECURE: Boolean!
ADMIN_COOKIE_SECURE: Boolean! ADMIN_COOKIE_SECURE: Boolean!
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
DEFAULT_AUTHORIZE_RESPONSE_MODE: String DEFAULT_AUTHORIZE_RESPONSE_MODE: String
DISABLE_PLAYGROUND: Boolean!
DISABLE_MAIL_OTP_LOGIN: Boolean!
DISABLE_TOTP_LOGIN: Boolean!
} }
type ValidateJWTTokenResponse { type ValidateJWTTokenResponse {
@ -180,6 +210,7 @@ type ValidateJWTTokenResponse {
type ValidateSessionResponse { type ValidateSessionResponse {
is_valid: Boolean! is_valid: Boolean!
user: User!
} }
type GenerateJWTKeysResponse { type GenerateJWTKeysResponse {
@ -262,6 +293,7 @@ input UpdateEnvInput {
ADMIN_COOKIE_SECURE: Boolean ADMIN_COOKIE_SECURE: Boolean
DISABLE_EMAIL_VERIFICATION: Boolean DISABLE_EMAIL_VERIFICATION: Boolean
DISABLE_BASIC_AUTHENTICATION: Boolean DISABLE_BASIC_AUTHENTICATION: Boolean
DISABLE_MOBILE_BASIC_AUTHENTICATION: Boolean
DISABLE_MAGIC_LINK_LOGIN: Boolean DISABLE_MAGIC_LINK_LOGIN: Boolean
DISABLE_LOGIN_PAGE: Boolean DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean DISABLE_SIGN_UP: Boolean
@ -283,15 +315,24 @@ input UpdateEnvInput {
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String APPLE_CLIENT_SECRET: String
DISCORD_CLIENT_ID: String
DISCORD_CLIENT_SECRET: String
TWITTER_CLIENT_ID: String TWITTER_CLIENT_ID: String
TWITTER_CLIENT_SECRET: String TWITTER_CLIENT_SECRET: String
MICROSOFT_CLIENT_ID: String MICROSOFT_CLIENT_ID: String
MICROSOFT_CLIENT_SECRET: String MICROSOFT_CLIENT_SECRET: String
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String
TWITCH_CLIENT_ID: String
TWITCH_CLIENT_SECRET: String
ROBLOX_CLIENT_ID: String
ROBLOX_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
DEFAULT_AUTHORIZE_RESPONSE_MODE: String DEFAULT_AUTHORIZE_RESPONSE_MODE: String
DISABLE_PLAYGROUND: Boolean
DISABLE_MAIL_OTP_LOGIN: Boolean
DISABLE_TOTP_LOGIN: Boolean
} }
input AdminLoginInput { input AdminLoginInput {
@ -302,6 +343,7 @@ input AdminSignupInput {
admin_secret: String! admin_secret: String!
} }
# Deprecated from v1.2.0
input MobileSignUpInput { input MobileSignUpInput {
email: String email: String
given_name: String given_name: String
@ -322,10 +364,11 @@ input MobileSignUpInput {
# it is used to get code for an on-going auth process during login # it is used to get code for an on-going auth process during login
# and use that code for setting `c_hash` in id_token # and use that code for setting `c_hash` in id_token
state: String state: String
app_data: Map
} }
input SignUpInput { input SignUpInput {
email: String! email: String
given_name: String given_name: String
family_name: String family_name: String
middle_name: String middle_name: String
@ -344,10 +387,12 @@ input SignUpInput {
# it is used to get code for an on-going auth process during login # it is used to get code for an on-going auth process during login
# and use that code for setting `c_hash` in id_token # and use that code for setting `c_hash` in id_token
state: String state: String
app_data: Map
} }
input LoginInput { input LoginInput {
email: String! email: String
phone_number: String
password: String! password: String!
roles: [String!] roles: [String!]
scope: [String!] scope: [String!]
@ -357,6 +402,7 @@ input LoginInput {
state: String state: String
} }
# Deprecated from v1.2.0
input MobileLoginInput { input MobileLoginInput {
phone_number: String! phone_number: String!
password: String! password: String!
@ -399,6 +445,7 @@ input UpdateProfileInput {
phone_number: String phone_number: String
picture: String picture: String
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
app_data: Map
} }
input UpdateUserInput { input UpdateUserInput {
@ -412,19 +459,24 @@ input UpdateUserInput {
gender: String gender: String
birthdate: String birthdate: String
phone_number: String phone_number: String
phone_number_verified: Boolean
picture: String picture: String
roles: [String] roles: [String]
is_multi_factor_auth_enabled: Boolean is_multi_factor_auth_enabled: Boolean
app_data: Map
} }
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String
phone_number: String
state: String state: String
redirect_uri: String redirect_uri: String
} }
input ResetPasswordInput { input ResetPasswordInput {
token: String! token: String
otp: String
phone_number: String
password: String! password: String!
confirm_password: String! confirm_password: String!
} }
@ -540,10 +592,11 @@ input DeleteEmailTemplateRequest {
} }
input VerifyOTPRequest { input VerifyOTPRequest {
# either email or phone_number is required # either email, phone_number or totp_token is required
email: String email: String
phone_number: String phone_number: String
otp: String! otp: String!
is_totp: Boolean
# state is used for authorization code grant flow # state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login # it is used to get code for an on-going auth process during login
# and use that code for setting `c_hash` in id_token # and use that code for setting `c_hash` in id_token
@ -566,19 +619,22 @@ input GetUserRequest {
type Mutation { type Mutation {
signup(params: SignUpInput!): AuthResponse! signup(params: SignUpInput!): AuthResponse!
# Deprecated from v1.2.0
mobile_signup(params: MobileSignUpInput): AuthResponse! mobile_signup(params: MobileSignUpInput): AuthResponse!
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
# Deprecated from v1.2.0
mobile_login(params: MobileLoginInput!): AuthResponse! mobile_login(params: MobileLoginInput!): AuthResponse!
magic_link_login(params: MagicLinkLoginInput!): Response! magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response! logout: Response!
update_profile(params: UpdateProfileInput!): Response! update_profile(params: UpdateProfileInput!): Response!
verify_email(params: VerifyEmailInput!): AuthResponse! verify_email(params: VerifyEmailInput!): AuthResponse!
resend_verify_email(params: ResendVerifyEmailInput!): Response! resend_verify_email(params: ResendVerifyEmailInput!): Response!
forgot_password(params: ForgotPasswordInput!): Response! forgot_password(params: ForgotPasswordInput!): ForgotPasswordResponse!
reset_password(params: ResetPasswordInput!): Response! reset_password(params: ResetPasswordInput!): Response!
revoke(params: OAuthRevokeInput!): Response! revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse! verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response! resend_otp(params: ResendOTPRequest!): Response!
deactivate_account: Response!
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!
@ -603,6 +659,7 @@ type Query {
meta: Meta! meta: Meta!
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
profile: User! profile: User!
is_registered(email: String!): Response! # custom api
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
validate_session(params: ValidateSessionInput): ValidateSessionResponse! validate_session(params: ValidateSessionInput): ValidateSessionResponse!
# admin only apis # admin only apis

View File

@ -2,6 +2,7 @@ package graph
// This file will be automatically regenerated based on the schema, any resolver implementations // This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end. // will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.45
import ( import (
"context" "context"
@ -57,7 +58,7 @@ func (r *mutationResolver) ResendVerifyEmail(ctx context.Context, params model.R
} }
// ForgotPassword is the resolver for the forgot_password field. // ForgotPassword is the resolver for the forgot_password field.
func (r *mutationResolver) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) { func (r *mutationResolver) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.ForgotPasswordResponse, error) {
return resolvers.ForgotPasswordResolver(ctx, params) return resolvers.ForgotPasswordResolver(ctx, params)
} }
@ -81,6 +82,11 @@ func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTP
return resolvers.ResendOTPResolver(ctx, params) return resolvers.ResendOTPResolver(ctx, params)
} }
// DeactivateAccount is the resolver for the deactivate_account field.
func (r *mutationResolver) DeactivateAccount(ctx context.Context) (*model.Response, error) {
return resolvers.DeactivateAccountResolver(ctx)
}
// DeleteUser is the resolver for the _delete_user field. // DeleteUser is the resolver for the _delete_user field.
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
return resolvers.DeleteUserResolver(ctx, params) return resolvers.DeleteUserResolver(ctx, params)
@ -181,6 +187,11 @@ func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.ProfileResolver(ctx) return resolvers.ProfileResolver(ctx)
} }
// IsRegistered is the resolver for the signup field.
func (r *queryResolver) IsRegistered(ctx context.Context, email string) (*model.Response, error) {
return resolvers.IsRegisteredResolver(ctx, email)
}
// ValidateJwtToken is the resolver for the validate_jwt_token field. // ValidateJwtToken is the resolver for the validate_jwt_token field.
func (r *queryResolver) ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) { func (r *queryResolver) ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) {
return resolvers.ValidateJwtTokenResolver(ctx, params) return resolvers.ValidateJwtTokenResolver(ctx, params)

View File

@ -55,6 +55,8 @@ import (
const ( const (
authorizeWebMessageTemplate = "authorize_web_message.tmpl" authorizeWebMessageTemplate = "authorize_web_message.tmpl"
authorizeFormPostTemplate = "authorize_form_post.tmpl" authorizeFormPostTemplate = "authorize_form_post.tmpl"
baseAppPath = "/app"
signupPath = "/app/signup"
) )
// AuthorizeHandler is the handler for the /authorize route // AuthorizeHandler is the handler for the /authorize route
@ -74,6 +76,7 @@ func AuthorizeHandler() gin.HandlerFunc {
clientID := strings.TrimSpace(gc.Query("client_id")) clientID := strings.TrimSpace(gc.Query("client_id"))
responseMode := strings.TrimSpace(gc.Query("response_mode")) responseMode := strings.TrimSpace(gc.Query("response_mode"))
nonce := strings.TrimSpace(gc.Query("nonce")) nonce := strings.TrimSpace(gc.Query("nonce"))
screenHint := strings.TrimSpace(gc.Query("screen_hint"))
var scope []string var scope []string
if scopeString == "" { if scopeString == "" {
@ -120,27 +123,33 @@ func AuthorizeHandler() gin.HandlerFunc {
// TODO add state with timeout // TODO add state with timeout
// used for response mode query or fragment // used for response mode query or fragment
loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI authState := "state=" + state + "&scope=" + scopeString + "&redirect_uri=" + redirectURI
if responseType == constants.ResponseTypeCode { if responseType == constants.ResponseTypeCode {
loginState += "&code=" + code authState += "&code=" + code
if err := memorystore.Provider.SetState(state, code+"@@"+codeChallenge); err != nil { if err := memorystore.Provider.SetState(state, code+"@@"+codeChallenge); err != nil {
log.Debug("Error setting temp code", err) log.Debug("Error setting temp code", err)
} }
} else { } else {
loginState += "&nonce=" + nonce authState += "&nonce=" + nonce
if err := memorystore.Provider.SetState(state, nonce); err != nil { if err := memorystore.Provider.SetState(state, nonce); err != nil {
log.Debug("Error setting temp code", err) log.Debug("Error setting temp code", err)
} }
} }
loginURL := "/app?" + loginState authURL := baseAppPath + "?" + authState
if responseMode == constants.ResponseModeFragment { if screenHint == constants.ScreenHintSignUp {
loginURL = "/app#" + loginState authURL = signupPath + "?" + authState
}
if responseMode == constants.ResponseModeFragment && screenHint == constants.ScreenHintSignUp {
authURL = signupPath + "#" + authState
} else if responseMode == constants.ResponseModeFragment {
authURL = baseAppPath + "#" + authState
} }
if responseType == constants.ResponseTypeCode && codeChallenge == "" { if responseType == constants.ResponseTypeCode && codeChallenge == "" {
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": map[string]interface{}{ "response": map[string]interface{}{
"error": "code_challenge_required", "error": "code_challenge_required",
@ -160,7 +169,7 @@ func AuthorizeHandler() gin.HandlerFunc {
sessionToken, err := cookie.GetSession(gc) sessionToken, err := cookie.GetSession(gc)
if err != nil { if err != nil {
log.Debug("GetSession failed: ", err) log.Debug("GetSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -168,7 +177,7 @@ func AuthorizeHandler() gin.HandlerFunc {
claims, err := token.ValidateBrowserSession(gc, sessionToken) claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil { if err != nil {
log.Debug("ValidateBrowserSession failed: ", err) log.Debug("ValidateBrowserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -176,7 +185,7 @@ func AuthorizeHandler() gin.HandlerFunc {
user, err := db.Provider.GetUserByID(gc, userID) user, err := db.Provider.GetUserByID(gc, userID)
if err != nil { if err != nil {
log.Debug("GetUserByID failed: ", err) log.Debug("GetUserByID failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": map[string]interface{}{ "response": map[string]interface{}{
"error": "signup_required", "error": "signup_required",
@ -197,27 +206,27 @@ func AuthorizeHandler() gin.HandlerFunc {
newSessionTokenData, newSessionToken, newSessionExpiresAt, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) newSessionTokenData, newSessionToken, newSessionExpiresAt, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
if err != nil { if err != nil {
log.Debug("CreateSessionToken failed: ", err) log.Debug("CreateSessionToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
// TODO: add state with timeout // TODO: add state with timeout
// if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { // if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil {
// log.Debug("SetState failed: ", err) // log.Debug("SetState failed: ", err)
// handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) // handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
// return // return
// } // }
// TODO: add state with timeout // TODO: add state with timeout
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+newSessionToken); err != nil { if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+newSessionToken); err != nil {
log.Debug("SetState failed: ", err) log.Debug("SetState failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken, newSessionExpiresAt); err != nil { if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken, newSessionExpiresAt); err != nil {
log.Debug("SetUserSession failed: ", err) log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -251,7 +260,7 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
} }
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": map[string]interface{}{ "response": map[string]interface{}{
"code": code, "code": code,
@ -267,19 +276,19 @@ func AuthorizeHandler() gin.HandlerFunc {
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "") authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "")
if err != nil { if err != nil {
log.Debug("CreateAuthToken failed: ", err) log.Debug("CreateAuthToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, authToken.FingerPrintHash, authToken.SessionTokenExpiresAt); err != nil { if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, authToken.FingerPrintHash, authToken.SessionTokenExpiresAt); err != nil {
log.Debug("SetUserSession failed: ", err) log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.AccessToken.Token, authToken.AccessToken.ExpiresAt); err != nil { if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.AccessToken.Token, authToken.AccessToken.ExpiresAt); err != nil {
log.Debug("SetUserSession failed: ", err) log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -322,14 +331,14 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
} }
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": res, "response": res,
}, http.StatusOK) }, http.StatusOK)
return return
} }
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
} }
} }
@ -352,14 +361,14 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC
return nil return nil
} }
func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) { func handleResponse(gc *gin.Context, responseMode, authURI, redirectURI string, data map[string]interface{}, httpStatusCode int) {
isAuthenticationRequired := false isAuthenticationRequired := false
if _, ok := data["response"].(map[string]interface{})["error"]; ok { if _, ok := data["response"].(map[string]interface{})["error"]; ok {
isAuthenticationRequired = true isAuthenticationRequired = true
} }
if isAuthenticationRequired && responseMode != constants.ResponseModeWebMessage { if isAuthenticationRequired && responseMode != constants.ResponseModeWebMessage {
gc.Redirect(http.StatusFound, loginURI) gc.Redirect(http.StatusFound, authURI)
return return
} }

View File

@ -7,15 +7,16 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
"golang.org/x/oauth2"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
@ -23,6 +24,7 @@ import (
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
@ -32,11 +34,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
provider := ctx.Param("oauth_provider") provider := ctx.Param("oauth_provider")
state := ctx.Request.FormValue("state") state := ctx.Request.FormValue("state")
sessionState, err := memorystore.Provider.GetState(state) sessionState, err := memorystore.Provider.GetState(state)
if sessionState == "" || err != nil { if sessionState == "" || err != nil {
log.Debug("Invalid oauth state: ", state) log.Debug("Invalid oauth state: ", state)
ctx.JSON(400, gin.H{"error": "invalid oauth state"}) ctx.JSON(400, gin.H{"error": "invalid oauth state"})
return
} }
// contains random token, redirect url, role // contains random token, redirect url, role
sessionSplit := strings.Split(state, "___") sessionSplit := strings.Split(state, "___")
@ -46,32 +48,49 @@ func OAuthCallbackHandler() gin.HandlerFunc {
ctx.JSON(400, gin.H{"error": "invalid redirect url"}) ctx.JSON(400, gin.H{"error": "invalid redirect url"})
return return
} }
// remove state from store // remove state from store
go memorystore.Provider.RemoveState(state) go memorystore.Provider.RemoveState(state)
stateValue := sessionSplit[0] stateValue := sessionSplit[0]
redirectURL := sessionSplit[1] redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",") inputRoles := strings.Split(sessionSplit[2], ",")
scopes := strings.Split(sessionSplit[3], ",") scopeString := sessionSplit[3]
scopes := []string{}
if scopeString != "" {
if strings.Contains(scopeString, ",") {
scopes = strings.Split(scopeString, ",")
}
if strings.Contains(scopeString, " ") {
scopes = strings.Split(scopeString, " ")
}
}
var user *models.User var user *models.User
oauthCode := ctx.Request.FormValue("code") oauthCode := ctx.Request.FormValue("code")
if oauthCode == "" {
log.Debug("Invalid oauth code: ", oauthCode)
ctx.JSON(400, gin.H{"error": "invalid oauth code"})
return
}
switch provider { switch provider {
case constants.AuthRecipeMethodGoogle: case constants.AuthRecipeMethodGoogle:
user, err = processGoogleUserInfo(oauthCode) user, err = processGoogleUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodGithub: case constants.AuthRecipeMethodGithub:
user, err = processGithubUserInfo(oauthCode) user, err = processGithubUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodFacebook: case constants.AuthRecipeMethodFacebook:
user, err = processFacebookUserInfo(oauthCode) user, err = processFacebookUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodLinkedIn: case constants.AuthRecipeMethodLinkedIn:
user, err = processLinkedInUserInfo(oauthCode) user, err = processLinkedInUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodApple: case constants.AuthRecipeMethodApple:
user, err = processAppleUserInfo(oauthCode) user, err = processAppleUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodDiscord:
user, err = processDiscordUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodTwitter: case constants.AuthRecipeMethodTwitter:
user, err = processTwitterUserInfo(oauthCode, sessionState) user, err = processTwitterUserInfo(ctx, oauthCode, sessionState)
case constants.AuthRecipeMethodMicrosoft: case constants.AuthRecipeMethodMicrosoft:
user, err = processMicrosoftUserInfo(oauthCode) user, err = processMicrosoftUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodTwitch:
user, err = processTwitchUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodRoblox:
user, err = processRobloxUserInfo(ctx, oauthCode, sessionState)
default: default:
log.Info("Invalid oauth provider") log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`) err = fmt.Errorf(`invalid oauth provider`)
@ -83,7 +102,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
return return
} }
existingUser, err := db.Provider.GetUserByEmail(ctx, user.Email) existingUser, err := db.Provider.GetUserByEmail(ctx, refs.StringValue(user.Email))
log := log.WithField("user", user.Email) log := log.WithField("user", user.Email)
isSignUp := false isSignUp := false
@ -241,8 +260,9 @@ func OAuthCallbackHandler() gin.HandlerFunc {
expiresIn = 1 expiresIn = 1
} }
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce // params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce
// Note: If OIDC breaks in the future, use the above params
params := "state=" + stateValue + "&nonce=" + nonce
if code != "" { if code != "" {
params += "&code=" + code params += "&code=" + code
} }
@ -260,6 +280,8 @@ func OAuthCallbackHandler() gin.HandlerFunc {
go func() { go func() {
if isSignUp { if isSignUp {
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, provider, user) utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, provider, user)
// User is also logged in with signup
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, provider, user)
} else { } else {
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, provider, user) utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, provider, user)
} }
@ -279,13 +301,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
} }
func processGoogleUserInfo(code string) (*models.User, error) { func processGoogleUserInfo(ctx context.Context, code string) (*models.User, error) {
var user *models.User
ctx := context.Background()
oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code) oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code)
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid google exchange code: %s", err.Error()) return nil, fmt.Errorf("invalid google exchange code: %s", err.Error())
} }
verifier := oauth.OIDCProviders.GoogleOIDC.Verifier(&oidc.Config{ClientID: oauth.OAuthProviders.GoogleConfig.ClientID}) verifier := oauth.OIDCProviders.GoogleOIDC.Verifier(&oidc.Config{ClientID: oauth.OAuthProviders.GoogleConfig.ClientID})
@ -293,36 +313,35 @@ func processGoogleUserInfo(code string) (*models.User, error) {
rawIDToken, ok := oauth2Token.Extra("id_token").(string) rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok { if !ok {
log.Debug("Failed to extract ID Token from OAuth2 token") log.Debug("Failed to extract ID Token from OAuth2 token")
return user, fmt.Errorf("unable to extract id_token") return nil, fmt.Errorf("unable to extract id_token")
} }
// Parse and verify ID Token payload. // Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken) idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil { if err != nil {
log.Debug("Failed to verify ID Token: ", err) log.Debug("Failed to verify ID Token: ", err)
return user, fmt.Errorf("unable to verify id_token: %s", err.Error()) return nil, fmt.Errorf("unable to verify id_token: %s", err.Error())
} }
user := &models.User{}
if err := idToken.Claims(&user); err != nil { if err := idToken.Claims(&user); err != nil {
log.Debug("Failed to parse ID Token claims: ", err) log.Debug("Failed to parse ID Token claims: ", err)
return user, fmt.Errorf("unable to extract claims") return nil, fmt.Errorf("unable to extract claims")
} }
return user, nil return user, nil
} }
func processGithubUserInfo(code string) (*models.User, error) { func processGithubUserInfo(ctx context.Context, code string) (*models.User, error) {
var user *models.User oauth2Token, err := oauth.OAuthProviders.GithubConfig.Exchange(ctx, code)
oauth2Token, err := oauth.OAuthProviders.GithubConfig.Exchange(context.TODO(), code)
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid github exchange code: %s", err.Error()) return nil, fmt.Errorf("invalid github exchange code: %s", err.Error())
} }
client := http.Client{} client := http.Client{}
req, err := http.NewRequest("GET", constants.GithubUserInfoURL, nil) req, err := http.NewRequest("GET", constants.GithubUserInfoURL, nil)
if err != nil { if err != nil {
log.Debug("Failed to create github user info request: ", err) log.Debug("Failed to create github user info request: ", err)
return user, fmt.Errorf("error creating github user info request: %s", err.Error()) return nil, fmt.Errorf("error creating github user info request: %s", err.Error())
} }
req.Header.Set( req.Header.Set(
"Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken), "Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken),
@ -331,18 +350,18 @@ func processGithubUserInfo(code string) (*models.User, error) {
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Debug("Failed to request github user info: ", err) log.Debug("Failed to request github user info: ", err)
return user, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
log.Debug("Failed to read github user info response body: ", err) log.Debug("Failed to read github user info response body: ", err)
return user, fmt.Errorf("failed to read github response body: %s", err.Error()) return nil, fmt.Errorf("failed to read github response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request github user info: ", string(body)) log.Debug("Failed to request github user info: ", string(body))
return user, fmt.Errorf("failed to request github user info: %s", string(body)) return nil, fmt.Errorf("failed to request github user info: %s", string(body))
} }
userRawData := make(map[string]string) userRawData := make(map[string]string)
@ -371,7 +390,7 @@ func processGithubUserInfo(code string) (*models.User, error) {
req, err := http.NewRequest(http.MethodGet, constants.GithubUserEmails, nil) req, err := http.NewRequest(http.MethodGet, constants.GithubUserEmails, nil)
if err != nil { if err != nil {
log.Debug("Failed to create github emails request: ", err) log.Debug("Failed to create github emails request: ", err)
return user, fmt.Errorf("error creating github user info request: %s", err.Error()) return nil, fmt.Errorf("error creating github user info request: %s", err.Error())
} }
req.Header.Set( req.Header.Set(
"Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken), "Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken),
@ -380,24 +399,25 @@ func processGithubUserInfo(code string) (*models.User, error) {
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Debug("Failed to request github user email: ", err) log.Debug("Failed to request github user email: ", err)
return user, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
log.Debug("Failed to read github user email response body: ", err) log.Debug("Failed to read github user email response body: ", err)
return user, fmt.Errorf("failed to read github response body: %s", err.Error()) return nil, fmt.Errorf("failed to read github response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request github user email: ", string(body)) log.Debug("Failed to request github user email: ", string(body))
return user, fmt.Errorf("failed to request github user info: %s", string(body)) return nil, fmt.Errorf("failed to request github user info: %s", string(body))
} }
emailData := []GithubUserEmails{} emailData := []GithubUserEmails{}
err = json.Unmarshal(body, &emailData) err = json.Unmarshal(body, &emailData)
if err != nil { if err != nil {
log.Debug("Failed to parse github user email: ", err) log.Debug("Failed to parse github user email: ", err)
return nil, fmt.Errorf("failed to parse github user email: %s", err.Error())
} }
for _, userEmail := range emailData { for _, userEmail := range emailData {
@ -408,45 +428,44 @@ func processGithubUserInfo(code string) (*models.User, error) {
} }
} }
user = &models.User{ user := &models.User{
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &picture, Picture: &picture,
Email: email, Email: &email,
} }
return user, nil return user, nil
} }
func processFacebookUserInfo(code string) (*models.User, error) { func processFacebookUserInfo(ctx context.Context, code string) (*models.User, error) {
var user *models.User oauth2Token, err := oauth.OAuthProviders.FacebookConfig.Exchange(ctx, code)
oauth2Token, err := oauth.OAuthProviders.FacebookConfig.Exchange(context.TODO(), code)
if err != nil { if err != nil {
log.Debug("Invalid facebook exchange code: ", err) log.Debug("Invalid facebook exchange code: ", err)
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error()) return nil, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
} }
client := http.Client{} client := http.Client{}
req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+oauth2Token.AccessToken, nil) req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+oauth2Token.AccessToken, nil)
if err != nil { if err != nil {
log.Debug("Error creating facebook user info request: ", err) log.Debug("Error creating facebook user info request: ", err)
return user, fmt.Errorf("error creating facebook user info request: %s", err.Error()) return nil, fmt.Errorf("error creating facebook user info request: %s", err.Error())
} }
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Debug("Failed to process facebook user: ", err) log.Debug("Failed to process facebook user: ", err)
return user, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
log.Debug("Failed to read facebook response: ", err) log.Debug("Failed to read facebook response: ", err)
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error()) return nil, fmt.Errorf("failed to read facebook response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request facebook user info: ", string(body)) log.Debug("Failed to request facebook user info: ", string(body))
return user, fmt.Errorf("failed to request facebook user info: %s", string(body)) return nil, fmt.Errorf("failed to request facebook user info: %s", string(body))
} }
userRawData := make(map[string]interface{}) userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData) json.Unmarshal(body, &userRawData)
@ -459,29 +478,28 @@ func processFacebookUserInfo(code string) (*models.User, error) {
lastName := fmt.Sprintf("%v", userRawData["last_name"]) lastName := fmt.Sprintf("%v", userRawData["last_name"])
picture := fmt.Sprintf("%v", picDataObject["url"]) picture := fmt.Sprintf("%v", picDataObject["url"])
user = &models.User{ user := &models.User{
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &picture, Picture: &picture,
Email: email, Email: &email,
} }
return user, nil return user, nil
} }
func processLinkedInUserInfo(code string) (*models.User, error) { func processLinkedInUserInfo(ctx context.Context, code string) (*models.User, error) {
var user *models.User oauth2Token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(ctx, code)
oauth2Token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(context.TODO(), code)
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid linkedin exchange code: %s", err.Error()) return nil, fmt.Errorf("invalid linkedin exchange code: %s", err.Error())
} }
client := http.Client{} client := http.Client{}
req, err := http.NewRequest("GET", constants.LinkedInUserInfoURL, nil) req, err := http.NewRequest("GET", constants.LinkedInUserInfoURL, nil)
if err != nil { if err != nil {
log.Debug("Failed to create linkedin user info request: ", err) log.Debug("Failed to create linkedin user info request: ", err)
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error()) return nil, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
} }
req.Header = http.Header{ req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)}, "Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
@ -490,19 +508,19 @@ func processLinkedInUserInfo(code string) (*models.User, error) {
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Debug("Failed to request linkedin user info: ", err) log.Debug("Failed to request linkedin user info: ", err)
return user, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
log.Debug("Failed to read linkedin user info response body: ", err) log.Debug("Failed to read linkedin user info response body: ", err)
return user, fmt.Errorf("failed to read linkedin response body: %s", err.Error()) return nil, fmt.Errorf("failed to read linkedin response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body)) log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body)) return nil, fmt.Errorf("failed to request linkedin user info: %s", string(body))
} }
userRawData := make(map[string]interface{}) userRawData := make(map[string]interface{})
@ -511,7 +529,7 @@ func processLinkedInUserInfo(code string) (*models.User, error) {
req, err = http.NewRequest("GET", constants.LinkedInEmailURL, nil) req, err = http.NewRequest("GET", constants.LinkedInEmailURL, nil)
if err != nil { if err != nil {
log.Debug("Failed to create linkedin email info request: ", err) log.Debug("Failed to create linkedin email info request: ", err)
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error()) return nil, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
} }
req.Header = http.Header{ req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)}, "Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
@ -520,18 +538,18 @@ func processLinkedInUserInfo(code string) (*models.User, error) {
response, err = client.Do(req) response, err = client.Do(req)
if err != nil { if err != nil {
log.Debug("Failed to request linkedin email info: ", err) log.Debug("Failed to request linkedin email info: ", err)
return user, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err = io.ReadAll(response.Body) body, err = io.ReadAll(response.Body)
if err != nil { if err != nil {
log.Debug("Failed to read linkedin email info response body: ", err) log.Debug("Failed to read linkedin email info response body: ", err)
return user, fmt.Errorf("failed to read linkedin email response body: %s", err.Error()) return nil, fmt.Errorf("failed to read linkedin email response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body)) log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body)) return nil, fmt.Errorf("failed to request linkedin user info: %s", string(body))
} }
emailRawData := make(map[string]interface{}) emailRawData := make(map[string]interface{})
json.Unmarshal(body, &emailRawData) json.Unmarshal(body, &emailRawData)
@ -541,19 +559,19 @@ func processLinkedInUserInfo(code string) (*models.User, error) {
profilePicture := userRawData["profilePicture"].(map[string]interface{})["displayImage~"].(map[string]interface{})["elements"].([]interface{})[0].(map[string]interface{})["identifiers"].([]interface{})[0].(map[string]interface{})["identifier"].(string) profilePicture := userRawData["profilePicture"].(map[string]interface{})["displayImage~"].(map[string]interface{})["elements"].([]interface{})[0].(map[string]interface{})["identifiers"].([]interface{})[0].(map[string]interface{})["identifier"].(string)
emailAddress := emailRawData["elements"].([]interface{})[0].(map[string]interface{})["handle~"].(map[string]interface{})["emailAddress"].(string) emailAddress := emailRawData["elements"].([]interface{})[0].(map[string]interface{})["handle~"].(map[string]interface{})["emailAddress"].(string)
user = &models.User{ user := &models.User{
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &profilePicture, Picture: &profilePicture,
Email: emailAddress, Email: &emailAddress,
} }
return user, nil return user, nil
} }
func processAppleUserInfo(code string) (*models.User, error) { func processAppleUserInfo(ctx context.Context, code string) (*models.User, error) {
var user *models.User var user = &models.User{}
oauth2Token, err := oauth.OAuthProviders.AppleConfig.Exchange(context.TODO(), code) oauth2Token, err := oauth.OAuthProviders.AppleConfig.Exchange(ctx, code)
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid apple exchange code: %s", err.Error()) return user, fmt.Errorf("invalid apple exchange code: %s", err.Error())
@ -580,12 +598,12 @@ func processAppleUserInfo(code string) (*models.User, error) {
log.Debug("Failed to unmarshal claims data: ", err) log.Debug("Failed to unmarshal claims data: ", err)
return user, fmt.Errorf("failed to unmarshal claims data: %s", err.Error()) return user, fmt.Errorf("failed to unmarshal claims data: %s", err.Error())
} }
if val, ok := claims["email"]; !ok || val == nil {
if val, ok := claims["email"]; !ok {
log.Debug("Failed to extract email from claims.") log.Debug("Failed to extract email from claims.")
return user, fmt.Errorf("unable to extract email, please check the scopes enabled for your app. It needs `email`, `name` scopes") return user, fmt.Errorf("unable to extract email, please check the scopes enabled for your app. It needs `email`, `name` scopes")
} else { } else {
user.Email = val.(string) email := val.(string)
user.Email = &email
} }
if val, ok := claims["name"]; ok { if val, ok := claims["name"]; ok {
@ -601,22 +619,86 @@ func processAppleUserInfo(code string) (*models.User, error) {
} }
} }
return user, err return nil, err
} }
func processTwitterUserInfo(code, verifier string) (*models.User, error) { func processDiscordUserInfo(ctx context.Context, code string) (*models.User, error) {
var user *models.User oauth2Token, err := oauth.OAuthProviders.DiscordConfig.Exchange(ctx, code)
oauth2Token, err := oauth.OAuthProviders.TwitterConfig.Exchange(context.TODO(), code, oauth2.SetAuthURLParam("code_verifier", verifier))
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid twitter exchange code: %s", err.Error()) return nil, fmt.Errorf("invalid discord exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.DiscordUserInfoURL, nil)
if err != nil {
log.Debug("Failed to create Discord user info request: ", err)
return nil, fmt.Errorf("error creating Discord user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
}
response, err := client.Do(req)
if err != nil {
log.Debug("Failed to request Discord user info: ", err)
return nil, err
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read Discord user info response body: ", err)
return nil, fmt.Errorf("failed to read Discord response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request Discord user info: ", string(body))
return nil, fmt.Errorf("failed to request Discord user info: %s", string(body))
}
// Unmarshal the response body into a map
responseRawData := make(map[string]interface{})
if err := json.Unmarshal(body, &responseRawData); err != nil {
log.Debug("Failed to unmarshal Discord response: ", err)
return nil, fmt.Errorf("failed to unmarshal Discord response: %s", err.Error())
}
// Safely extract the user data
userRawData, ok := responseRawData["user"].(map[string]interface{})
if !ok {
log.Debug("User data is not in expected format or missing in response")
return nil, fmt.Errorf("user data is not in expected format or missing in response")
}
// Extract the username
firstName, ok := userRawData["username"].(string)
if !ok {
log.Debug("Username is not in expected format or missing in user data")
return nil, fmt.Errorf("username is not in expected format or missing in user data")
}
profilePicture := fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.png", userRawData["id"].(string), userRawData["avatar"].(string))
user := &models.User{
GivenName: &firstName,
Picture: &profilePicture,
}
return user, nil
}
func processTwitterUserInfo(ctx context.Context, code, verifier string) (*models.User, error) {
oauth2Token, err := oauth.OAuthProviders.TwitterConfig.Exchange(ctx, code, oauth2.SetAuthURLParam("code_verifier", verifier))
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return nil, fmt.Errorf("invalid twitter exchange code: %s", err.Error())
} }
client := http.Client{} client := http.Client{}
req, err := http.NewRequest("GET", constants.TwitterUserInfoURL, nil) req, err := http.NewRequest("GET", constants.TwitterUserInfoURL, nil)
if err != nil { if err != nil {
log.Debug("Failed to create Twitter user info request: ", err) log.Debug("Failed to create Twitter user info request: ", err)
return user, fmt.Errorf("error creating Twitter user info request: %s", err.Error()) return nil, fmt.Errorf("error creating Twitter user info request: %s", err.Error())
} }
req.Header = http.Header{ req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)}, "Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
@ -625,19 +707,19 @@ func processTwitterUserInfo(code, verifier string) (*models.User, error) {
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Debug("Failed to request Twitter user info: ", err) log.Debug("Failed to request Twitter user info: ", err)
return user, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
log.Debug("Failed to read Twitter user info response body: ", err) log.Debug("Failed to read Twitter user info response body: ", err)
return user, fmt.Errorf("failed to read Twitter response body: %s", err.Error()) return nil, fmt.Errorf("failed to read Twitter response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request Twitter user info: ", string(body)) log.Debug("Failed to request Twitter user info: ", string(body))
return user, fmt.Errorf("failed to request Twitter user info: %s", string(body)) return nil, fmt.Errorf("failed to request Twitter user info: %s", string(body))
} }
responseRawData := make(map[string]interface{}) responseRawData := make(map[string]interface{})
@ -661,7 +743,7 @@ func processTwitterUserInfo(code, verifier string) (*models.User, error) {
nickname := userRawData["username"].(string) nickname := userRawData["username"].(string)
profilePicture := userRawData["profile_image_url"].(string) profilePicture := userRawData["profile_image_url"].(string)
user = &models.User{ user := &models.User{
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &profilePicture, Picture: &profilePicture,
@ -672,34 +754,133 @@ func processTwitterUserInfo(code, verifier string) (*models.User, error) {
} }
// process microsoft user information // process microsoft user information
func processMicrosoftUserInfo(code string) (*models.User, error) { func processMicrosoftUserInfo(ctx context.Context, code string) (*models.User, error) {
var user *models.User
ctx := context.Background()
oauth2Token, err := oauth.OAuthProviders.MicrosoftConfig.Exchange(ctx, code) oauth2Token, err := oauth.OAuthProviders.MicrosoftConfig.Exchange(ctx, code)
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid google exchange code: %s", err.Error()) return nil, fmt.Errorf("invalid microsoft exchange code: %s", err.Error())
}
// we need to skip issuer check because for common tenant it will return internal issuer which does not match
verifier := oauth.OIDCProviders.MicrosoftOIDC.Verifier(&oidc.Config{
ClientID: oauth.OAuthProviders.MicrosoftConfig.ClientID,
SkipIssuerCheck: true,
})
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
log.Debug("Failed to extract ID Token from OAuth2 token")
return nil, fmt.Errorf("unable to extract id_token")
}
// Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
log.Debug("Failed to verify ID Token: ", err)
return nil, fmt.Errorf("unable to verify id_token: %s", err.Error())
}
user := &models.User{}
if err := idToken.Claims(&user); err != nil {
log.Debug("Failed to parse ID Token claims: ", err)
return nil, fmt.Errorf("unable to extract claims")
} }
verifier := oauth.OIDCProviders.MicrosoftOIDC.Verifier(&oidc.Config{ClientID: oauth.OAuthProviders.MicrosoftConfig.ClientID}) return user, nil
}
// process twitch user information
func processTwitchUserInfo(ctx context.Context, code string) (*models.User, error) {
oauth2Token, err := oauth.OAuthProviders.TwitchConfig.Exchange(ctx, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return nil, fmt.Errorf("invalid twitch exchange code: %s", err.Error())
}
// Extract the ID Token from OAuth2 token. // Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string) rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok { if !ok {
log.Debug("Failed to extract ID Token from OAuth2 token") log.Debug("Failed to extract ID Token from OAuth2 token")
return user, fmt.Errorf("unable to extract id_token") return nil, fmt.Errorf("unable to extract id_token")
} }
verifier := oauth.OIDCProviders.TwitchOIDC.Verifier(&oidc.Config{
ClientID: oauth.OAuthProviders.TwitchConfig.ClientID,
SkipIssuerCheck: true,
})
// Parse and verify ID Token payload. // Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken) idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil { if err != nil {
log.Debug("Failed to verify ID Token: ", err) log.Debug("Failed to verify ID Token: ", err)
return user, fmt.Errorf("unable to verify id_token: %s", err.Error()) return nil, fmt.Errorf("unable to verify id_token: %s", err.Error())
} }
user := &models.User{}
if err := idToken.Claims(&user); err != nil { if err := idToken.Claims(&user); err != nil {
log.Debug("Failed to parse ID Token claims: ", err) log.Debug("Failed to parse ID Token claims: ", err)
return user, fmt.Errorf("unable to extract claims") return nil, fmt.Errorf("unable to extract claims")
}
return user, nil
}
// process roblox user information
func processRobloxUserInfo(ctx context.Context, code, verifier string) (*models.User, error) {
oauth2Token, err := oauth.OAuthProviders.RobloxConfig.Exchange(ctx, code, oauth2.SetAuthURLParam("code_verifier", verifier))
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return nil, fmt.Errorf("invalid roblox exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.RobloxUserInfoURL, nil)
if err != nil {
log.Debug("Failed to create roblox user info request: ", err)
return nil, fmt.Errorf("error creating roblox user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
}
response, err := client.Do(req)
if err != nil {
log.Debug("Failed to request roblox user info: ", err)
return nil, err
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read roblox user info response body: ", err)
return nil, fmt.Errorf("failed to read roblox response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request roblox user info: ", string(body))
return nil, fmt.Errorf("failed to request roblox user info: %s", string(body))
}
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
// log.Info(userRawData)
nameArr := strings.SplitAfterN(userRawData["name"].(string), " ", 2)
firstName := nameArr[0]
lastName := ""
if len(nameArr) == 2 {
lastName = nameArr[1]
}
nickname := userRawData["nickname"].(string)
profilePicture := userRawData["picture"].(string)
email := ""
if val, ok := userRawData["email"]; ok {
email = val.(string)
} else {
email = userRawData["sub"].(string)
}
user := &models.User{
GivenName: &firstName,
FamilyName: &lastName,
Picture: &profilePicture,
Nickname: &nickname,
Email: &email,
} }
return user, nil return user, nil

View File

@ -4,10 +4,12 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/oauth"
@ -38,11 +40,8 @@ func OAuthLoginHandler() gin.HandlerFunc {
} }
if state == "" { if state == "" {
log.Debug("state is empty") log.Debug("state is empty. creating a new state")
c.JSON(400, gin.H{ state = uuid.New().String()
"error": "invalid state",
})
return
} }
var scope []string var scope []string
@ -190,6 +189,24 @@ func OAuthLoginHandler() gin.HandlerFunc {
oauth.OAuthProviders.TwitterConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodTwitter oauth.OAuthProviders.TwitterConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodTwitter
url := oauth.OAuthProviders.TwitterConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("code_challenge", challenge), oauth2.SetAuthURLParam("code_challenge_method", "S256")) url := oauth.OAuthProviders.TwitterConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("code_challenge", challenge), oauth2.SetAuthURLParam("code_challenge_method", "S256"))
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodDiscord:
if oauth.OAuthProviders.DiscordConfig == nil {
log.Debug("Discord OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodDiscord)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
oauth.OAuthProviders.DiscordConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodDiscord
url := oauth.OAuthProviders.DiscordConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodApple: case constants.AuthRecipeMethodApple:
if oauth.OAuthProviders.AppleConfig == nil { if oauth.OAuthProviders.AppleConfig == nil {
log.Debug("Apple OAuth provider is not configured") log.Debug("Apple OAuth provider is not configured")
@ -227,6 +244,42 @@ func OAuthLoginHandler() gin.HandlerFunc {
oauth.OAuthProviders.MicrosoftConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodMicrosoft oauth.OAuthProviders.MicrosoftConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodMicrosoft
url := oauth.OAuthProviders.MicrosoftConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.MicrosoftConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodTwitch:
if oauth.OAuthProviders.TwitchConfig == nil {
log.Debug("Twitch OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodTwitch)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.TwitchConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodTwitch
url := oauth.OAuthProviders.TwitchConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodRoblox:
if oauth.OAuthProviders.RobloxConfig == nil {
log.Debug("RobloxConfig OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodRoblox)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.RobloxConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodRoblox
url := oauth.OAuthProviders.RobloxConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
default: default:
log.Debug("Invalid oauth provider: ", provider) log.Debug("Invalid oauth provider: ", provider)
c.JSON(422, gin.H{ c.JSON(422, gin.H{

View File

@ -24,7 +24,7 @@ func OpenIDConfigurationHandler() gin.HandlerFunc {
"response_types_supported": []string{"code", "token", "id_token"}, "response_types_supported": []string{"code", "token", "id_token"},
"scopes_supported": []string{"openid", "email", "profile"}, "scopes_supported": []string{"openid", "email", "profile"},
"response_modes_supported": []string{"query", "fragment", "form_post", "web_message"}, "response_modes_supported": []string{"query", "fragment", "form_post", "web_message"},
"subject_types_supported": "public", "subject_types_supported": []string{"public"},
"id_token_signing_alg_values_supported": []string{jwtType}, "id_token_signing_alg_values_supported": []string{jwtType},
"claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "role", "gender", "birthdate", "phone_number", "phone_number_verified", "nonce", "updated_at", "created_at", "revoked_timestamp", "login_method", "signup_methods", "token_type"}, "claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "role", "gender", "birthdate", "phone_number", "phone_number_verified", "nonce", "updated_at", "created_at", "revoked_timestamp", "login_method", "signup_methods", "token_type"},
}) })

View File

@ -1,15 +1,44 @@
package handlers package handlers
import ( import (
"net/http"
"github.com/99designs/gqlgen/graphql/playground" "github.com/99designs/gqlgen/graphql/playground"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
) )
// PlaygroundHandler is the handler for the /playground route // PlaygroundHandler is the handler for the /playground route
func PlaygroundHandler() gin.HandlerFunc { func PlaygroundHandler() gin.HandlerFunc {
h := playground.Handler("GraphQL", "/graphql")
return func(c *gin.Context) { return func(c *gin.Context) {
var h http.HandlerFunc
disablePlayground, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePlayGround)
if err != nil {
log.Debug("error while getting disable playground value")
disablePlayground = false
}
// if env set to false, then check if logged in as super admin, if logged in then return graphql else 401 error
// if env set to true, then disabled the playground with 404 error
if !disablePlayground {
if token.IsSuperAdmin(c) {
h = playground.Handler("GraphQL", "/graphql")
} else {
log.Debug("not logged in as super admin")
c.JSON(http.StatusUnauthorized, gin.H{"error": "not logged in as super admin"})
return
}
} else {
log.Debug("playground is disabled")
c.JSON(http.StatusNotFound, gin.H{"error": "playground is disabled"})
return
}
h.ServeHTTP(c.Writer, c.Request) h.ServeHTTP(c.Writer, c.Request)
} }
} }

View File

@ -24,9 +24,13 @@ func RevokeRefreshTokenHandler() gin.HandlerFunc {
}) })
return return
} }
// get client ID
clientID := strings.TrimSpace(reqBody["client_id"]) // kept for backward compatibility // else we expect to be present as header
if clientID == "" {
clientID = gc.Request.Header.Get("x-authorizer-client-id")
}
// get fingerprint hash // get fingerprint hash
refreshToken := strings.TrimSpace(reqBody["refresh_token"]) refreshToken := strings.TrimSpace(reqBody["refresh_token"])
clientID := strings.TrimSpace(reqBody["client_id"])
if clientID == "" { if clientID == "" {
log.Debug("Client ID is empty") log.Debug("Client ID is empty")

View File

@ -105,7 +105,7 @@ func TokenHandler() gin.HandlerFunc {
if codeVerifier == "" && clientSecret == "" { if codeVerifier == "" && clientSecret == "" {
gc.JSON(http.StatusBadRequest, gin.H{ gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_dat", "error": "invalid_data",
"error_description": "The code verifier or client secret is required", "error_description": "The code verifier or client secret is required",
}) })
return return
@ -263,12 +263,10 @@ func TokenHandler() gin.HandlerFunc {
"roles": roles, "roles": roles,
"expires_in": expiresIn, "expires_in": expiresIn,
} }
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token res["refresh_token"] = authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token, authToken.RefreshToken.ExpiresAt) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token, authToken.RefreshToken.ExpiresAt)
} }
gc.JSON(http.StatusOK, res) gc.JSON(http.StatusOK, res)
} }
} }

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