Compare commits

...

305 Commits

Author SHA1 Message Date
ef20829b2e 🐛 Fix did not remove user from program if they didn't pay 2025-04-02 23:15:59 +08:00
e716f5700c 🐛 Fix check punishment expires 2025-04-02 01:33:45 +08:00
b327e08062 DirectAccess in users 2025-03-29 18:04:05 +08:00
6ada023cea :arrow_up Upgrade nexus to fix panic 2025-03-29 16:00:01 +08:00
7389f70128 🛂 Clear auth context cache when logout for security 2025-03-29 15:32:20 +08:00
cc7c96311f ⬆️ Upgrade nexus 2025-03-29 15:22:53 +08:00
d3d7c4b573 ⬆️ Upgrade nexus for bug fixes 2025-03-29 13:39:57 +08:00
3d58ff2391 🔊 Add auth context cache miss log 2025-03-29 13:34:37 +08:00
9cda542b01 ⬆️ Upgrade nexus sdk 2025-03-29 13:29:39 +08:00
e1d7b4e20b ♻️ Rebuilt the otp system with cache 2025-03-29 13:22:39 +08:00
a4c6e9a905 ♻️ Rebuilt cache with the new cache system from nexus 2025-03-29 13:15:20 +08:00
c8e8104d5d Punishments permission override 2025-03-25 23:29:39 +08:00
de28ae027f Notify punishments 2025-03-25 23:20:42 +08:00
a5033c0fb0 Punishment API 2025-03-25 23:10:14 +08:00
742edaa9e2 🐛 Fix set avatar cause group permission leaked to personal 2025-03-25 21:48:51 +08:00
9712119238 🐛 Fix charging for programs 2025-03-25 21:43:49 +08:00
f8a4924d56 🐛 Fix post leave program 2025-03-24 13:11:33 +08:00
fa3561ae58 🐛 Fix place transaction wrongly 2025-03-23 21:52:10 +08:00
548bd6984b 🐛 Wrong experience check 2025-03-23 21:43:41 +08:00
27e1196130 🐛 Fix wrong determine of experience 2025-03-23 21:39:35 +08:00
173ef92c01 Program member benifits 2025-03-23 19:57:19 +08:00
23e5ba432b Programs and members 2025-03-23 19:36:27 +08:00
2ead62ecce 👽 Support wallet multi currency 2025-03-23 17:59:49 +08:00
b18dd5ef82 Permission check 2025-03-23 17:25:38 +08:00
c4758c6217 🐛 Fix large JWT header 2025-03-23 00:07:32 +08:00
f9aa4e3c10 👔 Captcha usage in check in & register
🐛 Fix counting streak bugs etc
2025-03-22 20:00:05 +08:00
34c2d0d5e7 🚚 Remap get account page path 2025-03-21 00:06:03 +08:00
82fa877cad 🐛 Prevent duplicate device token be registered 2025-03-19 21:29:14 +08:00
73683d66e8 :bug [127;2u: Fix grpc handler 2025-03-16 12:14:28 +08:00
ecaace5616 List available realms supports with publics 2025-03-16 12:12:13 +08:00
db018b35f9 👔 Prevent user from deleting their primary contact 2025-03-16 11:54:28 +08:00
45dd50ccba User able to re-send the confirm register email 2025-03-15 22:20:33 +08:00
bcb2cd2f9c GeoIP 2025-03-15 18:06:20 +08:00
32e91e2601 ♻️ Refactored event audit system 2025-03-15 16:37:47 +08:00
35e5eadb05 🐛 Fix realm did not update attachment meta 2025-03-11 00:04:10 +08:00
651a738646 👽 Support new count usage API 2025-03-10 23:15:20 +08:00
e43b9c9341 Broadcast realm was deleted 2025-03-10 22:01:16 +08:00
dfc85c5913 🐛 Fix panic on get status from cache 2025-03-09 13:36:12 +08:00
879cd7991b 🐛 Bug fix on count streak wrongly 2025-03-09 12:24:48 +08:00
84c1a8f836 👔 Optimized check in reward expr 2025-03-09 12:21:45 +08:00
0f9bde2533 👔 Update check in result caluclate expr 2025-03-09 01:30:30 +08:00
1ed9816b12 🐛 Fix key exchange to original client 2025-03-04 20:56:07 +08:00
8ca5573b86 🐛 Fix key exchange wrong client 2025-03-04 13:12:22 +08:00
8c600daf86 🐛 Fix decoding websocket package issue 2025-03-04 00:50:31 +08:00
bdef101bc0 🐛 Fix key exchange missing client id 2025-03-04 00:44:30 +08:00
d6c364ceb3 🐛 Fix key exchange 2025-03-04 00:38:16 +08:00
0c0164c2e8 🎨 Change the api handler name of listing notifications 2025-03-03 20:54:53 +08:00
53d856c7a7 🐛 Fix activating badges 2025-03-02 21:04:24 +08:00
bc31a94c98 🐛 Fix account page did not do auto migrate 2025-03-02 12:48:11 +08:00
0fac34edfb Account page (aka detailed version description) 2025-03-02 12:47:29 +08:00
5979fd5b2c Enchance user profile
💥 Move description from account to profile
2025-03-02 12:00:29 +08:00
4616f7cc93 Wearing badge 2025-03-02 11:52:45 +08:00
378c60cef8 PaKex Key exchange 2025-03-01 15:22:51 +08:00
af2bc29068 🐛 Fix db cleaner 2025-03-01 14:14:00 +08:00
07d9335180 🗑️ Clean up code 2025-03-01 14:12:29 +08:00
e737d31fd9 🐛 Fix leave realm 2025-02-22 00:27:26 +08:00
701d9b1929 🐛 Trying to fix notify again 2025-02-21 23:11:50 +08:00
e2e0cdc9b0 🐛 Fix leave member 2025-02-21 23:08:41 +08:00
e65713a5b2 🐛 Fix router stacking issue 2025-02-21 22:51:48 +08:00
816ee07947 🐛 Fix cannot add notification subscription 2025-02-21 22:39:55 +08:00
6db14a71af 👔 Prevent user from creating account with name that only includes number 2025-02-20 23:04:03 +08:00
a649f468fc 👔 Change adding realm condition 2025-02-20 22:32:47 +08:00
2f541f2346 Auto handle increment of popularity on realm member changes 2025-02-20 21:58:07 +08:00
bac655c1cf 🗃️ Realm popularity field & sorting communities according to it 2025-02-20 21:42:19 +08:00
a2d033b56c 🐛 Fix delete account at least destroy auth tickets 2025-02-20 21:22:36 +08:00
76cc34c90a 🐛 Try to fix leave own realm bug 2025-02-20 00:03:12 +08:00
160fc79a07 🐛 Fix list user 2025-02-17 23:44:17 +08:00
59e1bf1ddf 🐛 Trying to fix some bugs 2025-02-17 23:36:07 +08:00
badae3aa0d ♻️ Get users in batch supports name 2025-02-17 23:09:46 +08:00
11ca7c0ba6 Introduce batch get user api 2025-02-17 22:30:07 +08:00
09dddf7721 Support localized notification 2025-02-16 23:33:48 +08:00
4fa155cafb 👔 Delete realm will auto delete realm members 2025-02-16 19:44:00 +08:00
1bb73c996b 👽 Upgrade nexus to provide language in userinfo 2025-02-16 18:42:32 +08:00
76682ab27f 🐛 Fix wrong type of notify OTP 2025-02-15 18:33:13 +08:00
64fd2f3cd9 🐛 Fix relationships 2025-02-15 18:32:32 +08:00
497c0692ca 🐛 Fix create duplicate relationship 2025-02-15 16:00:01 +08:00
a08372d4d2 👔 No longer return error when trying add a member who already in the realm 2025-02-10 18:01:32 +08:00
271e61510e 🐛 Fix inconsistent remove member behaviour with messaging 2025-02-10 18:00:00 +08:00
4bf28e5298 Rollback skip push notify 2025-02-04 22:06:30 +08:00
7ff93f1344 🐛 Bug fixes pushing notification skip stragry 2025-02-04 14:07:29 +08:00
a40ab9efdb 🐛 Skip pushing notification when ws is available 2025-02-03 16:44:58 +08:00
bf7c10d195 Support get client by alias 2025-02-02 21:22:53 +08:00
17a99cce61 Third client query toolkit 2025-02-02 21:13:55 +08:00
7dbb552dd2 Third client query grpc endpoint
⬆️ Upgrade protobuf
2025-02-02 21:03:58 +08:00
7d1165b87b 🐛 Fix use wrong config key 2025-02-02 14:33:00 +08:00
eaa8fb5225 ♻️ Replace i18n services with nexus one 2025-02-02 14:28:03 +08:00
ec0048042a 👔 Limit max auth steps to 2 for normal users 2025-02-01 19:00:34 +08:00
62a3168190 Register with preferred language 2025-02-01 18:59:50 +08:00
79844c414b 🐛 Fix email html rendering 2025-02-01 18:27:57 +08:00
cd01657857 🐛 Bug fixes on localization 2025-02-01 18:21:54 +08:00
b68aed0284 🌐 Fully localized notification 2025-02-01 17:59:58 +08:00
d7ee87433f Push email & notification localization 2025-02-01 17:53:21 +08:00
820d96f6b0 Account preferred language 2025-02-01 16:39:14 +08:00
509390b38a Contact API 2025-02-01 14:20:03 +08:00
78d624697c 🐛 Bug fixes 2025-01-31 18:13:42 +08:00
5b1c0784ec 🐛 Fix notify one user api 2025-01-31 18:04:38 +08:00
e115374589 👔 Did not block unconfirmed user login, give them no permission instead 2025-01-29 19:45:10 +08:00
09010d5867 Assocation with Wallet to give daily rewards 2025-01-29 02:37:57 +08:00
867a556204 🐛 Fix wrong type const 2025-01-28 19:41:22 +08:00
71e959805f 🐛 Bug fixes on factor config 2025-01-28 19:33:26 +08:00
fabde24734 🐛 Trying to fix factor respond with null config 2025-01-28 19:27:48 +08:00
123342b13b Impl for totp code, and in app notify factor 2025-01-28 18:20:18 +08:00
dd9a44d126 ♻️ Refactor the get userinfo endpoint for oidc 2025-01-27 19:34:48 +08:00
dc2de65245 List, create & delete auth factor apis 2025-01-27 19:19:31 +08:00
a43e89a5a3 🐛 Bug fixes 2025-01-27 18:17:34 +08:00
0db2967214 🐛 Trying to fix ack message issue 2025-01-27 16:39:14 +08:00
0dbc30d53c 🐛 Unsubscribe the stream after used the stream to validate code 2025-01-27 16:31:15 +08:00
e4ba72725e 🐛 Trying to fix nats: subject does not match consumer 2025-01-27 16:12:09 +08:00
144219f0aa 🐛 Fix nats option durable set more than once 2025-01-27 15:53:34 +08:00
72d0cbb7be 🐛 Fix JetStream invalid stream name 2025-01-27 15:48:09 +08:00
1f75a9e64b ♻️ Use nats jetstream instead of database to store otp 2025-01-27 15:43:24 +08:00
2dac1759d9 Remove the profile field on userinfo 2025-01-25 01:24:55 +08:00
5b516094a0 🐛 Add compability on userinfo 2025-01-22 15:58:56 +08:00
6a4f2b6869 🐛 Fix openid configuration singing algorithm 2025-01-04 18:18:38 +08:00
5bdc59b4af 🚚 Move well known api path 2025-01-04 18:11:09 +08:00
5273498af1 👽 Update magic token callback url 2025-01-04 17:22:11 +08:00
fcd3b56f89 Mark all notification as read api 2025-01-01 11:16:54 +08:00
61976f6c47 👔 Remove auto enable two-step verification 2025-01-01 01:13:16 +08:00
9c0abdb46f Configurable auto signoff duration 2024-12-29 22:21:56 +08:00
f33497d002 Notify all users endpoint 2024-12-25 00:25:32 +08:00
94e55a6d4e 🐛 Fix abuse report have no default status 2024-12-12 00:32:43 +08:00
afc2ec80e9 🐛 Fix list user relative return wrong data 2024-12-11 23:58:19 +08:00
573ccc0478 🐛 Bug fixes on notifications 2024-12-09 23:34:21 +08:00
fdf2d28f51 A separate API to only get notification count 2024-12-09 23:24:12 +08:00
0115e6723a 🐛 Fix missing api endpoint 2024-12-08 22:18:45 +08:00
02f122328a Add cache into querying user 2024-12-08 20:21:40 +08:00
77c543f88e 🐛 Fix random panic 2024-12-05 13:09:52 +08:00
50498976af 🐛 Bug fix directory service wasn't registered 2024-12-03 22:12:10 +08:00
bede9716f1 🔊 Verbose logging at receive broadcasting event 2024-12-03 21:49:41 +08:00
3074cd93d8 🔊 Verbose logging at setting last seen at 2024-12-03 21:24:16 +08:00
87c3b81ad4 🐛 Fix check user exists in realm bug 2024-12-01 12:17:49 +08:00
20ff541ab4 💥 Pagination fetching realm member api
🐛 Fix remove realm member has no memberId path param
2024-12-01 11:32:54 +08:00
e0c9646c98 🐛 Prevent user adding a user twice into a realm 2024-12-01 02:04:57 +08:00
d3a1382711 Add realm member support both account name and id 2024-12-01 01:57:05 +08:00
f8492ea1af 🐛 Hotfix previous commit compile issue 2024-12-01 01:27:42 +08:00
10e9813bea 💥 Move remove member api arguments from body to querystring just as messaging 2024-12-01 01:26:33 +08:00
192a03e9d1 🚚 Move make friendship api 2024-11-30 20:25:56 +08:00
08c639497d Better relationships stauts query 2024-11-30 17:31:35 +08:00
312cf820ab Better check in experience random algorithm 2024-11-30 01:01:54 +08:00
55b3a6f164 💥 Passing relationship api arguments in body instead of querystring 2024-11-30 00:35:11 +08:00
659548a544 Result modifiers in check-in 2024-11-27 22:03:09 +08:00
c16019341f 🚚 Rename daily-sign to check-in 2024-11-27 21:57:10 +08:00
1515e29d5b 🔇 Remove authenticate result logging 2024-11-24 21:57:58 +08:00
668d9805e2 🐛 Fix group permission didn't extend properly 2024-11-23 21:44:14 +08:00
7081cb8885 🔊 Add verbose permission logging 2024-11-23 21:35:57 +08:00
bc2e7df574 🐛 Fix unable get user full perm node 2024-11-23 20:51:57 +08:00
bc5a0ea966 🐛 Fix get user grpc method didn't extend group's permission 2024-11-23 20:24:11 +08:00
f89a2244ea ⬆️ Resum go.mod 2024-11-23 13:30:26 +08:00
bf733da247 Support jwks.json according OIDC stranded 2024-11-23 13:07:49 +08:00
ca9bd7ac14 🐛 Fix notification push batch emitted twice 2024-11-23 12:43:09 +08:00
3f4fe68105 🐛 Fix notifiable is empty when user do not set 2024-11-23 12:31:00 +08:00
7a2ce20460 🔊 Verbose notifying check logging 2024-11-23 12:16:34 +08:00
720f9690f6 🔊 Verbose notifying logging 2024-11-23 00:34:53 +08:00
663b563b39 💥 Remove deprecated subscription API 2024-11-23 00:28:42 +08:00
8e1ed67c48 🐛 Fix get user info query statement bug 2024-11-17 01:05:11 +08:00
4695a617d7 Allow access user info via numeric id 2024-11-17 00:17:56 +08:00
f91be512f3 Allow get realm by numeric auto increment id 2024-11-16 21:36:34 +08:00
40b64a3559 🐛 Fix auth cache 2024-11-03 21:31:32 +08:00
808e7998dd Authkit can get user 2024-11-03 01:47:50 +08:00
40a0403521 Authkit support check user related permission 2024-11-02 12:48:32 +08:00
b4da8ce78b ♻️ Replace nil context with context.Background in authkit 2024-11-02 12:48:17 +08:00
d1f1183beb 🐛 Fix grpc namespace conflict 2024-10-31 22:40:10 +08:00
c3619f6d25 Realm operations now available in authkit 2024-10-31 22:08:51 +08:00
2b4f71e732 Add list relative method into authkit 2024-10-31 21:35:21 +08:00
39ac016b46 🧑‍💻 Improve DX by extending authkit 2024-10-31 21:26:25 +08:00
8326c716e3 Support users related rpc calls 2024-10-31 21:07:53 +08:00
8ff2648e4c 🚚 Update package name from Hypdrogen to Hypernet 2024-10-31 20:38:50 +08:00
e6f6389eb0 🔀 Merge rolled back changes 2024-10-31 00:23:53 +08:00
2fcc784bc4 Revert "♻️ Move models.Account to sec.UserInfo" for a better solution
This reverts commit 8fbb7960
2024-10-31 00:17:53 +08:00
69c6ac6581 🚚 Auth kit and parser of nexus userinfo token 2024-10-31 00:15:25 +08:00
2d322f070a 🐛 Refresh token bug 2024-10-30 23:59:16 +08:00
d7b1913bc3 🐛 Fix check perm panic 2024-10-30 23:28:26 +08:00
8fbb79607b ♻️ Move models.Account to sec.UserInfo 2024-10-30 23:26:13 +08:00
14baee03fe 🚚 Move http server package 2024-10-27 12:50:07 +08:00
7a0756a5e8 ♻️ All parts into nexus 2024-10-27 00:07:59 +08:00
4c44af74b5 ♻️ Refactored more modules into nexus 2024-10-24 00:46:59 +08:00
b4fb7b53af ♻️ Remove most of the dealer deps and move to nexus 2024-10-24 00:13:16 +08:00
e412d5e742 🐛 Trying to fix panic 2024-10-18 23:10:33 +08:00
e83e92b054 🐛 Fix notification order... again 2024-10-16 21:07:53 +08:00
10d977ab23 🐛 Fix notification order 2024-10-16 20:57:08 +08:00
29629cead6 🐛 Bug fixes on compile time 2024-10-16 01:06:27 +08:00
df8058e794 👽 Support better metadata encoder / decoder 2024-10-16 01:01:39 +08:00
be32ad1522 🐛 Bug fixes in non-cached notification preferences 2024-10-14 23:45:28 +08:00
8e3ce09734 New login alert 2024-10-14 22:28:30 +08:00
974c5d9700 More event logs point 2024-10-14 22:24:27 +08:00
a4a139c56e Implement event recorder grpc 2024-10-14 22:09:45 +08:00
06bd632f37 🐛 Bug fixes on settings auth preferences 2024-10-13 14:07:49 +08:00
68b4390edf 🐛 Bug fixes on multi-factors based authentication 2024-10-13 14:02:48 +08:00
6ea4850459 ♻️ Improve notifications mark read system 2024-10-13 13:00:51 +08:00
bee9799415 Allow user view and remove notification subscriptions 2024-10-13 12:53:47 +08:00
821e0c3e60 🐛 Trying to prevent send same notification to the same user in batch 2024-10-13 12:46:14 +08:00
39c3799d82 ♻️ Single table to store auth preferences 2024-10-13 12:36:51 +08:00
9287e6c5cc Auth config to limit auth steps 2024-10-13 01:45:08 +08:00
0f18c6ff16 🐛 Hotfix panic when getting auth context from cache 2024-10-07 00:53:23 +08:00
94c15f58ec Allow user block user initially 2024-09-26 22:09:39 +08:00
cb3cc3f540 🐛 Fix check delete account issue 2024-09-25 20:18:11 +08:00
cec234bc53 🐛 Fix wrong condition on check auth context is cached 2024-09-22 14:32:55 +08:00
27b625e0c8 Bring cache into preference notification 2024-09-22 14:04:56 +08:00
bbceb65dbf ♻️ Refactored cache system 2024-09-22 13:13:05 +08:00
648f10b25a 🐛 Fix batch notification prefs querying issue 2024-09-22 01:27:57 +08:00
0a362dadec 🐛 Try to fix notifiable check 2024-09-21 23:47:04 +08:00
9174767d80 🐛 Fix notifiable check 2024-09-21 22:54:54 +08:00
53c6b2a636 List other user daily records 2024-09-21 22:50:01 +08:00
10e4a67835 👔 Modify report abuse condition 2024-09-21 19:30:06 +08:00
ba32925b58 🐛 Tryin' to fix notifications issue 2024-09-20 21:55:25 +08:00
3031f61ea4 Account deletion 2024-09-19 22:18:22 +08:00
02bffc062f Report status update with a message 2024-09-19 21:06:35 +08:00
817c60c4e0 Abuse report system 2024-09-19 21:02:21 +08:00
5bcce9d44e 🐛 Fix cannot determine correct notifiable in batch 2024-09-17 21:15:34 +08:00
7a9e31fd86 Supply access policy in grpc get realms 2024-09-17 16:54:45 +08:00
4b4eb3e581 New query method on querying realm members 2024-09-17 16:46:58 +08:00
5203234c4a 🐛 Fix bugs in updating notification preferences 2024-09-17 15:56:41 +08:00
f287e295e5 Notification preferences 2024-09-17 14:50:05 +08:00
df9fb0a92a ♻️ Better sign in flow 2024-09-16 02:37:02 +08:00
ea3d6c20d3 👽 Support new version dealer devkit 2024-09-11 23:04:14 +08:00
313f6b8d4f Realm avatar, banner and access policy 2024-09-11 22:31:30 +08:00
582e6cdb61 🐛 Fix daily sign random panic 2024-09-08 10:39:08 +08:00
d4ed047ed7 🐛 Fix daily sign batch list query issue 2024-09-07 19:12:42 +08:00
3ce5ef6617 🐛 Fix date formatting issue on daily signing 2024-09-02 22:48:06 +08:00
a23c51f483 🐛 Fix auto maintain range missing models 2024-09-02 22:12:25 +08:00
bfddfa2201 Can get today's daily sign record 2024-09-02 20:07:19 +08:00
bee5b676fa 🐛 Fix daily check issue 2024-09-01 16:51:13 +08:00
99f8e4c891 Daily signs 2024-09-01 16:38:09 +08:00
f240226563 🐛 Fix query statement column issue 2024-08-26 00:53:13 +08:00
d205a41614 🐛 Fix bot related key api issue 2024-08-26 00:44:10 +08:00
51a53a25da 🐛 Fix preloading issue 2024-08-26 00:33:57 +08:00
1bdeba58bc Preload api key's ticket 2024-08-25 23:43:15 +08:00
46c7b6dff1 🐛 Fix api key missing account id 2024-08-25 23:15:29 +08:00
b097dab9e1 🐛 Fix api key wasn't in auto maintain range 2024-08-25 23:12:33 +08:00
52c06d9826 🐛 Fix path parameters misplaced 2024-08-25 21:35:22 +08:00
4b0910f6eb 🐛 Fix bot related bot key apis path error 2024-08-25 21:34:54 +08:00
2b2e7b5a89 Bot related bot key apis 2024-08-25 20:51:58 +08:00
476ef57236 🐛 Fix compare perm node function 2024-08-25 18:36:19 +08:00
0f2b45352c 🐛 Fix compare perm node panic 2024-08-25 18:08:37 +08:00
a6b0170b1d 🐛 Fix query issue 2024-08-25 17:16:22 +08:00
876cfa9956 Pagination bots api 2024-08-25 17:03:06 +08:00
7a585ec5b9 Return affiliated to and automated by in userinfo grpc call 2024-08-25 00:08:06 +08:00
b22657d09f Bots aka. automated accounts 2024-08-24 23:49:19 +08:00
8f61253bd3 Bot token aka. API token 2024-08-24 20:28:10 +08:00
516f5593de 🗃️ Update modeling 2024-08-24 15:17:26 +08:00
5e76fa07b7 👽 Use hyper's NetworkPackage instead of own UnifiedCommand 2024-08-24 14:29:36 +08:00
31fcfa2bf5 👽 Use pointer user id in stream request
⬆️ Re-sum go mod
2024-08-23 19:42:30 +08:00
9d075f862c ⬆️ Upgrade dealer package 2024-08-23 19:36:30 +08:00
0285b82ca2 👽 Change avatar and banner id to string 2024-08-18 22:08:58 +08:00
dd3586b241 ⬆️ Implement list user relative grpc function 2024-08-14 22:50:03 +08:00
bdcc8de59b 🐛 Fix internal token audience update isn't fully applied 2024-08-13 15:16:02 +08:00
0ee3f1bc97 Use capital to deal with links 2024-08-13 10:58:59 +08:00
a97615b6c7 🗑️ Totally removed frontend 2024-08-12 21:16:48 +08:00
fe5e9c66b9 🔀 Merge pull request '♻️ Use Capital (the new portal website) instead of embed frontend)' (#6) from refactor/use-capital-as-frontend into master
Reviewed-on: Hydrogen/Passport#6
2024-08-12 13:07:13 +00:00
e4d73b1d31 ♻️ Separate application domain and token issuer 2024-08-12 20:58:20 +08:00
142e7c3434 🗑️ Clean up code 2024-08-12 20:53:03 +08:00
7a072988ce 🐛 Fix validate condition issue 2024-08-01 23:55:33 +08:00
817c573048 🐛 Fix batch get userinfo 2024-08-01 12:47:19 +08:00
c51af61820 Better account name validation 2024-08-01 12:21:34 +08:00
688d026d75 Support batch get userinfo by username 2024-08-01 12:11:57 +08:00
ed13d62110 👽 Fix avatar cannot be updated due to attachment api updated 2024-07-31 14:16:13 +08:00
d2beaa67b4 Able get multiple userinfo in one request 2024-07-30 20:28:38 +08:00
e97f3b6047 🐛 Fix search account query 2024-07-30 19:39:53 +08:00
57b4b314fe Search accounts w/ username or nick 2024-07-30 18:20:45 +08:00
7c09138ef7 Present nonce in id token 2024-07-28 22:30:51 +08:00
6ef46d984d Better id token in oidc 2024-07-28 20:04:22 +08:00
94aed49092 Present azp in token 2024-07-28 19:50:49 +08:00
ebbfd7450c 🐛 Fix user nick condtion doesn't match registering 2024-07-27 23:29:10 +08:00
c458123d9e 🐛 Fix permissions in groups 2024-07-24 18:09:18 +08:00
7737d9fd08 Default user group 2024-07-24 17:33:05 +08:00
f113719e79 Account groups 2024-07-24 17:23:44 +08:00
2d181bfdaf 🐛 Fix endpoint conflict again... 2024-07-24 00:04:21 +08:00
24395b698e 🐛 Fix conflict on endpoints 2024-07-24 00:02:30 +08:00
cb0fefecfb More relationship APIs 2024-07-23 23:50:05 +08:00
3cab122b76 🐛 Fix cannot clear notifications 2024-07-23 22:04:52 +08:00
e56c48362d 🚚 Update API endpoints 2024-07-23 21:25:13 +08:00
d08b7a273d 🐛 Fix push notification to wrong person 2024-07-21 22:52:24 +08:00
6350ec1e43 ♻️ Use dealer postman instead of built-in feature to deliver email and notify 2024-07-21 14:22:54 +08:00
27d501d7a7 Support stream controller event emit 2024-07-20 19:58:54 +08:00
6643d31b1a Show invisible is enabled in api 2024-07-20 15:14:50 +08:00
b3bb7cb582 Provide API to try out our newest notification feature 2024-07-19 23:35:15 +08:00
8fcaf99103 Notification with avatar(only in APNs) and picture 2024-07-19 23:27:58 +08:00
282a0891d0 Optimize batch notification speed 2024-07-17 14:04:55 +08:00
7436d4b2cc 🐛 Fix concurrent write and read auth context cache 2024-07-17 13:27:16 +08:00
7b9aebd5d7 🐛 Fix attachment url issue 2024-07-16 20:11:08 +08:00
cef0a552f4 ⬆️ Configured for support dealer 2024-07-16 18:09:18 +08:00
c912271610 Make log more quiet 2024-07-16 14:15:21 +08:00
1ce04f7067 🔀 Merge pull request '♻️ 转移到 Dealer' (#5) from refactor/dealer into master
Reviewed-on: Hydrogen/Passport#5
2024-07-16 05:03:45 +00:00
14bcea02d0 ⬆️ Upgrade dealer 2024-07-16 11:09:16 +08:00
4795da84f9 🐛 Fix model relation issue 2024-07-16 00:05:09 +08:00
a8d919dc5b ♻️ Refactored relation system
⬆️ Support new realm & relation api
2024-07-16 00:02:28 +08:00
4143a7b2c8 ⬆️ Support new auth api 2024-07-15 00:46:36 +08:00
69fb9531cb ♻️ Basiclly moved to Dealer from Consul 2024-07-15 00:01:17 +08:00
a60be78ce6 🐛 Fix oauth ticket need mfa 2024-07-14 14:28:16 +08:00
0e7e7e6d55 🐛 Fix recycle account error 2024-07-12 13:31:39 +08:00
d405b34747 🐛 Bug fixes on cron jobs 2024-07-12 11:25:41 +08:00
e049b2c566 🐛 Fix suspend account check 2024-07-12 00:35:45 +08:00
681eb3cf7f Suspended account 2024-07-11 18:34:05 +08:00
e5bdb23c09 🐛 Fix push notifications wont work 2024-07-10 17:38:39 +08:00
77c1a029bd 🐛 Bug fixes 2024-07-05 00:05:15 +08:00
f7063fc109 💩 Remove mis-imported cgo 2024-07-04 23:55:06 +08:00
3216547d46 🚨 Fix ts lint issue 2024-07-04 23:36:33 +08:00
8c7f255473 View auth factors in admin panel 2024-07-04 23:12:43 +08:00
13c26486b6 🐛 Fix clear function doesn't real clear items in slice 2024-07-04 23:08:22 +08:00
46468fae5f Admin panel & users, users' permissions management 2024-07-04 22:58:34 +08:00
da15c72fb3 Admin check users' auth factor 2024-07-03 23:33:22 +08:00
182a389180 Admin notify one user 2024-07-03 23:07:59 +08:00
74819c1c2b Admin force confirm account 2024-07-03 23:01:20 +08:00
8c89d89382 Optimized audit, event logging system
 Audit logs
 Admin edit user permissions
2024-07-03 22:57:17 +08:00
5200e00c9b ♻️ Optimized the initial permission system 2024-07-03 22:36:42 +08:00
240 changed files with 11671 additions and 21226 deletions

5
.gitignore vendored
View File

@@ -1,2 +1,7 @@
/dist
/uploads
/keys
geoip.mmdb
.DS_Store

12
.idea/Passport.iml generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="animate.css" level="application" />
<orderEntry type="library" name="tailwindcss" level="application" />
<orderEntry type="library" name="@tailwindcss/typography" level="application" />
</component>
</module>

View File

@@ -1,59 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="GO-241.18034.61">
<data-source name="hy_passport@localhost" uuid="74bcf3ef-a2b9-435b-b9e5-f32902a33b25">
<database-info product="PostgreSQL" version="16.3 (Homebrew)" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.6.0" dbms="POSTGRES" exact-version="16.3" exact-driver-version="42.6">
<identifier-quote-string>&quot;</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="lower" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>postgres</user-name>
<schema-mapping>
<introspection-scope>
<node negative="1">
<node kind="database" qname="@">
<node kind="schema" qname="@" />
</node>
<node kind="database" qname="hy_passport" />
</node>
</introspection-scope>
</schema-mapping>
</data-source>
<data-source name="passport@id.solsynth.dev" uuid="723637bc-6ce3-4bbe-afb3-d88730c75a1b">
<database-info product="PostgreSQL" version="16.2 (Ubuntu 16.2-1.pgdg22.04+1)" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.6.0" dbms="POSTGRES" exact-version="16.2" exact-driver-version="42.6">
<identifier-quote-string>&quot;</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="lower" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>postgres</user-name>
<schema-mapping>
<introspection-scope>
<node kind="database" qname="@">
<node kind="schema" qname="@" />
</node>
</introspection-scope>
</schema-mapping>
</data-source>
</component>
</project>

19
.idea/dataSources.xml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="hy_passport@localhost" uuid="74bcf3ef-a2b9-435b-b9e5-f32902a33b25">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/hy_passport</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="passport@id.solsynth.dev" uuid="723637bc-6ce3-4bbe-afb3-d88730c75a1b">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://id.solsynth.dev:5432/passport</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
#n:public
!<md> [41831, 0, null, null, -2147483648, -2147483648]

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
#n:public
!<md> [7430, 0, null, null, -2147483648, -2147483648]

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{@tailwindcss/typography, animate.css, tailwindcss}" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Passport.iml" filepath="$PROJECT_DIR$/.idea/Passport.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

189
.idea/workspace.xml generated
View File

@@ -1,189 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="ALL" />
</component>
<component name="ChangeListManager">
<list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":sparkles: Reset password APIs">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/server/api/accounts_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/api/accounts_api.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/server/api/index.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/server/api/index.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/services/accounts.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/accounts.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/web/src/views/flow/confirm.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/flow/confirm.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/web/src/views/flow/password-reset.vue" beforeDir="false" afterPath="$PROJECT_DIR$/web/src/views/flow/password-reset.vue" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Go File" />
</list>
</option>
</component>
<component name="GOROOT" url="file:///opt/homebrew/opt/go/libexec" />
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="features/kex" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="ProjectErrors" />
</component>
<component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 6
}</component>
<component name="ProjectId" id="2fLXu43fjlLYVIGNrhGhOgBFq2O" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"DefaultGoTemplateProperty": "Go File",
"Go Build.Backend.executor": "Run",
"Go 构建.Backend.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.go.formatter.settings.were.checked": "true",
"RunOnceActivity.go.migrated.go.modules.settings": "true",
"RunOnceActivity.go.modules.automatic.dependencies.download": "true",
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
"git-widget-placeholder": "master",
"go.import.settings.migrated": "true",
"go.sdk.automatically.set": "true",
"last_opened_file_path": "/Users/littlesheep/Documents/Projects/Hydrogen/Passport/web/src/views",
"node.js.detected.package.eslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"run.code.analysis.last.selected.profile": "pProject Default",
"settings.editor.selected.configurable": "preferences.pluginManager",
"ts.external.directory.path": "/Users/littlesheep/Documents/Projects/Hydrogen/Passport/web/node_modules/typescript/lib",
"vue.rearranger.settings.migration": "true"
},
"keyToStringList": {
"DatabaseDriversLRU": [
"postgresql"
]
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/web/src/views" />
<recent name="$PROJECT_DIR$/pkg/internal/server/api" />
<recent name="$PROJECT_DIR$/web" />
<recent name="$PROJECT_DIR$/pkg/services" />
<recent name="$PROJECT_DIR$/pkg/server/ui" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/web/src/views/flow" />
<recent name="$PROJECT_DIR$/pkg/internal/server/exts" />
<recent name="$PROJECT_DIR$/pkg/internal/server/api" />
<recent name="$PROJECT_DIR$/pkg/internal" />
<recent name="$PROJECT_DIR$/pkg" />
</key>
</component>
<component name="RunAnythingCache">
<myKeys>
<visibility group="Grunt" flag="true" />
<visibility group="Gulp" flag="true" />
<visibility group="HTTP 请求" flag="true" />
<visibility group="Recent projects" flag="true" />
<visibility group="Run configurations" flag="true" />
<visibility group="npm" flag="true" />
<visibility group="yarn" flag="true" />
</myKeys>
</component>
<component name="RunManager">
<configuration name="Backend" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="Passport" />
<working_directory value="$PROJECT_DIR$" />
<kind value="FILE" />
<package value="git.solsynth.dev/hydrogen/passport" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/pkg/main.go" />
<output_directory value="$PROJECT_DIR$/dist" />
<method v="2" />
</configuration>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-gosdk-33c477a475b1-e0158606a674-org.jetbrains.plugins.go.sharedIndexes.bundled-GO-241.18034.61" />
<option value="bundled-js-predefined-1d06a55b98c1-0b3e54e931b4-JavaScript-GO-241.18034.61" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="UnknownFeatures">
<option featureType="dependencySupport" implementationName="javascript:npm:prettier" />
<option featureType="dependencySupport" implementationName="executable:docker" />
<option featureType="dependencySupport" implementationName="javascript:npm:unocss" />
<option featureType="dependencySupport" implementationName="javascript:npm:vite" />
<option featureType="dependencySupport" implementationName="executable:kubectl" />
<option featureType="dependencySupport" implementationName="javascript:npm:vue" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State>
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="refactor/v2" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value=":bug: Authenticate wrong payload hotfix" />
<MESSAGE value=":sparkles: Can pick up mfa request" />
<MESSAGE value=":sparkles: Status system" />
<MESSAGE value=":bug: Fix status expired in cache" />
<MESSAGE value=":bug: Fix online condition" />
<MESSAGE value=":sparkles: Last seen at" />
<MESSAGE value=":sparkles: Edit, delete current status" />
<MESSAGE value=":bug: Fix clear status affected the statutes cleared before" />
<MESSAGE value=":sparkles: Get self-current status API" />
<MESSAGE value=":sparkles: Get myself current status API" />
<MESSAGE value=":bug: Fix miscall function" />
<MESSAGE value=":bug: Fix ws security blocked" />
<MESSAGE value=":bug: Invisible status is visible to others" />
<MESSAGE value=":ambulance: Fix nil pointer panic" />
<MESSAGE value=":bug: Not supposed to appear to status at the same time" />
<MESSAGE value=":ambulance: Fix getting user panic again..." />
<MESSAGE value=":bug: Fix status validation issue" />
<MESSAGE value=":bug: Fix bugs in status" />
<MESSAGE value=":bug: Fix status query condition" />
<MESSAGE value=":bug: Fix disturbable condition" />
<MESSAGE value=":sparkles: Admin notify all API" />
<MESSAGE value=":bug: Fix request body validation" />
<MESSAGE value=":bug: Fix API mapping issue" />
<MESSAGE value=":recycle: Improve notify API" />
<MESSAGE value=":sparkles: Reset password APIs" />
<option name="LAST_COMMIT_MESSAGE" value=":sparkles: Reset password APIs" />
</component>
<component name="VgoProject">
<settings-migrated>true</settings-migrated>
</component>
</project>

View File

@@ -1,23 +1,16 @@
# Building Backend
FROM golang:alpine as passport-server
RUN apk add nodejs npm
WORKDIR /source
COPY . .
WORKDIR /source/web
RUN npm install
RUN npm run build
WORKDIR /source
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/main.go
# Runtime
FROM golang:alpine
COPY --from=passport-server /dist /passport/server
COPY --from=passport-server /source/web/dist /passport/web
COPY ./templates /templates
COPY ./locales /locales
EXPOSE 8444

160
go.mod
View File

@@ -1,128 +1,100 @@
module git.solsynth.dev/hydrogen/passport
module git.solsynth.dev/hypernet/passport
go 1.21.6
toolchain go1.22.1
go 1.23.2
require (
firebase.google.com/go v3.13.0+incompatible
git.solsynth.dev/hydrogen/paperclip v0.0.0-20240622051057-0f56dba45745
github.com/go-playground/validator/v10 v10.17.0
github.com/gofiber/contrib/websocket v1.3.0
github.com/gofiber/fiber/v2 v2.52.4
github.com/golang-jwt/jwt/v5 v5.2.0
git.solsynth.dev/hypernet/nexus v0.0.0-20250330063116-4350d197f9c6
git.solsynth.dev/hypernet/paperclip v0.0.0-20250310151112-1d866f317f47
git.solsynth.dev/hypernet/pusher v0.0.0-20250216145944-5fb769823a88
git.solsynth.dev/hypernet/wallet v0.0.0-20250323095812-468cd655f886
github.com/fatih/color v1.18.0
github.com/go-playground/validator/v10 v10.22.1
github.com/goccy/go-json v0.10.3
github.com/gofiber/contrib/fiberzerolog v1.0.2
github.com/gofiber/fiber/v2 v2.52.6
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/hashicorp/consul/api v1.29.1
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/json-iterator/go v1.1.12
github.com/mbobakov/grpc-consul-resolver v1.5.3
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/oschwald/geoip2-golang v1.11.0
github.com/pquerna/otp v1.4.0
github.com/robfig/cron/v3 v3.0.1
github.com/rs/zerolog v1.31.0
github.com/samber/lo v1.39.0
github.com/sideshow/apns2 v0.23.0
github.com/spf13/viper v1.18.2
github.com/rs/zerolog v1.33.0
github.com/samber/lo v1.47.0
github.com/spf13/viper v1.19.0
github.com/sujit-baniya/flash v0.1.8
golang.org/x/crypto v0.24.0
golang.org/x/text v0.16.0
google.golang.org/api v0.183.0
google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.2
gorm.io/datatypes v1.2.0
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.6
golang.org/x/crypto v0.33.0
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.4
gorm.io/datatypes v1.2.4
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.12
)
require (
cloud.google.com/go v0.114.0 // indirect
cloud.google.com/go/auth v0.5.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/firestore v1.15.0 // indirect
cloud.google.com/go/iam v1.1.8 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
cloud.google.com/go/storage v1.41.0 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/fasthttp/websocket v1.5.8 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/form v3.1.4+incompatible // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/eko/gocache/lib/v4 v4.2.0 // indirect
github.com/eko/gocache/store/redis/v4 v4.2.2 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.1 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // 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/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/nats-io/nats.go v1.37.0 // indirect
github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nicksnyder/go-i18n/v2 v2.5.0 // indirect
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.52.3 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/redis/go-redis/v9 v9.7.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tinylib/msgp v1.2.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
github.com/valyala/fasthttp v1.59.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.2 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
)
replace git.solsynth.dev/hydrogen/bus => ../Bus

597
go.sum
View File

@@ -1,574 +1,303 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY=
cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E=
cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw=
cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0=
cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80=
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
git.solsynth.dev/hydrogen/paperclip v0.0.0-20240622051057-0f56dba45745 h1:40BUsQMNXjqHyytkyF9py1HjTAWlRgO6R57YXUrHNy4=
git.solsynth.dev/hydrogen/paperclip v0.0.0-20240622051057-0f56dba45745/go.mod h1:FsQGSLTl0gvo+9Jmbot02S72suyF9tFTrzDj70Xhifo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329072130-f113ae6cbaf7 h1:0OitkUQJ3hrobm71UHETLB9N6jTgm6jKTeGRJuBI/6E=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329072130-f113ae6cbaf7/go.mod h1:5tk62VQ1DcbR0EAN2jAOqYxHiegUPEC805JlfQ/G19I=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329072729-4a08fd8f1c46 h1:oH2jq7ZG5cslCULUMWqv4dS/YNvd+Xcuv4rBPj0uGA8=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329072729-4a08fd8f1c46/go.mod h1:5tk62VQ1DcbR0EAN2jAOqYxHiegUPEC805JlfQ/G19I=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329075932-d5422ab5b04c h1:XgdTgJxSAQuCbiG15hN5pY6chzcz8sX3Onm2itS+Ufs=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329075932-d5422ab5b04c/go.mod h1:5tk62VQ1DcbR0EAN2jAOqYxHiegUPEC805JlfQ/G19I=
git.solsynth.dev/hypernet/nexus v0.0.0-20250330063116-4350d197f9c6 h1:K7dYn7/rAXry3dSghFVd4aHOt2+8nTbhdav6DTW8sP8=
git.solsynth.dev/hypernet/nexus v0.0.0-20250330063116-4350d197f9c6/go.mod h1:5tk62VQ1DcbR0EAN2jAOqYxHiegUPEC805JlfQ/G19I=
git.solsynth.dev/hypernet/paperclip v0.0.0-20250310151112-1d866f317f47 h1:fvu+bNKPTNtQocssnKbEZ66MqR0iBfAxY3HwlqnmYyE=
git.solsynth.dev/hypernet/paperclip v0.0.0-20250310151112-1d866f317f47/go.mod h1:jvxq2qftz2v72x+24+cTFJdQKr9eHQTdk3KVR7cx36s=
git.solsynth.dev/hypernet/pusher v0.0.0-20250216145944-5fb769823a88 h1:2HEENe9KUrdaJeNBzx9lsuXQGyzWqCgnLTKQnr8xFr8=
git.solsynth.dev/hypernet/pusher v0.0.0-20250216145944-5fb769823a88/go.mod h1:ildzMtLagNsLK0Rkw4Hgk2TrrwqZnjwJIUx0MNZwcDY=
git.solsynth.dev/hypernet/wallet v0.0.0-20250323095812-468cd655f886 h1:rVssXF8jZ64ctAfzlCgIgF22NCT9VAPAVxrwlcItx3s=
git.solsynth.dev/hypernet/wallet v0.0.0-20250323095812-468cd655f886/go.mod h1:rmomNGQ6RBSp8TpZGA8tFr5M54AL2NADJ/1n0MfrIRM=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/eko/gocache/lib/v4 v4.2.0 h1:MNykyi5Xw+5Wu3+PUrvtOCaKSZM1nUSVftbzmeC7Yuw=
github.com/eko/gocache/lib/v4 v4.2.0/go.mod h1:7ViVmbU+CzDHzRpmB4SXKyyzyuJ8A3UW3/cszpcqB4M=
github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0=
github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form v3.1.4+incompatible h1:lvKiHVxE2WvzDIoyMnWcjyiBxKt2+uFJyZcPYWsLnjI=
github.com/go-playground/form v3.1.4+incompatible/go.mod h1:lhcKXfTuhRtIZCIKUeJ0b5F207aeQCPbZU09ScKjwWg=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/contrib/websocket v1.3.0 h1:XADFAGorer1VJ1bqC4UkCjqS37kwRTV0415+050NrMk=
github.com/gofiber/contrib/websocket v1.3.0/go.mod h1:xguaOzn2ZZ759LavtosEP+rcxIgBEE/rdumPINhR+Xo=
github.com/gofiber/contrib/fiberzerolog v1.0.2 h1:LMa/luarQVeINoRwZLHtLQYepLPDIwUNB5OmdZKk+s8=
github.com/gofiber/contrib/fiberzerolog v1.0.2/go.mod h1:aTPsgArSgxRWcUeJ/K6PiICz3mbQENR1QOR426QwOoQ=
github.com/gofiber/fiber/v2 v2.36.0/go.mod h1:tgCr+lierLwLoVHHO/jn3Niannv34WRkQETU8wiL9fQ=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/hashicorp/consul/api v1.29.1 h1:UEwOjYJrd3lG1x5w7HxDRMGiAUPrb3f103EoeKuuEcc=
github.com/hashicorp/consul/api v1.29.1/go.mod h1:lumfRkY/coLuqMICkI7Fh3ylMG31mQSRZyef2c5YvJI=
github.com/hashicorp/consul/proto-public v0.6.1 h1:+uzH3olCrksXYWAYHKqK782CtK9scfqH+Unlw3UHhCg=
github.com/hashicorp/consul/proto-public v0.6.1/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mbobakov/grpc-consul-resolver v1.5.3 h1:xL7nJm8qCvxgHMqlnF4naXruBUoHqfUWORl3UmwKByU=
github.com/mbobakov/grpc-consul-resolver v1.5.3/go.mod h1:0wN8+McBocuk5mO9xlAfrmBSothm7sps43bFGubg0m4=
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nicksnyder/go-i18n/v2 v2.5.0 h1:3wH1gpaekcgGuwzWdSu7JwJhH9Tk87k1ezt0i1p2/Is=
github.com/nicksnyder/go-i18n/v2 v2.5.0/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA=
github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sideshow/apns2 v0.23.0 h1:lpkikaZ995GIcKk6AFsYzHyezCrsrfEDvUWcWkEGErY=
github.com/sideshow/apns2 v0.23.0/go.mod h1:7Fceu+sL0XscxrfLSkAoH6UtvKefq3Kq1n4W3ayQZqE=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/sujit-baniya/flash v0.1.8 h1:BwcrybCatPU30VMA9IBA5q3ZE0VSr5c7qTqwZrSvyRI=
github.com/sujit-baniya/flash v0.1.8/go.mod h1:kmlAIkLDMlLshEeeE6fETEW8kSOopKN5WA3KXLmS/U0=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20170512130425-ab89591268e0/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE=
google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240604185151-ef581f913117 h1:HCZ6DlkKtCDAtD8ForECsY3tKuaR+p4R3grlK80uCCc=
google.golang.org/genproto v0.0.0-20240604185151-ef581f913117/go.mod h1:lesfX/+9iA+3OdqeCpoDddJaNxVB1AB6tD7EfqMmprc=
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
gorm.io/datatypes v1.2.4 h1:uZmGAcK/QZ0uyfCuVg0VQY1ZmV9h1fuG0tMwKByO1z4=
gorm.io/datatypes v1.2.4/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

18
locales/en-US.json Normal file
View File

@@ -0,0 +1,18 @@
{
"subjectLoginOneTimePassword": "Login verification code",
"shortBodyLoginOneTimePassword": "%s is your login verification code. It will expires in 30 minutes.",
"subjectConfirmRegistration": "Confirm your registration",
"subjectResetPassword": "Reset your password",
"subjectDeleteAccount": "Confirm your account deletion",
"subjectLoginAlert": "Login alert",
"shortBodyLoginAlert": "Your account got logged in from %s. If it's not your device, please deal with it immediately.",
"subjectAbuseReportUpdated": "Abuse report status has been changed",
"shortBodyAbuseReportUpdated": "Report #%d has been changed to %s. Moderator message: %s",
"subtitlePunishment": "Case #%d Moderated by %s",
"subjectPunishmentCreated": "You have been punished",
"shortBodyPunishmentCreated": "You have been punished for %s. Learn more inside the app.",
"subjectPunishmentUpdated": "Your punishment has been updated",
"shortBodyPunishmentUpdated": "Your punishment #%s has been updated. Learn more inside the app.",
"subjectPunishmentDeleted": "Your punishment has been revoked",
"shortBodyPunishmentDeleted": "Your punishment #%s has been revoked."
}

18
locales/zh-CN.json Normal file
View File

@@ -0,0 +1,18 @@
{
"subjectLoginOneTimePassword": "您的验证码",
"shortBodyLoginOneTimePassword": "%s 是您的登录验证码,它将在 30 分钟后过期。",
"subjectConfirmRegistration": "确认您的注册",
"subjectResetPassword": "重置您的密码",
"subjectDeleteAccount": "确认您的帐户删除",
"subjectLoginAlert": "登陆提醒",
"shortBodyLoginAlert": "您的帐户在 %s 登录,若它不是你的设备,请立即处理。",
"subjectAbuseReportUpdated": "举报状态已更新",
"shortBodyAbuseReportUpdated": "举报 #%d 已更新为 %s。管理员回复%s",
"subtitlePunishment": "案件 #%d 由 %s 处理",
"subjectPunishmentCreated": "你收到了一份处分",
"shortBodyPunishmentCreated": "你因为 %s 而被处分,详情请在应用内查看。",
"subjectPunishmentUpdated": "你的处分已更新",
"shortBodyPunishmentUpdated": "你的处分 #%s 已更新。详情请在应用内查看。",
"subjectPunishmentDeleted": "你的处分已撤销",
"shortBodyPunishmentDeleted": "你的处分 #%s 已撤销。"
}

46
pkg/authkit/audit.go Normal file
View File

@@ -0,0 +1,46 @@
package authkit
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"github.com/gofiber/fiber/v2"
)
func AddEvent(nx *nex.Conn, userId uint, action string, meta map[string]any, ip, ua string) error {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return fmt.Errorf("failed to get auth service client: %v", err)
}
_, err = proto.NewAuditServiceClient(conn).RecordEvent(context.Background(), &proto.RecordEventRequest{
UserId: uint64(userId),
Action: action,
Metadata: nex.EncodeMap(meta),
Ip: ip,
UserAgent: ua,
})
return err
}
func AddEventExt(nx *nex.Conn, action string, meta map[string]any, c *fiber.Ctx) error {
user, ok := c.Locals("nex_user").(*sec.UserInfo)
if !ok {
return fmt.Errorf("failed to get user info, make sure you call this method behind the ContextMiddleware")
}
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return fmt.Errorf("failed to get auth service client: %v", err)
}
_, err = proto.NewAuditServiceClient(conn).RecordEvent(context.Background(), &proto.RecordEventRequest{
UserId: uint64(user.ID),
Action: action,
Metadata: nex.EncodeMap(meta),
Ip: c.IP(),
UserAgent: c.Get(fiber.HeaderUserAgent),
})
return err
}

26
pkg/authkit/auth.go Normal file
View File

@@ -0,0 +1,26 @@
package authkit
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"github.com/samber/lo"
)
func EnsureUserPermGranted(nx *nex.Conn, userId, otherId uint, key string, val any) error {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return fmt.Errorf("failed to get auth service client: %v", err)
}
resp, err := proto.NewAuthServiceClient(conn).EnsureUserPermGranted(context.Background(), &proto.CheckUserPermRequest{
UserId: uint64(userId),
OtherId: uint64(otherId),
Key: key,
Value: nex.EncodeMap(val),
})
if err != nil {
return err
}
return lo.Ternary(resp.GetIsValid(), nil, fmt.Errorf("missing permission: %v", key))
}

View File

@@ -0,0 +1,19 @@
package models
import "gorm.io/datatypes"
type AccountGroup struct {
BaseModel
Name string `json:"name"`
PermNodes datatypes.JSONMap `json:"perm_nodes"`
}
type AccountGroupMember struct {
BaseModel
Account Account `json:"account"`
Group AccountGroup `json:"group"`
AccountID uint `json:"account_id"`
GroupID uint `json:"group_id"`
}

View File

@@ -0,0 +1,94 @@
package models
import (
"fmt"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"gorm.io/datatypes"
"github.com/samber/lo"
"github.com/spf13/viper"
)
type Account struct {
BaseModel
Name string `json:"name" gorm:"uniqueIndex"`
Nick string `json:"nick"`
Avatar *string `json:"avatar"`
Banner *string `json:"banner"`
ConfirmedAt *time.Time `json:"confirmed_at"`
SuspendedAt *time.Time `json:"suspended_at"`
PermNodes datatypes.JSONMap `json:"perm_nodes"`
Language string `json:"language"`
AutomatedBy *Account `json:"automated_by" gorm:"foreignKey:AutomatedID"`
AutomatedID *uint `json:"automated_id"`
AffiliatedTo *Realm `json:"affiliated_to" gorm:"foreignKey:AffiliatedID"`
AffiliatedID *uint `json:"affiliated_id"`
Profile AccountProfile `json:"profile,omitempty"`
Contacts []AccountContact `json:"contacts,omitempty"`
Badges []Badge `json:"badges,omitempty"`
Tickets []AuthTicket `json:"tickets,omitempty"`
Factors []AuthFactor `json:"factors,omitempty"`
Relations []AccountRelationship `json:"relations,omitempty" gorm:"foreignKey:AccountID"`
Punishments []Punishment `json:"punishments,omitempty"`
// Keep this for backward compability
Description string `json:"description" gorm:"-"`
}
func (v Account) GetAvatar() *string {
if v.Avatar != nil {
return lo.ToPtr(fmt.Sprintf("%s/%s", viper.GetString("content_endpoint"), *v.Avatar))
}
return nil
}
func (v Account) GetBanner() *string {
if v.Banner != nil {
return lo.ToPtr(fmt.Sprintf("%s/%s", viper.GetString("content_endpoint"), *v.Banner))
}
return nil
}
func (v Account) GetPrimaryEmail() AccountContact {
val, _ := lo.Find(v.Contacts, func(item AccountContact) bool {
return item.Type == EmailAccountContact && item.IsPrimary
})
return val
}
func (v Account) EncodeToUserInfo() *proto.UserInfo {
return &proto.UserInfo{
Id: uint64(v.ID),
Name: v.Name,
Language: v.Language,
PermNodes: nex.EncodeMap(v.PermNodes),
Metadata: nex.EncodeMap(v),
}
}
type AccountContactType = int8
const (
EmailAccountContact = AccountContactType(iota)
)
type AccountContact struct {
BaseModel
Type int8 `json:"type"`
Content string `json:"content" gorm:"uniqueIndex"`
IsPublic bool `json:"is_public"`
IsPrimary bool `json:"is_primary"`
VerifiedAt *time.Time `json:"verified_at"`
AccountID uint `json:"account_id"`
}

View File

@@ -0,0 +1,16 @@
package models
import "gorm.io/datatypes"
type AuditRecord struct {
BaseModel
Action string `json:"action"`
Metadata datatypes.JSONMap `json:"metadata"`
Location *string `json:"location"`
CoordinateX *float64 `json:"coordinate_x"`
CoordinateY *float64 `json:"coordinate_y"`
UserAgent string `json:"user_agent"`
IpAddress string `json:"ip_address"`
AccountID uint `json:"account_id"`
}

View File

@@ -0,0 +1,85 @@
package models
import (
"fmt"
"time"
"gorm.io/datatypes"
)
type AuthConfig struct {
AlwaysRisky bool `json:"always_risky"`
MaximumAuthSteps int `json:"maximum_auth_steps" validate:"required,min=1,max=99"`
}
type AuthFactorType = int8
const (
PasswordAuthFactor = AuthFactorType(iota)
EmailPasswordFactor
TimeOtpFactor
InAppNotifyFactor
)
type AuthFactor struct {
BaseModel
Type int8 `json:"type"`
Secret string `json:"-"`
Config datatypes.JSONMap `json:"config"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
}
type AuthTicket struct {
BaseModel
Location *string `json:"location"`
CoordinateX *float64 `json:"coordinate_x"`
CoordinateY *float64 `json:"coordinate_y"`
IpAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
StepRemain int `json:"step_remain"`
Claims datatypes.JSONSlice[string] `json:"claims"`
Audiences datatypes.JSONSlice[string] `json:"audiences"`
FactorTrail datatypes.JSONSlice[int] `json:"factor_trail"`
GrantToken *string `json:"grant_token"`
AccessToken *string `json:"access_token"`
RefreshToken *string `json:"refresh_token"`
ExpiredAt *time.Time `json:"expired_at"`
AvailableAt *time.Time `json:"available_at"`
LastGrantAt *time.Time `json:"last_grant_at"`
Nonce *string `json:"nonce"`
ClientID *uint `json:"client_id"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
}
func (v AuthTicket) IsAvailable() error {
if v.StepRemain > 0 {
return fmt.Errorf("ticket isn't authenticated yet")
}
if v.AvailableAt != nil && time.Now().Unix() < v.AvailableAt.Unix() {
return fmt.Errorf("ticket isn't available yet")
}
if v.ExpiredAt != nil && time.Now().Unix() > v.ExpiredAt.Unix() {
return fmt.Errorf("ticket expired")
}
return nil
}
func (v AuthTicket) IsCanBeAvailble() error {
if v.StepRemain > 0 {
return fmt.Errorf("ticket isn't authenticated yet")
}
return nil
}
type AuthContext struct {
Ticket AuthTicket `json:"ticket"`
Account Account `json:"account"`
}

View File

@@ -7,5 +7,6 @@ type Badge struct {
Type string `json:"type"`
Metadata datatypes.JSONMap `json:"metadata"`
IsActive bool `json:"is_active"`
AccountID uint `json:"account_id"`
}

13
pkg/authkit/models/bot.go Normal file
View File

@@ -0,0 +1,13 @@
package models
type ApiKey struct {
BaseModel
Name string `json:"name"`
Description string `json:"description"`
Lifecycle *int64 `json:"lifecycle"`
Ticket AuthTicket `json:"ticket" gorm:"TicketID"`
TicketID uint `json:"ticket_id"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
}

View File

@@ -0,0 +1,21 @@
package models
import "gorm.io/datatypes"
type CheckInRecord struct {
BaseModel
ResultTier int `json:"result_tier"`
ResultExperience int `json:"result_experience"`
ResultCoin float64 `json:"result_coin"`
CurrentStreak int `json:"current_streak"`
// The result modifiers are some random tips that will show up in the client;
// This field is to use to make sure the tips will be the same when the client is reloaded.
// For now, this modifier slice will contain four random numbers from 0 to 1024.
// Client should mod this modifier by the length of total available tips.
ResultModifiers datatypes.JSONSlice[int] `json:"result_modifiers"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
}

View File

@@ -0,0 +1,16 @@
package models
import "gorm.io/datatypes"
type ThirdClient struct {
BaseModel
Alias string `json:"alias" gorm:"uniqueIndex"`
Name string `json:"name"`
Description string `json:"description"`
Secret string `json:"secret"`
Urls datatypes.JSONSlice[string] `json:"urls"`
Callbacks datatypes.JSONSlice[string] `json:"callbacks"`
IsDraft bool `json:"is_draft"`
AccountID *uint `json:"account_id"`
}

View File

@@ -0,0 +1,18 @@
package models
import "gorm.io/datatypes"
type ActionEvent struct {
BaseModel
Type string `json:"type"`
Metadata datatypes.JSONMap `json:"metadata"`
Location *string `json:"location"`
CoordinateX *float64 `json:"coordinate_x"`
CoordinateY *float64 `json:"coordinate_y"`
IpAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
}

View File

@@ -0,0 +1,65 @@
package models
import (
"time"
"git.solsynth.dev/hypernet/pusher/pkg/pushkit"
"gorm.io/datatypes"
)
type Notification struct {
BaseModel
Topic string `json:"topic"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Body string `json:"body"`
Metadata datatypes.JSONMap `json:"metadata"`
Priority int `json:"priority"`
SenderID *uint `json:"sender_id"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
ReadAt *time.Time `json:"read_at"`
}
func (v Notification) EncodeToPushkit() pushkit.Notification {
return pushkit.Notification{
Topic: v.Topic,
Title: v.Title,
Subtitle: v.Subtitle,
Body: v.Body,
Metadata: v.Metadata,
Priority: v.Priority,
}
}
func NewNotificationFromPushkit(pk pushkit.Notification) Notification {
return Notification{
Topic: pk.Topic,
Title: pk.Title,
Subtitle: pk.Subtitle,
Body: pk.Body,
Metadata: pk.Metadata,
Priority: pk.Priority,
SenderID: nil,
}
}
const (
NotifySubscriberFirebase = "firebase"
NotifySubscriberAPNs = "apple"
)
type NotificationSubscriber struct {
BaseModel
UserAgent string `json:"user_agent"`
Provider string `json:"provider"`
DeviceID string `json:"device_id" gorm:"uniqueIndex"`
DeviceToken string `json:"device_token"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
}

View File

@@ -0,0 +1,19 @@
package models
import "gorm.io/datatypes"
type PreferenceAuth struct {
BaseModel
Config datatypes.JSONType[AuthConfig] `json:"config"`
AccountID uint `json:"account_id"`
Account Account `json:"account"`
}
type PreferenceNotification struct {
BaseModel
Config datatypes.JSONMap `json:"config"`
AccountID uint `json:"account_id"`
Account Account `json:"account"`
}

View File

@@ -0,0 +1,31 @@
package models
import (
"time"
"gorm.io/datatypes"
)
type AccountProfile struct {
BaseModel
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Description string `json:"description"`
TimeZone string `json:"time_zone"`
Location string `json:"location"`
Pronouns string `json:"pronouns"`
Gender string `json:"gender"`
Links datatypes.JSONMap `json:"links"`
Experience uint64 `json:"experience"`
LastSeenAt *time.Time `json:"last_seen_at"`
Birthday *time.Time `json:"birthday"`
AccountID uint `json:"account_id"`
}
type AccountPage struct {
BaseModel
Content string `json:"content"`
AccountID uint `json:"account_id"`
}

View File

@@ -0,0 +1,44 @@
package models
import (
"time"
"gorm.io/datatypes"
)
type ProgramPrice struct {
Currency string `json:"currency"`
Amount float64 `json:"amount"`
}
type ProgramBadge struct {
Type string `json:"type"`
Metadata map[string]any `json:"metadata"`
}
type ProgramGroup struct {
ID uint `json:"id"`
}
type Program struct {
BaseModel
Name string `json:"name"`
Description string `json:"description"`
Alias string `json:"alias" gorm:"uniqueIndex"`
ExpRequirement int64 `json:"exp_requirement"`
Price datatypes.JSONType[ProgramPrice] `json:"price"`
Badge datatypes.JSONType[ProgramBadge] `json:"badge"`
Group datatypes.JSONType[ProgramGroup] `json:"group"`
Appearance datatypes.JSONMap `json:"appearance"`
}
type ProgramMember struct {
BaseModel
LastPaid *time.Time `json:"last_paid"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
Program Program `json:"program"`
ProgramID uint `json:"program_id"`
}

View File

@@ -0,0 +1,26 @@
package models
import (
"time"
"gorm.io/datatypes"
)
const (
PunishmentTypeStrike = iota
PunishmentTypeLimited
PunishmentTypeDisabled
)
type Punishment struct {
BaseModel
Reason string `json:"reason"`
Type int `json:"type"`
PermNodes datatypes.JSONMap `json:"perm_nodes"`
ExpiredAt *time.Time `json:"expired_at"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
Moderator *Account `json:"moderator"`
ModeratorID *uint `json:"moderator_id"`
}

View File

@@ -0,0 +1,66 @@
package models
import (
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"gorm.io/datatypes"
)
const (
RealmPopularityMemberFactor = 5
RealmPopularityPostFactor = 10
RealmPopularityChatFactor = 2
)
type Realm struct {
BaseModel
Alias string `json:"alias" gorm:"uniqueIndex"`
Name string `json:"name"`
Description string `json:"description"`
Members []RealmMember `json:"members"`
Avatar *string `json:"avatar"`
Banner *string `json:"banner"`
Popularity int `json:"popularity"`
AccessPolicy datatypes.JSONMap `json:"access_policy"`
IsPublic bool `json:"is_public"`
IsCommunity bool `json:"is_community"`
AccountID uint `json:"account_id"`
}
func NewRealmFromProto(proto *proto.RealmInfo) Realm {
return Realm{
BaseModel: BaseModel{
ID: uint(proto.GetId()),
},
Alias: proto.GetAlias(),
Name: proto.GetName(),
Description: proto.GetDescription(),
Avatar: &proto.Avatar,
Banner: &proto.Banner,
IsPublic: proto.GetIsPublic(),
IsCommunity: proto.GetIsCommunity(),
AccessPolicy: nex.DecodeMap(proto.GetAccessPolicy()),
}
}
type RealmMember struct {
BaseModel
RealmID uint `json:"realm_id"`
AccountID uint `json:"account_id"`
Realm Realm `json:"realm"`
Account Account `json:"account"`
PowerLevel int `json:"power_level"`
}
func NewRealmMemberFromProto(proto *proto.RealmMemberInfo) RealmMember {
return RealmMember{
BaseModel: BaseModel{
ID: uint(proto.GetId()),
},
RealmID: uint(proto.GetRealmId()),
AccountID: uint(proto.GetUserId()),
PowerLevel: int(proto.GetPowerLevel()),
}
}

View File

@@ -0,0 +1,23 @@
package models
import "gorm.io/datatypes"
type RelationshipStatus = int8
const (
RelationshipPending = RelationshipStatus(iota)
RelationshipFriend
RelationshipBlocked
RelationshipWaiting
)
type AccountRelationship struct {
BaseModel
AccountID uint `json:"account_id"`
RelatedID uint `json:"related_id"`
Account Account `json:"account"`
Related Account `json:"related"`
Status RelationshipStatus `json:"status"`
PermNodes datatypes.JSONMap `json:"perm_nodes"`
}

View File

@@ -0,0 +1,19 @@
package models
const (
ReportStatusPending = "pending"
ReportStatusReviewing = "reviewing"
ReportStatusConfirmed = "confirmed"
ReportStatusRejected = "rejected"
ReportStatusProcessed = "processed"
)
type AbuseReport struct {
BaseModel
Resource string `json:"resource"`
Reason string `json:"reason"`
Status string `json:"status"`
AccountID uint `json:"account_id"`
Account Account `json:"account"`
}

View File

@@ -0,0 +1,22 @@
package models
import "time"
type MagicTokenType = int8
const (
ConfirmMagicToken = MagicTokenType(iota)
RegistrationMagicToken
ResetPasswordMagicToken
DeleteAccountMagicToken
)
type MagicToken struct {
BaseModel
Code string `json:"code"`
Type int8 `json:"type"`
AccountID *uint `json:"account_id"`
ExpiredAt *time.Time `json:"expired_at"`
LastNotifiedAt *time.Time `json:"last_notified_at"`
}

48
pkg/authkit/notify.go Normal file
View File

@@ -0,0 +1,48 @@
package authkit
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"git.solsynth.dev/hypernet/pusher/pkg/pushkit"
"github.com/goccy/go-json"
)
func NotifyUser(nx *nex.Conn, userId uint64, notify pushkit.Notification, unsaved ...bool) error {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return fmt.Errorf("failed to get auth service client: %v", err)
}
raw, _ := json.Marshal(notify)
if len(unsaved) == 0 {
unsaved = append(unsaved, false)
}
_, err = proto.NewNotifyServiceClient(conn).NotifyUser(context.Background(), &proto.NotifyUserRequest{
UserId: userId,
Notify: &proto.NotifyInfoPayload{
Unsaved: unsaved[0],
Data: raw,
},
})
return err
}
func NotifyUserBatch(nx *nex.Conn, userId []uint64, notify pushkit.Notification, unsaved ...bool) error {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return fmt.Errorf("failed to get auth service client: %v", err)
}
raw, _ := json.Marshal(notify)
if len(unsaved) == 0 {
unsaved = append(unsaved, false)
}
_, err = proto.NewNotifyServiceClient(conn).NotifyUserBatch(context.Background(), &proto.NotifyUserBatchRequest{
UserId: userId,
Notify: &proto.NotifyInfoPayload{
Unsaved: unsaved[0],
Data: raw,
},
})
return err
}

27
pkg/authkit/parser.go Normal file
View File

@@ -0,0 +1,27 @@
package authkit
import (
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"github.com/goccy/go-json"
"github.com/gofiber/fiber/v2"
)
// GetAccountFromUserInfo returns the account from the user info
// This method will not to query the database, it will parse the token and get the subject of the userinfo token
func GetAccountFromUserInfo(info *sec.UserInfo) models.Account {
raw, _ := json.Marshal(info.Metadata)
// We assume the token is signed by the same version of service
// So directly read the data out of the metadata
var out models.Account
_ = json.Unmarshal(raw, &out)
return out
}
func ParseAccountMiddleware(c *fiber.Ctx) error {
if info, ok := c.Locals("nex_user").(*sec.UserInfo); ok {
c.Locals("user", GetAccountFromUserInfo(info))
}
return c.Next()
}

109
pkg/authkit/realm.go Normal file
View File

@@ -0,0 +1,109 @@
package authkit
import (
"context"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"github.com/samber/lo"
)
func GetRealm(nx *nex.Conn, id uint) (models.Realm, error) {
var realm models.Realm
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return realm, err
}
resp, err := proto.NewRealmServiceClient(conn).GetRealm(context.Background(), &proto.LookupRealmRequest{
Id: lo.ToPtr(uint64(id)),
})
if err != nil {
return realm, err
}
return models.NewRealmFromProto(resp), nil
}
func GetRealmByAlias(nx *nex.Conn, alias string) (models.Realm, error) {
var realm models.Realm
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return realm, err
}
resp, err := proto.NewRealmServiceClient(conn).GetRealm(context.Background(), &proto.LookupRealmRequest{
Alias: &alias,
})
if err != nil {
return realm, err
}
return models.NewRealmFromProto(resp), nil
}
func ListRealm(nx *nex.Conn, id []uint) ([]models.Realm, error) {
var realms []models.Realm
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return realms, err
}
resp, err := proto.NewRealmServiceClient(conn).ListRealm(context.Background(), &proto.ListRealmRequest{
Id: lo.Map(id, func(item uint, _ int) uint64 {
return uint64(item)
}),
})
if err != nil {
return realms, err
}
for _, realm := range resp.GetData() {
realms = append(realms, models.NewRealmFromProto(realm))
}
return realms, nil
}
func GetRealmMember(nx *nex.Conn, realmID, userID uint) (models.RealmMember, error) {
var member models.RealmMember
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return member, err
}
resp, err := proto.NewRealmServiceClient(conn).GetRealmMember(context.Background(), &proto.RealmMemberLookupRequest{
RealmId: lo.ToPtr(uint64(realmID)),
UserId: lo.ToPtr(uint64(userID)),
})
if err != nil {
return member, err
}
return models.NewRealmMemberFromProto(resp), nil
}
func ListRealmMember(nx *nex.Conn, realmID uint) ([]models.RealmMember, error) {
var members []models.RealmMember
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return members, err
}
resp, err := proto.NewRealmServiceClient(conn).ListRealmMember(context.Background(), &proto.RealmMemberLookupRequest{
RealmId: lo.ToPtr(uint64(realmID)),
})
if err != nil {
return members, err
}
for _, member := range resp.GetData() {
members = append(members, models.NewRealmMemberFromProto(member))
}
return members, nil
}
func CheckRealmMemberPerm(nx *nex.Conn, realmID uint, userID, power int) bool {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return false
}
resp, err := proto.NewRealmServiceClient(conn).CheckRealmMemberPerm(context.Background(), &proto.CheckRealmPermRequest{
RealmId: uint64(realmID),
UserId: uint64(userID),
PowerLevel: int32(power),
})
if err != nil {
return false
}
return resp.GetIsSuccess()
}

23
pkg/authkit/relative.go Normal file
View File

@@ -0,0 +1,23 @@
package authkit
import (
"context"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
)
func ListRelative(nx *nex.Conn, userId uint, status int32, isRelated bool) ([]*proto.UserInfo, error) {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return nil, err
}
resp, err := proto.NewUserServiceClient(conn).ListUserRelative(context.Background(), &proto.ListUserRelativeRequest{
UserId: uint64(userId),
Status: status,
IsRelated: isRelated,
})
if err != nil {
return nil, err
}
return resp.GetData(), err
}

View File

@@ -0,0 +1,65 @@
package authkit
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"github.com/samber/lo"
)
func GetThirdClient(nx *nex.Conn, id uint, secret *string) (*models.ThirdClient, error) {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return nil, fmt.Errorf("failed to get auth service client: %v", err)
}
resp, err := proto.NewThirdClientServiceClient(conn).
GetThirdClient(context.Background(), &proto.GetThirdClientRequest{
Id: lo.ToPtr(uint64(id)),
Secret: secret,
})
if err != nil {
return nil, err
}
return &models.ThirdClient{
Alias: resp.GetInfo().GetAlias(),
Name: resp.GetInfo().GetName(),
Description: resp.GetInfo().GetDescription(),
IsDraft: resp.GetInfo().GetIsDraft(),
AccountID: lo.TernaryF(resp.GetInfo().AccountId != nil, func() *uint {
return lo.ToPtr(uint(resp.GetInfo().GetAccountId()))
}, func() *uint {
return nil
}),
}, nil
}
func GetThirdClientByAlias(nx *nex.Conn, alias string, secret *string) (*models.ThirdClient, error) {
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return nil, fmt.Errorf("failed to get auth service client: %v", err)
}
resp, err := proto.NewThirdClientServiceClient(conn).
GetThirdClient(context.Background(), &proto.GetThirdClientRequest{
Alias: &alias,
Secret: secret,
})
if err != nil {
return nil, err
}
return &models.ThirdClient{
Alias: resp.GetInfo().GetAlias(),
Name: resp.GetInfo().GetName(),
Description: resp.GetInfo().GetDescription(),
IsDraft: resp.GetInfo().GetIsDraft(),
AccountID: lo.TernaryF(resp.GetInfo().AccountId != nil, func() *uint {
return lo.ToPtr(uint(resp.GetInfo().GetAccountId()))
}, func() *uint {
return nil
}),
}, nil
}

118
pkg/authkit/user.go Normal file
View File

@@ -0,0 +1,118 @@
package authkit
import (
"context"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"github.com/samber/lo"
)
func GetUser(nx *nex.Conn, userId uint) (models.Account, error) {
cacheConn, err := cachekit.NewConn(nx, 3*time.Second)
if err == nil {
key := cachekit.FKey(cachekit.DAAttachment, userId)
if user, err := cachekit.Get[models.Account](cacheConn, key); err == nil {
return user, nil
}
}
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return models.Account{}, err
}
raw, _ := proto.NewUserServiceClient(conn).GetUser(context.Background(), &proto.GetUserRequest{
UserId: lo.ToPtr(uint64(userId)),
})
return GetAccountFromUserInfo(&sec.UserInfo{
ID: uint(raw.GetId()),
Name: raw.GetName(),
PermNodes: nex.DecodeMap(raw.GetPermNodes()),
Metadata: nex.DecodeMap(raw.GetMetadata()),
}), nil
}
func GetUserByName(nx *nex.Conn, name string) (models.Account, error) {
cacheConn, err := cachekit.NewConn(nx, 3*time.Second)
if err == nil {
key := cachekit.FKey(cachekit.DAAttachment, name)
if user, err := cachekit.Get[models.Account](cacheConn, key); err == nil {
return user, nil
}
}
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return models.Account{}, err
}
raw, _ := proto.NewUserServiceClient(conn).GetUser(context.Background(), &proto.GetUserRequest{
Name: &name,
})
return GetAccountFromUserInfo(&sec.UserInfo{
ID: uint(raw.GetId()),
Name: raw.GetName(),
PermNodes: nex.DecodeMap(raw.GetPermNodes()),
Metadata: nex.DecodeMap(raw.GetMetadata()),
}), nil
}
func ListUser(nx *nex.Conn, userIds []uint) ([]models.Account, error) {
var accounts []models.Account
var missingId []uint
cachedUsers := make(map[uint]models.Account)
// Try to get users from cache
cacheConn, err := cachekit.NewConn(nx, 3*time.Second)
if err == nil {
for _, userId := range userIds {
key := cachekit.FKey(cachekit.DAAttachment, userId)
if user, err := cachekit.Get[models.Account](cacheConn, key); err == nil {
cachedUsers[userId] = user
} else {
missingId = append(missingId, userId)
}
}
}
// If all users are found in cache, return them
if len(missingId) == 0 {
for _, account := range cachedUsers {
accounts = append(accounts, account)
}
return accounts, nil
}
// Fetch missing users from the gRPC service
conn, err := nx.GetClientGrpcConn(nex.ServiceTypeAuth)
if err != nil {
return nil, err
}
raw, _ := proto.NewUserServiceClient(conn).ListUser(context.Background(), &proto.ListUserRequest{
UserId: lo.Map(missingId, func(item uint, index int) uint64 {
return uint64(item)
}),
})
// Convert fetched users and add to the result
for _, item := range raw.GetData() {
account := GetAccountFromUserInfo(&sec.UserInfo{
ID: uint(item.GetId()),
Name: item.GetName(),
PermNodes: nex.DecodeMap(item.GetPermNodes()),
Metadata: nex.DecodeMap(item.GetMetadata()),
})
accounts = append(accounts, account)
}
// Merge cached and fetched results
for _, account := range cachedUsers {
accounts = append(accounts, account)
}
return accounts, nil
}

View File

@@ -1,63 +0,0 @@
package hyper
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"google.golang.org/grpc"
"time"
)
func (v *HyperConn) DoAuthenticate(atk, rtk string) (acc *proto.Userinfo, accessTk string, refreshTk string, err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var in *grpc.ClientConn
in, err = v.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return
}
var reply *proto.AuthReply
reply, err = proto.NewAuthClient(in).Authenticate(ctx, &proto.AuthRequest{
AccessToken: atk,
RefreshToken: &rtk,
})
if err != nil {
return
}
if reply != nil {
acc = reply.GetUserinfo()
accessTk = reply.GetAccessToken()
refreshTk = reply.GetRefreshToken()
if !reply.IsValid {
err = fmt.Errorf("invalid authorization context")
return
}
}
return
}
func (v *HyperConn) DoCheckPerm(atk string, key string, val []byte) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
in, err := v.DiscoverServiceGRPC("Hydrogen.Passport")
if err != nil {
return err
}
reply, err := proto.NewAuthClient(in).CheckPerm(ctx, &proto.CheckPermRequest{
Token: atk,
Key: key,
Value: val,
})
if err != nil {
return err
} else if !reply.GetIsValid() {
return fmt.Errorf("missing permission: %s", key)
}
return nil
}

View File

@@ -1,69 +0,0 @@
package hyper
import (
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"github.com/gofiber/fiber/v2"
jsoniter "github.com/json-iterator/go"
"strings"
"time"
)
const CookieAtk = "__hydrogen_atk"
const CookieRtk = "__hydrogen_rtk"
func (v *HyperConn) AuthMiddleware(c *fiber.Ctx) error {
var atk string
if cookie := c.Cookies(CookieAtk); len(cookie) > 0 {
atk = cookie
}
if header := c.Get(fiber.HeaderAuthorization); len(header) > 0 {
tk := strings.Replace(header, "Bearer", "", 1)
atk = strings.TrimSpace(tk)
}
if tk := c.Query("tk"); len(tk) > 0 {
atk = strings.TrimSpace(tk)
}
c.Locals("p_token", atk)
rtk := c.Cookies(CookieRtk)
if user, newAtk, newRtk, err := v.DoAuthenticate(atk, rtk); err == nil {
if newAtk != atk {
c.Cookie(&fiber.Cookie{
Name: CookieAtk,
Value: newAtk,
SameSite: "Lax",
Expires: time.Now().Add(60 * time.Minute),
Path: "/",
})
c.Cookie(&fiber.Cookie{
Name: CookieRtk,
Value: newRtk,
SameSite: "Lax",
Expires: time.Now().Add(24 * 30 * time.Hour),
Path: "/",
})
}
c.Locals("p_user", user)
}
return c.Next()
}
func (v *HyperConn) EnsureAuthenticated(c *fiber.Ctx) error {
if _, ok := c.Locals("p_user").(*proto.Userinfo); !ok {
return fiber.NewError(fiber.StatusUnauthorized)
}
return nil
}
func (v *HyperConn) EnsureGrantedPerm(c *fiber.Ctx, key string, val any) error {
if err := v.EnsureAuthenticated(c); err != nil {
return err
}
encodedVal, _ := jsoniter.Marshal(val)
if err := v.DoCheckPerm(c.Locals("p_token").(string), key, encodedVal); err != nil {
return fiber.NewError(fiber.StatusForbidden, err.Error())
}
return nil
}

View File

@@ -1,51 +0,0 @@
package hyper
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
health "google.golang.org/grpc/health/grpc_health_v1"
"time"
_ "github.com/mbobakov/grpc-consul-resolver"
)
type HyperConn struct {
Addr string
cacheGrpcConn map[string]*grpc.ClientConn
}
func NewHyperConn(addr string) *HyperConn {
return &HyperConn{
Addr: addr,
cacheGrpcConn: make(map[string]*grpc.ClientConn),
}
}
func (v *HyperConn) DiscoverServiceGRPC(name string) (*grpc.ClientConn, error) {
if val, ok := v.cacheGrpcConn[name]; ok {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if _, err := health.NewHealthClient(val).Check(ctx, &health.HealthCheckRequest{
Service: name,
}); err == nil {
return val, nil
} else {
delete(v.cacheGrpcConn, name)
}
}
target := fmt.Sprintf("consul://%s/%s", v.Addr, name)
conn, err := grpc.NewClient(
target,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
if err == nil {
v.cacheGrpcConn[name] = conn
}
return conn, err
}

View File

@@ -1,16 +1,19 @@
package database
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"gorm.io/gorm"
)
var AutoMaintainRange = []any{
&models.Account{},
&models.AccountGroup{},
&models.AccountGroupMember{},
&models.AuthFactor{},
&models.AccountProfile{},
&models.AccountPage{},
&models.AccountContact{},
&models.AccountFriendship{},
&models.AccountRelationship{},
&models.Status{},
&models.Badge{},
&models.Realm{},
@@ -21,6 +24,15 @@ var AutoMaintainRange = []any{
&models.ActionEvent{},
&models.Notification{},
&models.NotificationSubscriber{},
&models.AuditRecord{},
&models.ApiKey{},
&models.CheckInRecord{},
&models.PreferenceNotification{},
&models.PreferenceAuth{},
&models.AbuseReport{},
&models.Program{},
&models.ProgramMember{},
&models.Punishment{},
}
func RunMigration(source *gorm.DB) error {

View File

@@ -1,24 +1,28 @@
package database
import (
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
"git.solsynth.dev/hypernet/passport/pkg/internal/gap"
"github.com/oschwald/geoip2-golang"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
var C *gorm.DB
func NewGorm() error {
var err error
dsn, err := cruda.NewCrudaConn(gap.Nx).AllocDatabase("passport")
if err != nil {
return fmt.Errorf("failed to alloc database from nexus: %v", err)
}
dialector := postgres.Open(viper.GetString("database.dsn"))
C, err = gorm.Open(dialector, &gorm.Config{NamingStrategy: schema.NamingStrategy{
TablePrefix: viper.GetString("database.prefix"),
}, Logger: logger.New(&log.Logger, logger.Config{
C, err = gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: logger.New(&log.Logger, logger.Config{
Colorful: true,
IgnoreRecordNotFoundError: true,
LogLevel: lo.Ternary(viper.GetBool("debug.database"), logger.Info, logger.Silent),
@@ -26,3 +30,14 @@ func NewGorm() error {
return err
}
var Gc *geoip2.Reader
func NewGeoDB() error {
conn, err := geoip2.Open(viper.GetString("geoip_db"))
if err != nil {
return fmt.Errorf("failed to open geoip database: %v", err)
}
Gc = conn
return nil
}

View File

@@ -1,15 +0,0 @@
package gap
import "net"
func GetOutboundIP() (net.IP, error) {
conn, err := net.Dial("udp", "1.1.1.1:80")
if err != nil {
return nil, err
} else {
defer conn.Close()
}
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP, nil
}

View File

@@ -2,51 +2,76 @@ package gap
import (
"fmt"
"strconv"
"strings"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/nexus/pkg/nex/localize"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/nex/rx"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"git.solsynth.dev/hypernet/pusher/pkg/pushkit/pushcon"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/hashicorp/consul/api"
"github.com/spf13/viper"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
_ "github.com/mbobakov/grpc-consul-resolver"
)
func Register() error {
cfg := api.DefaultConfig()
cfg.Address = viper.GetString("consul.addr")
var (
Nx *nex.Conn
Px *pushcon.Conn
Rx *rx.MqConn
Ca *cachekit.Conn
)
client, err := api.NewClient(cfg)
if err != nil {
return err
}
const (
FactorOtpPrefix = "auth-otp"
)
func InitializeToNexus() error {
grpcBind := strings.SplitN(viper.GetString("grpc_bind"), ":", 2)
httpBind := strings.SplitN(viper.GetString("bind"), ":", 2)
outboundIp, _ := GetOutboundIP()
port, _ := strconv.Atoi(grpcBind[1])
outboundIp, _ := nex.GetOutboundIP()
registration := new(api.AgentServiceRegistration)
registration.ID = viper.GetString("id")
registration.Name = "Hydrogen.Passport"
registration.Address = outboundIp.String()
registration.Port = port
registration.Check = &api.AgentServiceCheck{
GRPC: fmt.Sprintf("%s:%s", outboundIp, grpcBind[1]),
Timeout: "5s",
Interval: "1m",
DeregisterCriticalServiceAfter: "3m",
grpcOutbound := fmt.Sprintf("%s:%s", outboundIp, grpcBind[1])
httpOutbound := fmt.Sprintf("%s:%s", outboundIp, httpBind[1])
var err error
Nx, err = nex.NewNexusConn(viper.GetString("nexus_addr"), &proto.ServiceInfo{
Id: viper.GetString("id"),
Type: nex.ServiceTypeAuth,
Label: "Passport",
GrpcAddr: grpcOutbound,
HttpAddr: lo.ToPtr("http://" + httpOutbound + "/api"),
})
if err == nil {
go func() {
err := Nx.RunRegistering()
if err != nil {
log.Error().Err(err).Msg("An error occurred while registering service...")
}
}()
}
return client.Agent().ServiceRegister(registration)
Px, err = pushcon.NewConn(Nx)
if err != nil {
return fmt.Errorf("error during initialize pushcon: %v", err)
}
Rx, err = rx.NewMqConn(Nx)
if err != nil {
return fmt.Errorf("error during initialize nexus rx module: %v", err)
}
Ca, err = cachekit.NewConn(Nx, time.Second*3)
if err != nil {
return fmt.Errorf("error during initialize nexus cache module: %v", err)
}
return err
}
func DiscoverPaperclip() (*grpc.ClientConn, error) {
target := fmt.Sprintf("consul://%s/Hydrogen.Paperclip", viper.GetString("consul.addr"))
return grpc.NewClient(
target,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
func LoadLocalization() error {
return localize.LoadLocalization(viper.GetString("locales_dir"), viper.GetString("templates_dir"))
}

View File

@@ -2,55 +2,41 @@ package grpc
import (
"context"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
jsoniter "github.com/json-iterator/go"
"github.com/samber/lo"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
)
func (v *Server) Authenticate(_ context.Context, in *proto.AuthRequest) (*proto.AuthReply, error) {
ctx, perms, atk, rtk, err := services.Authenticate(in.GetAccessToken(), in.GetRefreshToken(), 0)
func (v *App) Authenticate(_ context.Context, in *proto.AuthRequest) (*proto.AuthReply, error) {
ticket, perms, err := services.Authenticate(uint(in.GetSessionId()))
if err != nil {
return &proto.AuthReply{
IsValid: false,
}, nil
} else {
user := ctx.Account
rawPerms, _ := jsoniter.Marshal(perms)
userinfo := &proto.Userinfo{
user := ticket.Account
userinfo := &proto.UserInfo{
Id: uint64(user.ID),
Name: user.Name,
Nick: user.Nick,
Email: user.GetPrimaryEmail().Content,
Description: &user.Description,
}
if user.Avatar != nil {
userinfo.Avatar = *user.GetAvatar()
}
if user.Banner != nil {
userinfo.Banner = *user.GetBanner()
PermNodes: nex.EncodeMap(perms),
Metadata: nex.EncodeMap(user),
}
return &proto.AuthReply{
IsValid: true,
AccessToken: &atk,
RefreshToken: &rtk,
Permissions: rawPerms,
TicketId: lo.ToPtr(uint64(ctx.Ticket.ID)),
Userinfo: userinfo,
Info: &proto.AuthInfo{
SessionId: uint64(ticket.ID),
Info: userinfo,
},
}, nil
}
}
func (v *Server) CheckPerm(_ context.Context, in *proto.CheckPermRequest) (*proto.CheckPermReply, error) {
claims, err := services.DecodeJwt(in.GetToken())
if err != nil {
return nil, err
}
ctx, err := services.GetAuthContext(claims.ID)
func (v *App) EnsurePermGranted(_ context.Context, in *proto.CheckPermRequest) (*proto.CheckPermResponse, error) {
ctx, err := services.GetAuthContext(uint(in.GetSessionId()))
if err != nil {
return nil, err
}
@@ -61,10 +47,29 @@ func (v *Server) CheckPerm(_ context.Context, in *proto.CheckPermRequest) (*prot
var value any
_ = jsoniter.Unmarshal(in.GetValue(), &value)
perms := services.FilterPermNodes(heldPerms, ctx.Ticket.Claims)
perms := services.FilterPermNodes(heldPerms, ctx.Claims)
valid := services.HasPermNode(perms, in.GetKey(), value)
return &proto.CheckPermReply{
return &proto.CheckPermResponse{
IsValid: valid,
}, nil
}
func (v *App) EnsureUserPermGranted(_ context.Context, in *proto.CheckUserPermRequest) (*proto.CheckUserPermResponse, error) {
relation, err := services.GetRelationWithTwoNode(uint(in.GetUserId()), uint(in.GetOtherId()))
if err != nil {
return &proto.CheckUserPermResponse{
IsValid: false,
}, nil
}
defaultPerm := relation.Status == models.RelationshipFriend
var value any
_ = jsoniter.Unmarshal(in.GetValue(), &value)
valid := services.HasPermNodeWithDefault(relation.PermNodes, in.GetKey(), value, defaultPerm)
return &proto.CheckUserPermResponse{
IsValid: valid,
}, nil
}

View File

@@ -0,0 +1,21 @@
package grpc
import (
"context"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
"git.solsynth.dev/hypernet/passport/pkg/proto"
)
func (v *App) RecordEvent(ctx context.Context, request *proto.RecordEventRequest) (*proto.RecordEventResponse, error) {
services.AddEvent(
uint(request.GetUserId()),
request.GetAction(),
nex.DecodeMap(request.GetMetadata()),
request.GetIp(),
request.GetUserAgent(),
)
return &proto.RecordEventResponse{IsSuccess: true}, nil
}

View File

@@ -1,46 +0,0 @@
package grpc
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"github.com/samber/lo"
)
func (v *Server) ListFriendship(_ context.Context, request *proto.FriendshipLookupRequest) (*proto.ListFriendshipResponse, error) {
account, err := services.GetAccount(uint(request.GetAccountId()))
if err != nil {
return nil, err
}
friends, err := services.ListFriend(account, models.FriendshipStatus(request.GetStatus()))
if err != nil {
return nil, err
}
return &proto.ListFriendshipResponse{
Data: lo.Map(friends, func(item models.AccountFriendship, index int) *proto.FriendshipResponse {
return &proto.FriendshipResponse{
AccountId: uint64(item.AccountID),
RelatedId: uint64(item.RelatedID),
Status: uint32(item.Status),
}
}),
}, nil
}
func (v *Server) GetFriendship(ctx context.Context, request *proto.FriendshipTwoSideLookupRequest) (*proto.FriendshipResponse, error) {
friend, err := services.GetFriendWithTwoSides(uint(request.GetAccountId()), uint(request.GetRelatedId()))
if err != nil {
return nil, err
} else if friend.Status != models.FriendshipStatus(request.GetStatus()) {
return nil, fmt.Errorf("status mismatch")
}
return &proto.FriendshipResponse{
AccountId: uint64(friend.AccountID),
RelatedId: uint64(friend.RelatedID),
Status: uint32(friend.Status),
}, nil
}

View File

@@ -6,13 +6,13 @@ import (
"time"
)
func (v *Server) Check(ctx context.Context, request *health.HealthCheckRequest) (*health.HealthCheckResponse, error) {
func (v *App) Check(ctx context.Context, request *health.HealthCheckRequest) (*health.HealthCheckResponse, error) {
return &health.HealthCheckResponse{
Status: health.HealthCheckResponse_SERVING,
}, nil
}
func (v *Server) Watch(request *health.HealthCheckRequest, server health.Health_WatchServer) error {
func (v *App) Watch(request *health.HealthCheckRequest, server health.Health_WatchServer) error {
for {
if server.Send(&health.HealthCheckResponse{
Status: health.HealthCheckResponse_SERVING,

View File

@@ -2,48 +2,38 @@ package grpc
import (
"context"
jsoniter "github.com/json-iterator/go"
"fmt"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/pusher/pkg/pushkit"
"github.com/goccy/go-json"
"github.com/rs/zerolog/log"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"github.com/samber/lo"
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
"git.solsynth.dev/hypernet/passport/pkg/proto"
)
func (v *Server) NotifyUser(_ context.Context, in *proto.NotifyRequest) (*proto.NotifyReply, error) {
client, err := services.GetThirdClientWithSecret(in.GetClientId(), in.GetClientSecret())
if err != nil {
return nil, err
}
func (v *App) NotifyUser(_ context.Context, in *proto.NotifyUserRequest) (*proto.NotifyResponse, error) {
var err error
var user models.Account
if user, err = services.GetAccount(uint(in.GetRecipientId())); err != nil {
return nil, err
if user, err = services.GetAccount(uint(in.GetUserId())); err != nil {
return nil, fmt.Errorf("unable to get account: %v", err)
}
var metadata map[string]any
_ = jsoniter.Unmarshal(in.GetMetadata(), &metadata)
links := lo.Map(in.GetLinks(), func(item *proto.NotifyLink, index int) models.NotificationLink {
return models.NotificationLink{
Label: item.Label,
Url: item.Url,
}
})
notification := models.Notification{
Type: lo.Ternary(len(in.GetType()) > 0, in.GetType(), "common"),
Subject: in.GetSubject(),
Content: in.GetContent(),
Metadata: metadata,
Links: links,
IsRealtime: in.GetIsRealtime(),
IsForcePush: in.GetIsForcePush(),
RecipientID: user.ID,
SenderID: &client.ID,
var nty pushkit.Notification
if err = json.Unmarshal(in.GetNotify().GetData(), &nty); err != nil {
return nil, fmt.Errorf("unable to unmarshal notification: %v", err)
}
if in.GetIsRealtime() {
notification := models.NewNotificationFromPushkit(nty)
notification.Account = user
notification.AccountID = user.ID
log.Debug().Str("topic", notification.Topic).Uint("uid", notification.AccountID).Msg("Notifying user...")
if in.GetNotify().GetUnsaved() {
if err := services.PushNotification(notification); err != nil {
return nil, err
}
@@ -53,5 +43,98 @@ func (v *Server) NotifyUser(_ context.Context, in *proto.NotifyRequest) (*proto.
}
}
return &proto.NotifyReply{IsSent: true}, nil
return &proto.NotifyResponse{
IsSuccess: true,
}, nil
}
func (v *App) NotifyUserBatch(_ context.Context, in *proto.NotifyUserBatchRequest) (*proto.NotifyResponse, error) {
var err error
var users []models.Account
if users, err = services.GetAccountList(lo.Map(in.GetUserId(), func(item uint64, index int) uint {
return uint(item)
})); err != nil {
return nil, fmt.Errorf("unable to get account: %v", err)
}
var nty pushkit.Notification
if err = json.Unmarshal(in.GetNotify().GetData(), &nty); err != nil {
return nil, fmt.Errorf("unable to unmarshal notification: %v", err)
}
var checklist = make(map[uint]bool, len(users))
var notifications []models.Notification
for _, user := range users {
if _, ok := checklist[user.ID]; ok {
continue
}
notification := models.NewNotificationFromPushkit(nty)
notification.Account = user
notification.AccountID = user.ID
checklist[user.ID] = true
notifications = append(notifications, notification)
}
if len(notifications) == 0 {
return &proto.NotifyResponse{
IsSuccess: true,
}, nil
}
log.Debug().Str("topic", notifications[0].Topic).Any("uid", lo.Keys(checklist)).Msg("Notifying users...")
if in.GetNotify().GetUnsaved() {
services.PushNotificationBatch(notifications)
} else {
if err := services.NewNotificationBatch(notifications); err != nil {
return nil, err
}
}
return &proto.NotifyResponse{
IsSuccess: true,
}, nil
}
func (v *App) NotifyAllUser(_ context.Context, in *proto.NotifyInfoPayload) (*proto.NotifyResponse, error) {
var users []models.Account
if err := database.C.Find(&users).Error; err != nil {
return nil, fmt.Errorf("unable to get account: %v", err)
}
var nty pushkit.Notification
if err := json.Unmarshal(in.GetData(), &nty); err != nil {
return nil, fmt.Errorf("unable to unmarshal notification: %v", err)
}
var checklist = make(map[uint]bool, len(users))
var notifications []models.Notification
for _, user := range users {
if checklist[user.ID] {
continue
}
notification := models.NewNotificationFromPushkit(nty)
notification.Account = user
notification.AccountID = user.ID
checklist[user.ID] = true
notifications = append(notifications, notification)
}
log.Debug().Str("topic", notifications[0].Topic).Any("uid", lo.Keys(checklist)).Msg("Notifying users...")
if in.GetUnsaved() {
services.PushNotificationBatch(notifications)
} else {
if err := services.NewNotificationBatch(notifications); err != nil {
return nil, err
}
}
return &proto.NotifyResponse{
IsSuccess: true,
}, nil
}

View File

@@ -3,59 +3,49 @@ package grpc
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"github.com/samber/lo"
"google.golang.org/protobuf/types/known/emptypb"
)
func (v *Server) ListCommunityRealm(ctx context.Context, empty *emptypb.Empty) (*proto.ListRealmResponse, error) {
realms, err := services.ListCommunityRealm()
if err != nil {
return nil, err
}
return &proto.ListRealmResponse{
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmResponse {
return &proto.RealmResponse{
Id: uint64(item.ID),
Alias: item.Alias,
Name: item.Name,
Description: item.Description,
IsPublic: item.IsPublic,
IsCommunity: item.IsCommunity,
}
}),
}, nil
}
func (v *Server) ListAvailableRealm(ctx context.Context, request *proto.RealmLookupWithUserRequest) (*proto.ListRealmResponse, error) {
func (v *App) ListAvailableRealm(ctx context.Context, request *proto.LookupUserRealmRequest) (*proto.ListRealmResponse, error) {
account, err := services.GetAccount(uint(request.GetUserId()))
if err != nil {
return nil, fmt.Errorf("unable to find target account: %v", err)
}
realms, err := services.ListAvailableRealm(account)
realms, err := services.ListAvailableRealm(account, request.GetIncludePublic())
if err != nil {
return nil, err
}
return &proto.ListRealmResponse{
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmResponse {
return &proto.RealmResponse{
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmInfo {
info := &proto.RealmInfo{
Id: uint64(item.ID),
Alias: item.Alias,
Name: item.Name,
Description: item.Description,
IsPublic: item.IsPublic,
IsCommunity: item.IsCommunity,
AccessPolicy: nex.EncodeMap(item.AccessPolicy),
}
if item.Avatar != nil {
info.Avatar = *item.Avatar
}
if item.Banner != nil {
info.Banner = *item.Banner
}
return info
}),
}, nil
}
func (v *Server) ListOwnedRealm(ctx context.Context, request *proto.RealmLookupWithUserRequest) (*proto.ListRealmResponse, error) {
func (v *App) ListOwnedRealm(ctx context.Context, request *proto.LookupUserRealmRequest) (*proto.ListRealmResponse, error) {
account, err := services.GetAccount(uint(request.GetUserId()))
if err != nil {
return nil, fmt.Errorf("unable to find target account: %v", err)
@@ -66,20 +56,56 @@ func (v *Server) ListOwnedRealm(ctx context.Context, request *proto.RealmLookupW
}
return &proto.ListRealmResponse{
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmResponse {
return &proto.RealmResponse{
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmInfo {
info := &proto.RealmInfo{
Id: uint64(item.ID),
Alias: item.Alias,
Name: item.Name,
Description: item.Description,
IsPublic: item.IsPublic,
IsCommunity: item.IsCommunity,
AccessPolicy: nex.EncodeMap(item.AccessPolicy),
}
if item.Avatar != nil {
info.Avatar = *item.Avatar
}
if item.Banner != nil {
info.Banner = *item.Banner
}
return info
}),
}, nil
}
func (v *Server) GetRealm(ctx context.Context, request *proto.RealmLookupRequest) (*proto.RealmResponse, error) {
func (v *App) ListRealm(ctx context.Context, request *proto.ListRealmRequest) (*proto.ListRealmResponse, error) {
var realms []models.Realm
if err := database.C.Where("id IN ?", request.GetId()).Find(&realms).Error; err != nil {
return nil, err
}
return &proto.ListRealmResponse{
Data: lo.Map(realms, func(item models.Realm, index int) *proto.RealmInfo {
info := &proto.RealmInfo{
Id: uint64(item.ID),
Alias: item.Alias,
Name: item.Name,
Description: item.Description,
IsPublic: item.IsPublic,
IsCommunity: item.IsCommunity,
AccessPolicy: nex.EncodeMap(item.AccessPolicy),
}
if item.Avatar != nil {
info.Avatar = *item.Avatar
}
if item.Banner != nil {
info.Banner = *item.Banner
}
return info
}),
}, nil
}
func (v *App) GetRealm(ctx context.Context, request *proto.LookupRealmRequest) (*proto.RealmInfo, error) {
var realm models.Realm
tx := database.C.Model(&models.Realm{})
@@ -100,19 +126,33 @@ func (v *Server) GetRealm(ctx context.Context, request *proto.RealmLookupRequest
return nil, err
}
return &proto.RealmResponse{
info := &proto.RealmInfo{
Id: uint64(realm.ID),
Alias: realm.Alias,
Name: realm.Name,
Description: realm.Description,
IsPublic: realm.IsPublic,
IsCommunity: realm.IsCommunity,
}, nil
AccessPolicy: nex.EncodeMap(realm.AccessPolicy),
}
if realm.Avatar != nil {
info.Avatar = *realm.Avatar
}
if realm.Banner != nil {
info.Banner = *realm.Banner
}
return info, nil
}
func (v *Server) ListRealmMember(ctx context.Context, request *proto.RealmMemberLookupRequest) (*proto.ListRealmMemberResponse, error) {
func (v *App) ListRealmMember(ctx context.Context, request *proto.RealmMemberLookupRequest) (*proto.ListRealmMemberResponse, error) {
var members []models.RealmMember
tx := database.C.Where("realm_id = ?", request.GetRealmId())
if request.UserId == nil && request.RealmId == nil {
return nil, fmt.Errorf("either user id or realm id must be provided")
}
tx := database.C
if request.RealmId != nil {
tx = tx.Where("realm_id = ?", request.GetRealmId())
}
if request.UserId != nil {
tx = tx.Where("account_id = ?", request.GetUserId())
}
@@ -122,8 +162,9 @@ func (v *Server) ListRealmMember(ctx context.Context, request *proto.RealmMember
}
return &proto.ListRealmMemberResponse{
Data: lo.Map(members, func(item models.RealmMember, index int) *proto.RealmMemberResponse {
return &proto.RealmMemberResponse{
Data: lo.Map(members, func(item models.RealmMember, index int) *proto.RealmMemberInfo {
return &proto.RealmMemberInfo{
Id: uint64(item.ID),
RealmId: uint64(item.RealmID),
UserId: uint64(item.AccountID),
PowerLevel: int32(item.PowerLevel),
@@ -132,9 +173,15 @@ func (v *Server) ListRealmMember(ctx context.Context, request *proto.RealmMember
}, nil
}
func (v *Server) GetRealmMember(ctx context.Context, request *proto.RealmMemberLookupRequest) (*proto.RealmMemberResponse, error) {
func (v *App) GetRealmMember(ctx context.Context, request *proto.RealmMemberLookupRequest) (*proto.RealmMemberInfo, error) {
var member models.RealmMember
tx := database.C.Where("realm_id = ?", request.GetRealmId())
if request.UserId == nil && request.RealmId == nil {
return nil, fmt.Errorf("either user id or realm id must be provided")
}
tx := database.C
if request.RealmId != nil {
tx = tx.Where("realm_id = ?", request.GetRealmId())
}
if request.UserId != nil {
tx = tx.Where("account_id = ?", request.GetUserId())
}
@@ -143,9 +190,27 @@ func (v *Server) GetRealmMember(ctx context.Context, request *proto.RealmMemberL
return nil, err
}
return &proto.RealmMemberResponse{
return &proto.RealmMemberInfo{
Id: uint64(member.ID),
RealmId: uint64(member.RealmID),
UserId: uint64(member.AccountID),
PowerLevel: int32(member.PowerLevel),
}, nil
}
func (v *App) CheckRealmMemberPerm(ctx context.Context, request *proto.CheckRealmPermRequest) (*proto.CheckRealmPermResponse, error) {
var member models.RealmMember
tx := database.C.
Where("realm_id = ?", request.GetRealmId()).
Where("account_id = ?", request.GetUserId())
if err := tx.First(&member).Error; err != nil {
return &proto.CheckRealmPermResponse{
IsSuccess: false,
}, nil
}
return &proto.CheckRealmPermResponse{
IsSuccess: member.PowerLevel >= int(request.GetPowerLevel()),
}, nil
}

View File

@@ -1,43 +1,53 @@
package grpc
import (
"google.golang.org/grpc/reflection"
"net"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"google.golang.org/grpc/reflection"
nroto "git.solsynth.dev/hypernet/nexus/pkg/proto"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"github.com/spf13/viper"
"google.golang.org/grpc"
health "google.golang.org/grpc/health/grpc_health_v1"
)
import health "google.golang.org/grpc/health/grpc_health_v1"
type Server struct {
proto.UnimplementedAuthServer
proto.UnimplementedNotifyServer
proto.UnimplementedFriendshipsServer
proto.UnimplementedRealmsServer
type App struct {
nroto.UnimplementedAuthServiceServer
nroto.UnimplementedDirectoryServiceServer
nroto.UnimplementedUserServiceServer
nroto.UnimplementedStreamServiceServer
proto.UnimplementedRealmServiceServer
proto.UnimplementedAuditServiceServer
proto.UnimplementedNotifyServiceServer
proto.UnimplementedThirdClientServiceServer
health.UnimplementedHealthServer
srv *grpc.Server
}
func NewServer() *Server {
server := &Server{
func NewServer() *App {
server := &App{
srv: grpc.NewServer(),
}
proto.RegisterAuthServer(server.srv, &Server{})
proto.RegisterNotifyServer(server.srv, &Server{})
proto.RegisterFriendshipsServer(server.srv, &Server{})
proto.RegisterRealmsServer(server.srv, &Server{})
health.RegisterHealthServer(server.srv, &Server{})
nroto.RegisterAuthServiceServer(server.srv, server)
nroto.RegisterUserServiceServer(server.srv, server)
nroto.RegisterDirectoryServiceServer(server.srv, server)
nroto.RegisterStreamServiceServer(server.srv, server)
proto.RegisterNotifyServiceServer(server.srv, server)
proto.RegisterRealmServiceServer(server.srv, server)
proto.RegisterAuditServiceServer(server.srv, server)
proto.RegisterThirdClientServiceServer(server.srv, server)
health.RegisterHealthServer(server.srv, server)
reflection.Register(server.srv)
return server
}
func (v *Server) Listen() error {
func (v *App) Listen() error {
listener, err := net.Listen("tcp", viper.GetString("grpc_bind"))
if err != nil {
return err

125
pkg/internal/grpc/stream.go Normal file
View File

@@ -0,0 +1,125 @@
package grpc
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"git.solsynth.dev/hypernet/passport/pkg/internal/gap"
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
"git.solsynth.dev/hypernet/passport/pkg/internal/web/exts"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
)
func (v *App) BroadcastEvent(ctx context.Context, request *proto.EventInfo) (*proto.EventResponse, error) {
log.Debug().Str("event", request.GetEvent()).
Msg("Got a broadcasting event...")
switch request.GetEvent() {
// Last seen at
case "ws.client.register":
// No longer need update user online status
// Based on realtime sever connection status
break
case "ws.client.unregister":
// Update user last seen at
data := nex.DecodeMap(request.GetData())
err := services.SetAccountLastSeen(uint(data["user"].(float64)))
log.Debug().Err(err).Any("event", data).Msg("Setting account last seen...")
}
return &proto.EventResponse{}, nil
}
func (v *App) PushStream(_ context.Context, request *proto.PushStreamRequest) (*proto.PushStreamResponse, error) {
sc := proto.NewStreamServiceClient(gap.Nx.GetNexusGrpcConn())
var in nex.WebSocketPackage
if err := jsoniter.Unmarshal(request.GetBody(), &in); err != nil {
return nil, err
}
switch in.Action {
// PaKex (Key Exchange)
case "kex.ask":
var data struct {
UserID uint `json:"user_id" validate:"required"`
KeypairID string `json:"keypair_id" validate:"required"`
ClientID string `json:"client_id" validate:"required"`
}
err := jsoniter.Unmarshal(in.RawPayload(), &data)
if request.ClientId != nil {
data.ClientID = *request.ClientId
}
if err == nil {
err = exts.ValidateStruct(data)
}
if err != nil {
_, _ = sc.PushStream(context.Background(), &proto.PushStreamRequest{
ClientId: request.ClientId,
Body: nex.WebSocketPackage{
Action: "error",
Message: fmt.Sprintf("unable parse payload: %v", err),
}.Marshal(),
})
break
}
// Forward ask request
sc.PushStream(context.Background(), &proto.PushStreamRequest{
UserId: lo.ToPtr(uint64(data.UserID)),
Body: nex.WebSocketPackage{
Action: "kex.ask",
Payload: data,
}.Marshal(),
})
case "kex.ack":
var data struct {
UserID uint `json:"user_id" validate:"required"`
KeypairID string `json:"keypair_id" validate:"required"`
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
ClientID string `json:"client_id" validate:"required"`
}
err := jsoniter.Unmarshal(in.RawPayload(), &data)
if err == nil {
err = exts.ValidateStruct(data)
}
if err != nil {
_, _ = sc.PushStream(context.Background(), &proto.PushStreamRequest{
ClientId: request.ClientId,
Body: nex.WebSocketPackage{
Action: "error",
Message: fmt.Sprintf("unable parse payload: %v", err),
}.Marshal(),
})
break
}
if len(data.PublicKey) == 0 && len(data.PrivateKey) == 0 {
_, _ = sc.PushStream(context.Background(), &proto.PushStreamRequest{
ClientId: request.ClientId,
Body: nex.WebSocketPackage{
Action: "error",
Message: "one of public key and private key is required",
}.Marshal(),
})
break
}
// Forward ack request
sc.PushStream(context.Background(), &proto.PushStreamRequest{
ClientId: &data.ClientID,
Body: nex.WebSocketPackage{
Action: "kex.ack",
Payload: data,
}.Marshal(),
})
}
return &proto.PushStreamResponse{}, nil
}

View File

@@ -0,0 +1,42 @@
package grpc
import (
"context"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"git.solsynth.dev/hypernet/passport/pkg/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (v *App) GetThirdClient(ctx context.Context, request *proto.GetThirdClientRequest) (*proto.GetThirdClientResponse, error) {
tx := database.C
if request.Id == nil && request.Alias == nil {
return nil, status.Error(codes.InvalidArgument, "either id or alias must be specified")
}
if request.Id != nil {
tx = tx.Where("id = ?", request.Id)
} else if request.Alias != nil {
tx = tx.Where("alias = ?", request.Alias)
}
var client models.ThirdClient
if err := tx.First(&client).Error; err != nil {
return nil, status.Errorf(codes.NotFound, "requested client was not found")
}
if request.Secret != nil {
if client.Secret != request.GetSecret() {
return nil, status.Errorf(codes.PermissionDenied, "invalid secret")
}
}
return &proto.GetThirdClientResponse{
Info: &proto.ThirdClientInfo{
Id: uint64(client.ID),
Name: client.Name,
Description: client.Description,
},
}, nil
}

77
pkg/internal/grpc/user.go Normal file
View File

@@ -0,0 +1,77 @@
package grpc
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
"github.com/samber/lo"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (v *App) GetUser(ctx context.Context, request *proto.GetUserRequest) (*proto.UserInfo, error) {
var account models.Account
var err error
if request.UserId != nil {
account, err = services.GetAccountForEnd(uint(request.GetUserId()))
} else if request.Name != nil {
account, err = services.GetAccountForEnd(request.GetName())
}
if err != nil {
return nil, status.Errorf(codes.NotFound, fmt.Sprintf("unable to get account punishments: %v", err))
}
return account.EncodeToUserInfo(), nil
}
func (v *App) ListUser(ctx context.Context, request *proto.ListUserRequest) (*proto.MultipleUserInfo, error) {
var accounts []models.Account
if err := database.C.
Where("id IN ?", lo.Map(request.GetUserId(), func(id uint64, _ int) interface{} { return id })).
Find(&accounts).Error; err != nil {
return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to list users: %v", err))
}
return &proto.MultipleUserInfo{
Data: lo.Map(request.GetUserId(), func(item uint64, index int) *proto.UserInfo {
val, ok := lo.Find(accounts, func(x models.Account) bool {
return uint(item) == x.ID
})
if !ok {
return nil
}
return val.EncodeToUserInfo()
}),
}, nil
}
func (v *App) ListUserRelative(ctx context.Context, request *proto.ListUserRelativeRequest) (*proto.ListUserRelativeResponse, error) {
tx := database.C.Preload("Account").Preload("Related").Where("status = ?", request.GetStatus())
if request.GetIsRelated() {
tx = tx.Where("related_id = ?", request.GetUserId())
} else {
tx = tx.Where("account_id = ?", request.GetUserId())
}
var data []models.AccountRelationship
if err := tx.Find(&data).Error; err != nil {
return nil, err
}
return &proto.ListUserRelativeResponse{
Data: lo.Map(data, func(item models.AccountRelationship, index int) *proto.UserInfo {
account := lo.Ternary(request.GetIsRelated(), item.Account, item.Related)
val := &proto.UserInfo{
Id: uint64(account.ID),
Name: account.Name,
}
return val
}),
}, nil
}

View File

@@ -1,16 +0,0 @@
package i18n
import (
jsoniter "github.com/json-iterator/go"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
var Bundle *i18n.Bundle
func InitInternationalization() {
Bundle = i18n.NewBundle(language.English)
Bundle.RegisterUnmarshalFunc("json", jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal)
Bundle.LoadMessageFileFS(FS, "locale.en.json")
Bundle.LoadMessageFileFS(FS, "locale.zh.json")
}

View File

@@ -1,6 +0,0 @@
package i18n
import "embed"
//go:embed locale.*.json
var FS embed.FS

View File

@@ -1,23 +0,0 @@
{
"next": "Next",
"email": "Email",
"username": "Username",
"nickname": "Nickname",
"password": "Password",
"unknown": "Unknown",
"apply": "Apply",
"back": "Back",
"approve": "Approve",
"decline": "Decline",
"magicToken": "Magic Token",
"signinTitle": "Sign In",
"signinCaption": "Sign in to Solarpass to explore entire Solar Network. Explore posts, discover communities, talk with your best friends. All these things in the Solar Network!",
"signinRequired": "You need to sign in before do that.",
"signupTitle": "Sign Up",
"signupCaption": "Sign up to create an account on Solarpass, then you can explore the entire Solar Network! Enjoy the next-generation Internet Ecosystem!",
"authorizeTitle": "Authorize",
"authorizeCaption": "One Solarpass, get entire network.",
"mfaTitle": "Multi Factor Authenticate",
"mfaCaption": "We need use one more way to verify it is you.",
"mfaFactorEmail": "OTP through your email"
}

View File

@@ -1,23 +0,0 @@
{
"next": "下一步",
"email": "邮件地址",
"username": "用户名",
"nickname": "昵称",
"password": "密码",
"unknown": "未知",
"apply": "应用",
"back": "返回",
"approve": "接受",
"decline": "拒绝",
"magicToken": "魔法令牌",
"signinTitle": "登陆",
"signinCaption": "登陆 Solarpass 以探索整个 Solar Network浏览帖子、探索社区、和你的好朋友聊八卦一切尽在 Solar Network!",
"signinRequired": "你需要在那之前登陆",
"signupTitle": "注册",
"signupCaption": "注册以在 Solarpass 创建一个账号,之后你就可以探索整个 Solar Network享受下一代互联网生态系统",
"authorizeTitle": "授权",
"authorizeCaption": "一个 Solarpass整个网络。",
"mfaTitle": "多因素验证",
"mfaCaption": "我们需要另一个方法来确认你是你。",
"mfaFactorEmail": "电子邮寄一次性验证码"
}

View File

@@ -1,15 +0,0 @@
package i18n
import (
"github.com/gofiber/fiber/v2"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
func I18nMiddleware(c *fiber.Ctx) error {
accept := c.Get(fiber.HeaderAcceptLanguage)
localizer := i18n.NewLocalizer(Bundle, accept)
c.Locals("localizer", localizer)
return c.Next()
}

View File

@@ -1,81 +0,0 @@
package models
import (
"fmt"
"time"
"github.com/samber/lo"
"github.com/spf13/viper"
"gorm.io/datatypes"
)
type Account struct {
BaseModel
Name string `json:"name" gorm:"uniqueIndex"`
Nick string `json:"nick"`
Description string `json:"description"`
Avatar *uint `json:"avatar"`
Banner *uint `json:"banner"`
ConfirmedAt *time.Time `json:"confirmed_at"`
PermNodes datatypes.JSONMap `json:"perm_nodes"`
Profile AccountProfile `json:"profile"`
Statuses []Status `json:"statuses"`
Badges []Badge `json:"badges"`
Contacts []AccountContact `json:"contacts"`
RealmIdentities []RealmMember `json:"realm_identities"`
Tickets []AuthTicket `json:"tickets"`
Factors []AuthFactor `json:"factors"`
Events []ActionEvent `json:"events"`
MagicTokens []MagicToken `json:"-"`
ThirdClients []ThirdClient `json:"clients"`
Notifications []Notification `json:"notifications" gorm:"foreignKey:RecipientID"`
NotifySubscribers []NotificationSubscriber `json:"notify_subscribers"`
Friendships []AccountFriendship `json:"friendships" gorm:"foreignKey:AccountID"`
RelatedFriendships []AccountFriendship `json:"related_friendships" gorm:"foreignKey:RelatedID"`
}
func (v Account) GetAvatar() *string {
if v.Avatar != nil {
return lo.ToPtr(fmt.Sprintf("%s/api/attachments/%d", viper.GetString("content_endpoint"), *v.Avatar))
}
return nil
}
func (v Account) GetBanner() *string {
if v.Banner != nil {
return lo.ToPtr(fmt.Sprintf("%s/api/attachments/%d", viper.GetString("content_endpoint"), *v.Banner))
}
return nil
}
func (v Account) GetPrimaryEmail() AccountContact {
val, _ := lo.Find(v.Contacts, func(item AccountContact) bool {
return item.Type == EmailAccountContact && item.IsPrimary
})
return val
}
type AccountContactType = int8
const (
EmailAccountContact = AccountContactType(iota)
)
type AccountContact struct {
BaseModel
Type int8 `json:"type"`
Content string `json:"content" gorm:"uniqueIndex"`
IsPublic bool `json:"is_public"`
IsPrimary bool `json:"is_primary"`
VerifiedAt *time.Time `json:"verified_at"`
AccountID uint `json:"account_id"`
}

View File

@@ -1,64 +0,0 @@
package models
import (
"fmt"
"time"
"gorm.io/datatypes"
)
type AuthFactorType = int8
const (
PasswordAuthFactor = AuthFactorType(iota)
EmailPasswordFactor
)
type AuthFactor struct {
BaseModel
Type int8 `json:"type"`
Secret string `json:"-"`
Config JSONMap `json:"config"`
AccountID uint `json:"account_id"`
}
type AuthTicket struct {
BaseModel
Location string `json:"location"`
IpAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
RequireMFA bool `json:"require_mfa"`
RequireAuthenticate bool `json:"require_authenticate"`
Claims datatypes.JSONSlice[string] `json:"claims"`
Audiences datatypes.JSONSlice[string] `json:"audiences"`
GrantToken *string `json:"grant_token"`
AccessToken *string `json:"access_token"`
RefreshToken *string `json:"refresh_token"`
ExpiredAt *time.Time `json:"expired_at"`
AvailableAt *time.Time `json:"available_at"`
LastGrantAt *time.Time `json:"last_grant_at"`
ClientID *uint `json:"client_id"`
AccountID uint `json:"account_id"`
}
func (v AuthTicket) IsAvailable() error {
if v.RequireMFA || v.RequireAuthenticate {
return fmt.Errorf("ticket isn't authenticated yet")
}
if v.AvailableAt != nil && time.Now().Unix() < v.AvailableAt.Unix() {
return fmt.Errorf("ticket isn't available yet")
}
if v.ExpiredAt != nil && time.Now().Unix() > v.ExpiredAt.Unix() {
return fmt.Errorf("ticket expired")
}
return nil
}
type AuthContext struct {
Ticket AuthTicket `json:"ticket"`
Account Account `json:"account"`
LastUsedAt time.Time `json:"last_used_at"`
}

View File

@@ -1,18 +0,0 @@
package models
import "gorm.io/datatypes"
type ThirdClient struct {
BaseModel
Alias string `json:"alias" gorm:"uniqueIndex"`
Name string `json:"name"`
Description string `json:"description"`
Secret string `json:"secret"`
Urls datatypes.JSONSlice[string] `json:"urls"`
Callbacks datatypes.JSONSlice[string] `json:"callbacks"`
Sessions []AuthTicket `json:"tickets" gorm:"foreignKey:ClientID"`
Notifications []Notification `json:"notifications" gorm:"foreignKey:SenderID"`
IsDraft bool `json:"is_draft"`
AccountID *uint `json:"account_id"`
}

View File

@@ -1,12 +0,0 @@
package models
type ActionEvent struct {
BaseModel
Type string `json:"type"`
Target string `json:"target"`
Location string `json:"location"`
IpAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
AccountID uint `json:"account_id"`
}

View File

@@ -1,20 +0,0 @@
package models
type FriendshipStatus = int8
const (
FriendshipPending = FriendshipStatus(iota)
FriendshipActive
FriendshipBlocked
)
type AccountFriendship struct {
BaseModel
AccountID uint `json:"account_id"`
RelatedID uint `json:"related_id"`
BlockedBy *uint `json:"blocked_by"`
Account Account `json:"account"`
Related Account `json:"related"`
Status FriendshipStatus `json:"status"`
}

View File

@@ -1,40 +0,0 @@
package models
import (
"gorm.io/datatypes"
)
type Notification struct {
BaseModel
Type string `json:"type"`
Subject string `json:"subject"`
Content string `json:"content"`
Metadata datatypes.JSONMap `json:"metadata"`
Links datatypes.JSONSlice[NotificationLink] `json:"links"`
IsRealtime bool `json:"is_realtime" gorm:"-"`
IsForcePush bool `json:"is_force_push" gorm:"-"`
SenderID *uint `json:"sender_id"`
RecipientID uint `json:"recipient_id"`
}
// NotificationLink Used to embed into notify and render actions
type NotificationLink struct {
Label string `json:"label"`
Url string `json:"url"`
}
const (
NotifySubscriberFirebase = "firebase"
NotifySubscriberAPNs = "apple"
)
type NotificationSubscriber struct {
BaseModel
UserAgent string `json:"user_agent"`
Provider string `json:"provider"`
DeviceID string `json:"device_id" gorm:"uniqueIndex"`
DeviceToken string `json:"device_token"`
AccountID uint `json:"account_id"`
}

View File

@@ -1,16 +0,0 @@
package models
import (
"time"
)
type AccountProfile struct {
BaseModel
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Experience uint64 `json:"experience"`
LastSeenAt *time.Time `json:"last_seen_at"`
Birthday *time.Time `json:"birthday"`
AccountID uint `json:"account_id"`
}

View File

@@ -1,23 +0,0 @@
package models
type Realm struct {
BaseModel
Alias string `json:"alias" gorm:"uniqueIndex"`
Name string `json:"name"`
Description string `json:"description"`
Members []RealmMember `json:"members"`
IsPublic bool `json:"is_public"`
IsCommunity bool `json:"is_community"`
AccountID uint `json:"account_id"`
}
type RealmMember struct {
BaseModel
RealmID uint `json:"realm_id"`
AccountID uint `json:"account_id"`
Realm Realm `json:"realm"`
Account Account `json:"account"`
PowerLevel int `json:"power_level"`
}

View File

@@ -1,20 +0,0 @@
package models
import "time"
type MagicTokenType = int8
const (
ConfirmMagicToken = MagicTokenType(iota)
RegistrationMagicToken
ResetPasswordMagicToken
)
type MagicToken struct {
BaseModel
Code string `json:"code"`
Type int8 `json:"type"`
AccountID *uint `json:"account_id"`
ExpiredAt *time.Time `json:"expired_at"`
}

View File

@@ -1,21 +0,0 @@
package models
import jsoniter "github.com/json-iterator/go"
type UnifiedCommand struct {
Action string `json:"w"`
Message string `json:"m"`
Payload any `json:"p"`
}
func UnifiedCommandFromError(err error) UnifiedCommand {
return UnifiedCommand{
Action: "error",
Message: err.Error(),
}
}
func (v UnifiedCommand) Marshal() []byte {
data, _ := jsoniter.Marshal(v)
return data
}

View File

@@ -1,15 +0,0 @@
package admin
import (
"github.com/gofiber/fiber/v2"
)
func MapAdminAPIs(app *fiber.App) {
admin := app.Group("/api/admin")
{
admin.Post("/badges", grantBadge)
admin.Delete("/badges/:badgeId", revokeBadge)
admin.Post("/notify/all", notifyAllUser)
}
}

View File

@@ -1,61 +0,0 @@
package admin
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
)
func notifyAllUser(c *fiber.Ctx) error {
var data struct {
Type string `json:"type" validate:"required"`
Subject string `json:"subject" validate:"required,max=1024"`
Content string `json:"content" validate:"required,max=4096"`
Metadata map[string]any `json:"metadata"`
Links []models.NotificationLink `json:"links"`
IsForcePush bool `json:"is_force_push"`
IsRealtime bool `json:"is_realtime"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
if err := exts.EnsureGrantedPerm(c, "AdminNotifyAll", true); err != nil {
return err
}
var users []models.Account
if err := database.C.Find(&users).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
go func() {
for _, user := range users {
notification := models.Notification{
Type: data.Type,
Subject: data.Subject,
Content: data.Content,
Links: data.Links,
IsRealtime: data.IsRealtime,
IsForcePush: data.IsForcePush,
RecipientID: user.ID,
}
if data.IsRealtime {
if err := services.PushNotification(notification); err != nil {
log.Error().Err(err).Uint("user", user.ID).Msg("Failed to push notification...")
}
} else {
if err := services.NewNotification(notification); err != nil {
log.Error().Err(err).Uint("user", user.ID).Msg("Failed to create notification...")
}
}
}
}()
return c.SendStatus(fiber.StatusOK)
}

View File

@@ -1,187 +0,0 @@
package api
import (
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"strconv"
"time"
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"
jsoniter "github.com/json-iterator/go"
"github.com/spf13/viper"
)
func lookupAccount(c *fiber.Ctx) error {
probe := c.Query("probe")
if len(probe) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "you must provide a probe")
}
user, err := services.LookupAccount(probe)
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
return c.JSON(user)
}
func getUserinfo(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
var data models.Account
if err := database.C.
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
Preload("Profile").
Preload("Contacts").
Preload("Badges").
First(&data).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
var resp fiber.Map
raw, _ := jsoniter.Marshal(data)
jsoniter.Unmarshal(raw, &resp)
resp["sub"] = strconv.Itoa(int(data.ID))
resp["family_name"] = data.Profile.FirstName
resp["given_name"] = data.Profile.LastName
resp["name"] = data.Name
resp["email"] = data.GetPrimaryEmail().Content
resp["preferred_username"] = data.Nick
if data.Avatar != nil {
resp["picture"] = *data.GetAvatar()
}
return c.JSON(resp)
}
func getEvents(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
var count int64
var events []models.ActionEvent
if err := database.C.
Where(&models.ActionEvent{AccountID: user.ID}).
Model(&models.ActionEvent{}).
Count(&count).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
if err := database.C.
Order("created_at desc").
Where(&models.ActionEvent{AccountID: user.ID}).
Limit(take).
Offset(offset).
Find(&events).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return c.JSON(fiber.Map{
"count": count,
"data": events,
})
}
func editUserinfo(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
var data struct {
Nick string `json:"nick" validate:"required,min=4,max=24"`
Description string `json:"description"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Birthday time.Time `json:"birthday"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
var account models.Account
if err := database.C.
Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}).
Preload("Profile").
First(&account).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
account.Nick = data.Nick
account.Description = data.Description
account.Profile.FirstName = data.FirstName
account.Profile.LastName = data.LastName
account.Profile.Birthday = &data.Birthday
if err := database.C.Save(&account).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else if err := database.C.Save(&account.Profile).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
services.InvalidAuthCacheWithUser(account.ID)
return c.SendStatus(fiber.StatusOK)
}
func doRegister(c *fiber.Ctx) error {
var data struct {
Name string `json:"name" validate:"required,lowercase,alphanum,min=4,max=16"`
Nick string `json:"nick" validate:"required,min=4,max=24"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=4,max=32"`
MagicToken string `json:"magic_token"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
} else if viper.GetBool("use_registration_magic_token") && len(data.MagicToken) <= 0 {
return fmt.Errorf("missing magic token in request")
} else if viper.GetBool("use_registration_magic_token") {
if tk, err := services.ValidateMagicToken(data.MagicToken, models.RegistrationMagicToken); err != nil {
return err
} else {
database.C.Delete(&tk)
}
}
if user, err := services.CreateAccount(
data.Name,
data.Nick,
data.Email,
data.Password,
); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(user)
}
}
func doRegisterConfirm(c *fiber.Ctx) error {
var data struct {
Code string `json:"code" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
if err := services.ConfirmAccount(data.Code); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.SendStatus(fiber.StatusOK)
}

View File

@@ -1,112 +0,0 @@
package api
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/paperclip/pkg/proto"
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/gap"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func setAvatar(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
var data struct {
AttachmentID uint `json:"attachment" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
pc, err := gap.DiscoverPaperclip()
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "attachments services was not available")
}
if _, err := proto.NewAttachmentsClient(pc).CheckAttachmentExists(context.Background(), &proto.AttachmentLookupRequest{
Id: lo.ToPtr(uint64(data.AttachmentID)),
Usage: lo.ToPtr("p.avatar"),
}); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("avatar was not found in repository: %v", err))
}
user.Avatar = &data.AttachmentID
if err := database.C.Save(&user).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
services.InvalidAuthCacheWithUser(user.ID)
}
return c.SendStatus(fiber.StatusOK)
}
func setBanner(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
var data struct {
AttachmentID uint `json:"attachment" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
pc, err := gap.DiscoverPaperclip()
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "attachments services was not available")
}
if _, err := proto.NewAttachmentsClient(pc).CheckAttachmentExists(context.Background(), &proto.AttachmentLookupRequest{
Id: lo.ToPtr(uint64(data.AttachmentID)),
Usage: lo.ToPtr("p.banner"),
}); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("banner was not found in repository: %v", err))
}
user.Banner = &data.AttachmentID
if err := database.C.Save(&user).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
services.InvalidAuthCacheWithUser(user.ID)
}
return c.SendStatus(fiber.StatusOK)
}
func getAvatar(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
if content := user.GetAvatar(); content == nil {
return c.SendStatus(fiber.StatusNotFound)
} else {
return c.Redirect(*content, fiber.StatusFound)
}
}
func getBanner(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
if content := user.GetBanner(); content == nil {
return c.SendStatus(fiber.StatusNotFound)
} else {
return c.Redirect(*content, fiber.StatusFound)
}
}

View File

@@ -1,138 +0,0 @@
package api
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"
)
func listFriendship(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
status := c.QueryInt("status", -1)
var err error
var friends []models.AccountFriendship
if status < 0 {
if friends, err = services.ListAllFriend(user); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
} else {
if friends, err = services.ListFriend(user, models.FriendshipStatus(status)); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
}
return c.JSON(friends)
}
func getFriendship(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
relatedId, _ := c.ParamsInt("relatedId", 0)
related, err := services.GetAccount(uint(relatedId))
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if friend, err := services.GetFriendWithTwoSides(user.ID, related.ID); err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
} else {
return c.JSON(friend)
}
}
func makeFriendship(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
relatedName := c.Query("related")
relatedId, _ := c.ParamsInt("relatedId", 0)
var err error
var related models.Account
if relatedId > 0 {
related, err = services.GetAccount(uint(relatedId))
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
} else if len(relatedName) > 0 {
related, err = services.LookupAccount(relatedName)
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
} else {
return fiber.NewError(fiber.StatusBadRequest, "must one of username or user id")
}
friend, err := services.NewFriend(user, related, models.FriendshipPending)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(friend)
}
}
func editFriendship(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
relatedId, _ := c.ParamsInt("relatedId", 0)
var data struct {
Status uint8 `json:"status"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
related, err := services.GetAccount(uint(relatedId))
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
friendship, err := services.GetFriendWithTwoSides(user.ID, related.ID)
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
originalStatus := friendship.Status
friendship.Status = models.FriendshipStatus(data.Status)
if friendship, err := services.EditFriendWithCheck(friendship, user, originalStatus); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(friendship)
}
}
func deleteFriendship(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
relatedId, _ := c.ParamsInt("relatedId", 0)
related, err := services.GetAccount(uint(relatedId))
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
friendship, err := services.GetFriendWithTwoSides(user.ID, related.ID)
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if err := services.DeleteFriend(friendship); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(friendship)
}
}

View File

@@ -1,113 +0,0 @@
package api
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2"
)
func MapAPIs(app *fiber.App) {
app.Get("/.well-known/openid-configuration", getOidcConfiguration)
api := app.Group("/api").Name("API")
{
notify := api.Group("/notifications").Name("Notifications API")
{
notify.Get("/", getNotifications)
notify.Post("/subscribe", addNotifySubscriber)
notify.Put("/batch/read", markNotificationReadBatch)
notify.Put("/:notificationId/read", markNotificationRead)
}
api.Get("/users/lookup", lookupAccount)
me := api.Group("/users/me").Name("Myself Operations")
{
me.Get("/avatar", getAvatar)
me.Get("/banner", getBanner)
me.Put("/avatar", setAvatar)
me.Put("/banner", setBanner)
me.Get("/", getUserinfo)
me.Put("/", editUserinfo)
me.Get("/events", getEvents)
me.Get("/tickets", getTickets)
me.Delete("/tickets/:ticketId", killTicket)
me.Post("/confirm", doRegisterConfirm)
me.Post("/password-reset", requestResetPassword)
me.Patch("/password-reset", confirmResetPassword)
me.Get("/status", getMyselfStatus)
me.Post("/status", setStatus)
me.Put("/status", editStatus)
me.Delete("/status", clearStatus)
friends := me.Group("/friends").Name("Friends")
{
friends.Get("/", listFriendship)
friends.Get("/:relatedId", getFriendship)
friends.Post("/", makeFriendship)
friends.Post("/:relatedId", makeFriendship)
friends.Put("/:relatedId", editFriendship)
friends.Delete("/:relatedId", deleteFriendship)
}
}
directory := api.Group("/users/:alias").Name("User Directory")
{
directory.Get("/", getOtherUserinfo)
directory.Get("/status", getStatus)
}
api.Post("/users", doRegister)
auth := api.Group("/auth").Name("Auth")
{
auth.Post("/", doAuthenticate)
auth.Post("/mfa", doMultiFactorAuthenticate)
auth.Post("/token", getToken)
auth.Get("/tickets/:ticketId", getTicket)
auth.Get("/factors", getAvailableFactors)
auth.Post("/factors/:factorId", requestFactorToken)
auth.Get("/o/authorize", tryAuthorizeThirdClient)
auth.Post("/o/authorize", authorizeThirdClient)
}
realms := api.Group("/realms").Name("Realms API")
{
realms.Get("/", listCommunityRealm)
realms.Get("/me", listOwnedRealm)
realms.Get("/me/available", listAvailableRealm)
realms.Get("/:realm", getRealm)
realms.Get("/:realm/members", listRealmMembers)
realms.Get("/:realm/members/me", getMyRealmMember)
realms.Post("/", createRealm)
realms.Put("/:realmId", editRealm)
realms.Delete("/:realmId", deleteRealm)
realms.Post("/:realm/members", addRealmMember)
realms.Delete("/:realm/members", removeRealmMember)
realms.Delete("/:realm/members/me", leaveRealm)
}
developers := api.Group("/dev").Name("Developers API")
{
developers.Post("/notify", notifyUser)
}
api.Use(func(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
return c.Next()
}).Get("/ws", websocket.New(listenWebsocket))
api.All("/*", func(c *fiber.Ctx) error {
return fiber.ErrNotFound
})
}
}

View File

@@ -1,128 +0,0 @@
package api
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"
)
func getNotifications(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
tx := database.C.Where(&models.Notification{RecipientID: user.ID}).Model(&models.Notification{})
var count int64
var notifications []models.Notification
if err := tx.
Count(&count).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
if err := tx.
Limit(take).
Offset(offset).
Find(&notifications).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return c.JSON(fiber.Map{
"count": count,
"data": notifications,
})
}
func markNotificationRead(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
id, _ := c.ParamsInt("notificationId", 0)
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
var notify models.Notification
if err := database.C.Where(&models.Notification{
BaseModel: models.BaseModel{ID: uint(id)},
RecipientID: user.ID,
}).First(&notify).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if err := database.C.Delete(&notify).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.SendStatus(fiber.StatusOK)
}
}
func markNotificationReadBatch(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
var data struct {
MessageIDs []uint `json:"messages"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if err := database.C.Model(&models.Notification{}).
Where("recipient_id = ? AND id IN ?", user.ID, data.MessageIDs).
Delete(&models.Notification{}).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.SendStatus(fiber.StatusOK)
}
}
func addNotifySubscriber(c *fiber.Ctx) error {
if err := exts.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
var data struct {
Provider string `json:"provider" validate:"required"`
DeviceToken string `json:"device_token" validate:"required"`
DeviceID string `json:"device_id" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
var count int64
if err := database.C.Where(&models.NotificationSubscriber{
DeviceID: data.DeviceID,
DeviceToken: data.DeviceToken,
AccountID: user.ID,
}).Model(&models.NotificationSubscriber{}).Count(&count).Error; err != nil || count > 0 {
return c.SendStatus(fiber.StatusOK)
}
subscriber, err := services.AddNotifySubscriber(
user,
data.Provider,
data.DeviceID,
data.DeviceToken,
c.Get(fiber.HeaderUserAgent),
)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(subscriber)
}

View File

@@ -1,60 +0,0 @@
package api
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"
)
func notifyUser(c *fiber.Ctx) error {
var data struct {
ClientID string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"`
Type string `json:"type" validate:"required"`
Subject string `json:"subject" validate:"required,max=1024"`
Content string `json:"content" validate:"required,max=4096"`
Metadata map[string]any `json:"metadata"`
Links []models.NotificationLink `json:"links"`
IsForcePush bool `json:"is_force_push"`
IsRealtime bool `json:"is_realtime"`
UserID uint `json:"user_id" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
client, err := services.GetThirdClientWithSecret(data.ClientID, data.ClientSecret)
if err != nil {
return fiber.NewError(fiber.StatusForbidden, err.Error())
}
var user models.Account
if user, err = services.GetAccount(data.UserID); err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
notification := models.Notification{
Type: data.Type,
Subject: data.Subject,
Content: data.Content,
Links: data.Links,
IsRealtime: data.IsRealtime,
IsForcePush: data.IsForcePush,
RecipientID: user.ID,
SenderID: &client.ID,
}
if data.IsRealtime {
if err := services.PushNotification(notification); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
} else {
if err := services.NewNotification(notification); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
}
return c.SendStatus(fiber.StatusOK)
}

View File

@@ -1,23 +0,0 @@
package api
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"github.com/gofiber/fiber/v2"
)
func getOtherUserinfo(c *fiber.Ctx) error {
alias := c.Params("alias")
var account models.Account
if err := database.C.
Where(&models.Account{Name: alias}).
Omit("tickets", "challenges", "factors", "events", "clients", "notifications", "notify_subscribers").
Preload("Profile").
Preload("Badges").
First(&account).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(account)
}

View File

@@ -1,82 +0,0 @@
package api
import (
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/contrib/websocket"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
)
func listenWebsocket(c *websocket.Conn) {
user := c.Locals("user").(models.Account)
// Push connection
services.ClientRegister(user, c)
log.Debug().Uint("user", user.ID).Msg("New websocket connection established...")
// Event loop
var task models.UnifiedCommand
var messageType int
var payload []byte
var packet []byte
var err error
for {
if messageType, packet, err = c.ReadMessage(); err != nil {
break
} else if err := jsoniter.Unmarshal(packet, &task); err != nil {
_ = c.WriteMessage(messageType, models.UnifiedCommand{
Action: "error",
Message: "unable to unmarshal your command, requires json request",
}.Marshal())
continue
} else {
payload, _ = jsoniter.Marshal(task.Payload)
}
var message *models.UnifiedCommand
switch task.Action {
case "kex.request":
var req struct {
RequestID string `json:"request_id"`
KeypairID string `json:"keypair_id"`
Algorithm string `json:"algorithm"`
OwnerID uint `json:"owner_id"`
Deadline int64 `json:"deadline"`
}
_ = jsoniter.Unmarshal(payload, &req)
if len(req.RequestID) <= 0 || len(req.KeypairID) <= 0 || req.OwnerID <= 0 {
message = lo.ToPtr(models.UnifiedCommandFromError(fmt.Errorf("invalid request")))
}
services.KexRequest(c, req.RequestID, req.KeypairID, req.Algorithm, req.OwnerID, req.Deadline)
case "kex.provide":
var req struct {
RequestID string `json:"request_id"`
KeypairID string `json:"keypair_id"`
Algorithm string `json:"algorithm"`
PublicKey []byte `json:"public_key"`
}
_ = jsoniter.Unmarshal(payload, &req)
if len(req.RequestID) <= 0 || len(req.KeypairID) <= 0 {
message = lo.ToPtr(models.UnifiedCommandFromError(fmt.Errorf("invalid request")))
}
services.KexProvide(user.ID, req.RequestID, req.KeypairID, packet)
default:
message = lo.ToPtr(models.UnifiedCommandFromError(fmt.Errorf("unknown action")))
}
if message != nil {
if err = c.WriteMessage(messageType, message.Marshal()); err != nil {
break
}
}
}
// Pop connection
services.ClientUnregister(user, c)
log.Debug().Uint("user", user.ID).Msg("A websocket connection disconnected...")
}

View File

@@ -1,56 +0,0 @@
package exts
import (
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/hyper"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"strings"
)
func AuthMiddleware(c *fiber.Ctx) error {
var atk string
if cookie := c.Cookies(hyper.CookieAtk); len(cookie) > 0 {
atk = cookie
}
if header := c.Get(fiber.HeaderAuthorization); len(header) > 0 {
tk := strings.Replace(header, "Bearer", "", 1)
atk = strings.TrimSpace(tk)
}
if tk := c.Query("tk"); len(tk) > 0 {
atk = strings.TrimSpace(tk)
}
c.Locals("p_token", atk)
rtk := c.Cookies(hyper.CookieRtk)
if ctx, perms, newAtk, newRtk, err := services.Authenticate(atk, rtk, 0); err == nil {
if newAtk != atk {
SetAuthCookies(c, newAtk, newRtk)
}
c.Locals("permissions", perms)
c.Locals("user", ctx.Account)
}
return c.Next()
}
func EnsureAuthenticated(c *fiber.Ctx) error {
if _, ok := c.Locals("user").(models.Account); !ok {
return fiber.NewError(fiber.StatusUnauthorized)
}
return nil
}
func EnsureGrantedPerm(c *fiber.Ctx, key string, val any) error {
if err := EnsureAuthenticated(c); err != nil {
return err
}
perms := c.Locals("permissions").(map[string]any)
if !services.HasPermNode(perms, key, val) {
return fiber.NewError(fiber.StatusForbidden, fmt.Sprintf("missing permission: %s", key))
}
return nil
}

View File

@@ -1,27 +0,0 @@
package exts
import (
"git.solsynth.dev/hydrogen/passport/pkg/hyper"
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
"time"
)
func SetAuthCookies(c *fiber.Ctx, atk, rtk string) {
c.Cookie(&fiber.Cookie{
Name: hyper.CookieAtk,
Value: atk,
Domain: viper.GetString("security.cookie_domain"),
SameSite: viper.GetString("security.cookie_samesite"),
Expires: time.Now().Add(60 * time.Minute),
Path: "/",
})
c.Cookie(&fiber.Cookie{
Name: hyper.CookieRtk,
Value: rtk,
Domain: viper.GetString("security.cookie_domain"),
SameSite: viper.GetString("security.cookie_samesite"),
Expires: time.Now().Add(24 * 30 * time.Hour),
Path: "/",
})
}

View File

@@ -1,86 +0,0 @@
package server
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/admin"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/api"
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"net/http"
"path/filepath"
"strings"
"git.solsynth.dev/hydrogen/passport/pkg/internal/i18n"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/favicon"
"github.com/gofiber/fiber/v2/middleware/idempotency"
"github.com/gofiber/fiber/v2/middleware/logger"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
)
type HTTPApp struct {
app *fiber.App
}
func NewServer() *HTTPApp {
app := fiber.New(fiber.Config{
DisableStartupMessage: true,
EnableIPValidation: true,
ServerHeader: "Hydrogen.Passport",
AppName: "Hydrogen.Passport",
ProxyHeader: fiber.HeaderXForwardedFor,
JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal,
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
EnablePrintRoutes: viper.GetBool("debug.print_routes"),
})
app.Use(idempotency.New())
app.Use(cors.New(cors.Config{
AllowCredentials: true,
AllowMethods: strings.Join([]string{
fiber.MethodGet,
fiber.MethodPost,
fiber.MethodHead,
fiber.MethodOptions,
fiber.MethodPut,
fiber.MethodDelete,
fiber.MethodPatch,
}, ","),
AllowOriginsFunc: func(origin string) bool {
return true
},
}))
app.Use(logger.New(logger.Config{
Format: "${status} | ${latency} | ${method} ${path}\n",
Output: log.Logger,
}))
app.Use(exts.AuthMiddleware)
app.Use(i18n.I18nMiddleware)
admin.MapAdminAPIs(app)
api.MapAPIs(app)
app.Use(filesystem.New(filesystem.Config{
Root: http.Dir(viper.GetString("frontend_app")),
Index: "index.html",
NotFoundFile: "index.html",
MaxAge: 3600,
}))
app.Use(favicon.New(favicon.Config{
File: filepath.Join(viper.GetString("frontend_app"), "favicon.png"),
URL: "/favicon.png",
}))
return &HTTPApp{app}
}
func (v *HTTPApp) Listen() {
if err := v.app.Listen(viper.GetString("bind")); err != nil {
log.Fatal().Err(err).Msg("An error occurred when starting server...")
}
}

View File

@@ -0,0 +1,25 @@
package services
import (
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"github.com/samber/lo"
)
func GetUserAccountGroup(user models.Account) ([]models.AccountGroup, error) {
var members []models.AccountGroupMember
if err := database.C.Where(&models.AccountGroupMember{
AccountID: user.ID,
}).Find(&members).Error; err != nil {
return nil, err
}
var groups []models.AccountGroup
if err := database.C.Where("id IN ?", lo.Map(members, func(item models.AccountGroupMember, index int) uint {
return item.GroupID
})).Find(&groups).Error; err != nil {
return nil, err
}
return groups, nil
}

View File

@@ -1,19 +1,115 @@
package services
import (
"context"
"fmt"
"maps"
"time"
"unicode"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/gap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"gorm.io/datatypes"
"time"
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"github.com/google/uuid"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"github.com/samber/lo"
"gorm.io/gorm"
)
func KgAccountCache(query any) string {
return cachekit.FKey(cachekit.DAUser, query)
}
func CacheAccount(account models.Account) {
cachekit.Set[models.Account](
gap.Ca,
KgAccountCache(account.Name),
account,
60*time.Minute,
fmt.Sprintf("user#%d", account.ID),
)
cachekit.Set[models.Account](
gap.Ca,
KgAccountCache(account.ID),
account,
60*time.Minute,
fmt.Sprintf("user#%d", account.ID),
)
}
func ValidateAccountName(val string, min, max int) bool {
actualLength := 0
for _, r := range val {
if unicode.Is(unicode.Han, r) || unicode.Is(unicode.Hiragana, r) || unicode.Is(unicode.Katakana, r) || unicode.Is(unicode.Hangul, r) {
actualLength += 2
} else {
actualLength += 1
}
}
return actualLength >= min && max >= actualLength
}
func GetAccountForEnd(id any) (models.Account, error) {
if val, err := cachekit.Get[models.Account](gap.Ca, KgAccountCache(id)); err == nil {
return val, err
}
var account models.Account
tx := database.C
switch id.(type) {
case uint:
tx = tx.Where("id = ?", id)
case string:
tx = tx.Where("name = ?", id)
default:
return account, fmt.Errorf("invalid account id type")
}
if err := tx.
Preload("Profile").
Preload("Badges", func(db *gorm.DB) *gorm.DB {
return db.Order("badges.is_active DESC, badges.type DESC")
}).
First(&account).Error; err != nil {
return account, fmt.Errorf("requested user with id %d was not found", id)
}
groups, err := GetUserAccountGroup(account)
if err != nil {
return account, fmt.Errorf("unable to get account groups: %v", err)
}
for _, group := range groups {
for k, v := range group.PermNodes {
if _, ok := account.PermNodes[k]; !ok {
account.PermNodes[k] = v
}
}
}
punishments, err := ListPunishments(account)
if err != nil {
return account, fmt.Errorf("unable to get account punishments: %v", err)
}
account.Punishments = punishments
for _, punishment := range punishments {
if punishment.Type == models.PunishmentTypeLimited && len(punishment.PermNodes) > 0 {
maps.Copy(account.PermNodes, punishment.PermNodes)
}
}
CacheAccount(account)
return account, nil
}
func GetAccount(id uint) (models.Account, error) {
var account models.Account
if err := database.C.Where(models.Account{
@@ -25,6 +121,15 @@ func GetAccount(id uint) (models.Account, error) {
return account, nil
}
func GetAccountList(id []uint) ([]models.Account, error) {
var accounts []models.Account
if err := database.C.Where("id IN ?", id).Find(&accounts).Error; err != nil {
return accounts, err
}
return accounts, nil
}
func GetAccountWithName(alias string) (models.Account, error) {
var account models.Account
if err := database.C.Where(models.Account{
@@ -55,7 +160,16 @@ func LookupAccount(probe string) (models.Account, error) {
return account, fmt.Errorf("account was not found")
}
func CreateAccount(name, nick, email, password string) (models.Account, error) {
func SearchAccount(probe string) ([]models.Account, error) {
probe = "%" + probe + "%"
var accounts []models.Account
if err := database.C.Where("name LIKE ? OR nick LIKE ?", probe, probe).Find(&accounts).Error; err != nil {
return accounts, err
}
return accounts, nil
}
func CreateAccount(name, nick, email, password, lang string) (models.Account, error) {
user := models.Account{
Name: name,
Nick: nick,
@@ -67,10 +181,6 @@ func CreateAccount(name, nick, email, password string) (models.Account, error) {
Type: models.PasswordAuthFactor,
Secret: HashPassword(password),
},
{
Type: models.EmailPasswordFactor,
Secret: uuid.NewString()[:8],
},
},
Contacts: []models.AccountContact{
{
@@ -80,13 +190,15 @@ func CreateAccount(name, nick, email, password string) (models.Account, error) {
VerifiedAt: nil,
},
},
PermNodes: datatypes.JSONMap(viper.GetStringMap("permissions.default")),
Language: lang,
PermNodes: datatypes.JSONMap{},
ConfirmedAt: nil,
}
if err := database.C.Create(&user).Error; err != nil {
return user, err
}
// Only gave user permission group after they confiremd the registeration
if tk, err := NewMagicToken(models.ConfirmMagicToken, &user, nil); err != nil {
return user, err
@@ -112,28 +224,91 @@ func ConfirmAccount(code string) error {
return err
}
return database.C.Transaction(func(tx *gorm.DB) error {
if err = ForceConfirmAccount(user); err != nil {
return err
} else {
database.C.Delete(&token)
}
return nil
}
func ForceConfirmAccount(user models.Account) error {
user.ConfirmedAt = lo.ToPtr(time.Now())
for k, v := range viper.GetStringMap("permissions.verified") {
if val, ok := user.PermNodes[k]; !ok {
user.PermNodes[k] = v
} else if !ComparePermNode(val, v) {
user.PermNodes[k] = v
}
if viper.GetInt("default_user_group") > 0 {
database.C.Create(&models.AccountGroupMember{
AccountID: user.ID,
GroupID: uint(viper.GetInt("default_user_group")),
})
}
if err := database.C.Delete(&token).Error; err != nil {
return err
}
_ = database.C.Model(&models.AccountContact{}).Where("account_id = ?", user.ID).Updates(&models.AccountContact{
VerifiedAt: lo.ToPtr(time.Now()),
})
if err := database.C.Save(&user).Error; err != nil {
return err
}
InvalidAuthCacheWithUser(user.ID)
InvalidUserAuthCache(user.ID)
return nil
}
func CheckAbleToDeleteAccount(user models.Account) error {
if user.AutomatedID != nil {
return fmt.Errorf("bot cannot request delete account, head to developer portal and dispose bot")
}
var count int64
if err := database.C.
Where("account_id = ?", user.ID).
Where("expired_at < ?", time.Now()).
Where("type = ?", models.DeleteAccountMagicToken).
Model(&models.MagicToken{}).
Count(&count).Error; err != nil {
return fmt.Errorf("unable to check delete account ability: %v", err)
} else if count > 0 {
return fmt.Errorf("you requested delete account recently")
}
return nil
}
func RequestDeleteAccount(user models.Account) error {
if tk, err := NewMagicToken(
models.DeleteAccountMagicToken,
&user,
lo.ToPtr(time.Now().Add(24*time.Hour)),
); err != nil {
return err
} else if err := NotifyMagicToken(tk); err != nil {
log.Error().
Err(err).
Str("code", tk.Code).
Uint("user", user.ID).
Msg("Failed to notify delete account magic token...")
}
return nil
}
func ConfirmDeleteAccount(code string) error {
token, err := ValidateMagicToken(code, models.DeleteAccountMagicToken)
if err != nil {
return err
} else if token.AccountID == nil {
return fmt.Errorf("magic token didn't assign a valid account")
}
if err := DeleteAccount(*token.AccountID); err != nil {
return err
} else {
database.C.Delete(&token)
}
return nil
})
}
func CheckAbleToResetPassword(user models.Account) error {
@@ -189,41 +364,49 @@ func ConfirmResetPassword(code, newPassword string) error {
factor.Secret = HashPassword(newPassword)
}
return database.C.Save(&factor).Error
if err = database.C.Save(&factor).Error; err != nil {
return err
} else {
database.C.Delete(&token)
}
return nil
}
func DeleteAccount(id uint) error {
tx := database.C.Begin()
for _, model := range []any{
&models.Badge{},
&models.RealmMember{},
&models.AccountContact{},
&models.AuthFactor{},
&models.AuthTicket{},
&models.MagicToken{},
&models.ThirdClient{},
&models.Notification{},
&models.NotificationSubscriber{},
&models.AccountFriendship{},
} {
if err := tx.Delete(model, "account_id = ?", id).Error; err != nil {
if err := tx.Delete(&models.AuthTicket{}, "account_id = ?", id).Error; err != nil {
tx.Rollback()
return err
}
}
if err := tx.Delete(&models.Account{}, "id = ?", id).Error; err != nil {
if err := tx.Select(clause.Associations).Delete(&models.Account{}, "id = ?", id).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
if err := tx.Commit().Error; err != nil {
return err
} else {
InvalidUserAuthCache(id)
conn := gap.Nx.GetNexusGrpcConn()
_, _ = proto.NewDirectoryServiceClient(conn).BroadcastEvent(context.Background(), &proto.EventInfo{
Event: "deletion",
Data: nex.EncodeMap(map[string]any{
"type": "account",
"id": id,
}),
})
}
return nil
}
func RecycleUnConfirmAccount() {
deadline := time.Now().Add(-24 * time.Hour)
var hitList []models.Account
if err := database.C.Where("confirmed_at IS NULL").Find(&hitList).Error; err != nil {
if err := database.C.Where("confirmed_at IS NULL AND created_at <= ?", deadline).Find(&hitList).Error; err != nil {
log.Error().Err(err).Msg("An error occurred while recycling accounts...")
return
}

View File

@@ -2,45 +2,27 @@ package services
import (
"fmt"
"sync"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"git.solsynth.dev/hypernet/passport/pkg/internal/gap"
jsoniter "github.com/json-iterator/go"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
)
var (
authContextMutex sync.Mutex
authContextCache = make(map[string]models.AuthContext)
)
func Authenticate(atk, rtk string, rty int) (ctx models.AuthContext, perms map[string]any, newAtk, newRtk string, err error) {
var claims PayloadClaims
claims, err = DecodeJwt(atk)
if err != nil {
if len(rtk) > 0 && rty < 1 {
// Auto refresh and retry
newAtk, newRtk, err = RefreshToken(rtk)
if err == nil {
return Authenticate(newAtk, newRtk, rty+1)
}
}
err = fiber.NewError(fiber.StatusUnauthorized, fmt.Sprintf("invalid auth key: %v", err))
return
}
newAtk = atk
newRtk = rtk
if ctx, err = GetAuthContext(claims.ID); err == nil {
func Authenticate(sessionId uint) (ctx models.AuthTicket, perms map[string]any, err error) {
if ctx, err = GetAuthContext(sessionId); err == nil {
var heldPerms map[string]any
rawHeldPerms, _ := jsoniter.Marshal(ctx.Account.PermNodes)
_ = jsoniter.Unmarshal(rawHeldPerms, &heldPerms)
perms = FilterPermNodes(heldPerms, ctx.Ticket.Claims)
perms = FilterPermNodes(heldPerms, ctx.Claims)
ctx.Account.PermNodes = perms
return
}
@@ -48,78 +30,76 @@ func Authenticate(atk, rtk string, rty int) (ctx models.AuthContext, perms map[s
return
}
func GetAuthContext(jti string) (models.AuthContext, error) {
var err error
var ctx models.AuthContext
func KgAuthContextCache(sessionId uint) string {
return cachekit.FKey("auth-context", sessionId)
}
if val, ok := authContextCache[jti]; ok {
func GetAuthContext(sessionId uint) (models.AuthTicket, error) {
var err error
var ctx models.AuthTicket
key := KgAuthContextCache(sessionId)
if val, err := cachekit.Get[models.AuthTicket](gap.Ca, key); err == nil {
ctx = val
ctx.LastUsedAt = time.Now()
authContextMutex.Lock()
authContextCache[jti] = ctx
authContextMutex.Unlock()
log.Debug().Str("jti", jti).Msg("Used an auth context cache")
} else {
ctx, err = CacheAuthContext(jti)
log.Debug().Str("jti", jti).Msg("Created a new auth context cache")
log.Error().Err(err).Msg("Unable to get auth context cache")
ctx, err = CacheAuthContext(sessionId)
if err != nil {
log.Error().Err(err).Msg("Unable to cache auth context")
} else {
log.Debug().Uint("session", sessionId).Msg("Created a new auth context cache")
}
}
return ctx, err
}
func CacheAuthContext(jti string) (models.AuthContext, error) {
var ctx models.AuthContext
func CacheAuthContext(sessionId uint) (models.AuthTicket, error) {
// Query data from primary database
ticket, err := GetTicketWithToken(jti)
if err != nil {
return ctx, fmt.Errorf("invalid auth ticket: %v", err)
var ticket models.AuthTicket
if err := database.C.
Where("id = ?", sessionId).
First(&ticket).Error; err != nil {
return ticket, fmt.Errorf("invalid auth ticket: %v", err)
} else if err := ticket.IsAvailable(); err != nil {
return ctx, fmt.Errorf("unavailable auth ticket: %v", err)
return ticket, fmt.Errorf("unavailable auth ticket: %v", err)
}
user, err := GetAccount(ticket.AccountID)
if err != nil {
return ctx, fmt.Errorf("invalid account: %v", err)
return ticket, fmt.Errorf("invalid account: %v", err)
}
groups, err := GetUserAccountGroup(user)
if err != nil {
return ticket, fmt.Errorf("unable to get account groups: %v", err)
}
ctx = models.AuthContext{
Ticket: ticket,
Account: user,
LastUsedAt: time.Now(),
for _, group := range groups {
for k, v := range group.PermNodes {
if _, ok := user.PermNodes[k]; !ok {
user.PermNodes[k] = v
}
}
}
ticket.Account = user
// Put the data into the cache
key := KgAuthContextCache(sessionId)
err = cachekit.Set[models.AuthTicket](
gap.Ca,
key,
ticket,
time.Minute*10,
"auth-context",
fmt.Sprintf("user#%d", user.ID),
)
if err != nil {
log.Error().Err(err).Msg("Unable to cache auth context...")
}
// Put the data into memory for cache
authContextMutex.Lock()
authContextCache[jti] = ctx
authContextMutex.Unlock()
return ctx, nil
return ticket, err
}
func RecycleAuthContext() {
if len(authContextCache) == 0 {
return
}
affected := 0
for key, val := range authContextCache {
if val.LastUsedAt.Add(60*time.Second).Unix() < time.Now().Unix() {
affected++
authContextMutex.Lock()
delete(authContextCache, key)
authContextMutex.Unlock()
}
}
log.Debug().Int("affected", affected).Msg("Recycled auth context...")
}
func InvalidAuthCacheWithUser(userId uint) {
for key, val := range authContextCache {
if val.Account.ID == userId {
authContextMutex.Lock()
delete(authContextCache, key)
authContextMutex.Unlock()
}
}
func InvalidUserAuthCache(uid uint) {
cachekit.DeleteByTags(gap.Ca, "auth-context", fmt.Sprintf("user#%d", uid))
}

View File

@@ -1,8 +1,8 @@
package services
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
)
func GrantBadge(user models.Account, badge models.Badge) error {
@@ -13,3 +13,23 @@ func GrantBadge(user models.Account, badge models.Badge) error {
func RevokeBadge(badge models.Badge) error {
return database.C.Delete(&badge).Error
}
func ActiveBadge(badge models.Badge) error {
accountId := badge.AccountID
tx := database.C.Begin()
if err := tx.Model(&models.Badge{}).Where("account_id = ?", accountId).Update("is_active", false).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Model(&models.Badge{}).Where("id = ?", badge.ID).Update("is_active", true).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Commit().Error; err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,56 @@
package services
import (
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"time"
"git.solsynth.dev/hypernet/passport/pkg/internal/database"
"github.com/google/uuid"
"github.com/samber/lo"
)
func NewApiKey(user models.Account, key models.ApiKey, ip, ua string, claims []string) (models.ApiKey, error) {
key.Account = user
key.AccountID = user.ID
var expiredAt *time.Time
if key.Lifecycle != nil {
expiredAt = lo.ToPtr(time.Now().Add(time.Duration(*key.Lifecycle) * time.Second))
}
key.Ticket = models.AuthTicket{
IpAddress: ip,
UserAgent: ua,
StepRemain: 0,
Claims: claims,
Audiences: []string{InternalTokenAudience},
GrantToken: lo.ToPtr(uuid.NewString()),
AccessToken: lo.ToPtr(uuid.NewString()),
RefreshToken: lo.ToPtr(uuid.NewString()),
AvailableAt: lo.ToPtr(time.Now()),
ExpiredAt: expiredAt,
Account: user,
AccountID: user.ID,
}
if err := database.C.Save(&key).Error; err != nil {
return key, err
}
return key, nil
}
func RollApiKey(key models.ApiKey) (models.ApiKey, error) {
var ticket models.AuthTicket
if err := database.C.Where("id = ?", key.TicketID).First(&ticket).Error; err != nil {
return key, err
}
ticket, err := RotateTicket(ticket, true)
if err != nil {
return key, err
} else {
key.Ticket = ticket
}
return key, nil
}

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