aimeat 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/dist/.env.example +15 -0
  2. package/dist/bin/aimeat.js +0 -0
  3. package/dist/locales/en.json +79 -2
  4. package/dist/locales/fi.json +79 -2
  5. package/dist/public/css/views/admin.css +97 -0
  6. package/dist/public/js/services/admin.js +7 -1
  7. package/dist/public/views/admin/federation-tab.js +60 -0
  8. package/dist/public/views/admin/stats-tab.js +480 -187
  9. package/dist/public/views/profile/access-tab.js +1 -0
  10. package/dist/src/auth/node-keys.d.ts.map +1 -1
  11. package/dist/src/auth/node-keys.js +3 -0
  12. package/dist/src/auth/node-keys.js.map +1 -1
  13. package/dist/src/config.d.ts +13 -0
  14. package/dist/src/config.d.ts.map +1 -1
  15. package/dist/src/config.js +14 -0
  16. package/dist/src/config.js.map +1 -1
  17. package/dist/src/index.js +41 -2
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/mcp/extensions.js +1 -1
  20. package/dist/src/mcp/extensions.js.map +1 -1
  21. package/dist/src/middleware/rate-limit.d.ts.map +1 -1
  22. package/dist/src/middleware/rate-limit.js +4 -1
  23. package/dist/src/middleware/rate-limit.js.map +1 -1
  24. package/dist/src/models/schemas.d.ts +45 -0
  25. package/dist/src/models/schemas.d.ts.map +1 -1
  26. package/dist/src/models/schemas.js +37 -1
  27. package/dist/src/models/schemas.js.map +1 -1
  28. package/dist/src/routes/actions.d.ts.map +1 -1
  29. package/dist/src/routes/actions.js +1 -0
  30. package/dist/src/routes/actions.js.map +1 -1
  31. package/dist/src/routes/admin-monitoring.d.ts.map +1 -1
  32. package/dist/src/routes/admin-monitoring.js +7 -0
  33. package/dist/src/routes/admin-monitoring.js.map +1 -1
  34. package/dist/src/routes/admin.d.ts.map +1 -1
  35. package/dist/src/routes/admin.js +33 -15
  36. package/dist/src/routes/admin.js.map +1 -1
  37. package/dist/src/routes/agents.js +2 -2
  38. package/dist/src/routes/agents.js.map +1 -1
  39. package/dist/src/routes/apps.d.ts.map +1 -1
  40. package/dist/src/routes/apps.js +5 -1
  41. package/dist/src/routes/apps.js.map +1 -1
  42. package/dist/src/routes/auth.d.ts.map +1 -1
  43. package/dist/src/routes/auth.js +19 -1
  44. package/dist/src/routes/auth.js.map +1 -1
  45. package/dist/src/routes/boards.js +2 -2
  46. package/dist/src/routes/boards.js.map +1 -1
  47. package/dist/src/routes/consent.d.ts.map +1 -1
  48. package/dist/src/routes/consent.js +2 -1
  49. package/dist/src/routes/consent.js.map +1 -1
  50. package/dist/src/routes/disputes.js +4 -4
  51. package/dist/src/routes/disputes.js.map +1 -1
  52. package/dist/src/routes/extensions.d.ts.map +1 -1
  53. package/dist/src/routes/extensions.js +72 -21
  54. package/dist/src/routes/extensions.js.map +1 -1
  55. package/dist/src/routes/federation-auth.d.ts.map +1 -1
  56. package/dist/src/routes/federation-auth.js +2 -62
  57. package/dist/src/routes/federation-auth.js.map +1 -1
  58. package/dist/src/routes/federation-peer.d.ts.map +1 -1
  59. package/dist/src/routes/federation-peer.js +13 -1
  60. package/dist/src/routes/federation-peer.js.map +1 -1
  61. package/dist/src/routes/flags.d.ts.map +1 -1
  62. package/dist/src/routes/flags.js +2 -1
  63. package/dist/src/routes/flags.js.map +1 -1
  64. package/dist/src/routes/foundry.d.ts.map +1 -1
  65. package/dist/src/routes/foundry.js +2 -2
  66. package/dist/src/routes/foundry.js.map +1 -1
  67. package/dist/src/routes/generator.d.ts.map +1 -1
  68. package/dist/src/routes/generator.js +2 -2
  69. package/dist/src/routes/generator.js.map +1 -1
  70. package/dist/src/routes/ghii.d.ts.map +1 -1
  71. package/dist/src/routes/ghii.js +67 -31
  72. package/dist/src/routes/ghii.js.map +1 -1
  73. package/dist/src/routes/memory.d.ts.map +1 -1
  74. package/dist/src/routes/memory.js +2 -0
  75. package/dist/src/routes/memory.js.map +1 -1
  76. package/dist/src/routes/owners.d.ts.map +1 -1
  77. package/dist/src/routes/owners.js +104 -8
  78. package/dist/src/routes/owners.js.map +1 -1
  79. package/dist/src/routes/packages.js +1 -1
  80. package/dist/src/routes/packages.js.map +1 -1
  81. package/dist/src/routes/stats.d.ts +13 -0
  82. package/dist/src/routes/stats.d.ts.map +1 -1
  83. package/dist/src/routes/stats.js +54 -4
  84. package/dist/src/routes/stats.js.map +1 -1
  85. package/dist/src/routes/storage-files.d.ts.map +1 -1
  86. package/dist/src/routes/storage-files.js +5 -1
  87. package/dist/src/routes/storage-files.js.map +1 -1
  88. package/dist/src/routes/upload.js +2 -2
  89. package/dist/src/routes/upload.js.map +1 -1
  90. package/dist/src/routes/wallet.d.ts.map +1 -1
  91. package/dist/src/routes/wallet.js +2 -1
  92. package/dist/src/routes/wallet.js.map +1 -1
  93. package/dist/src/routes/work.d.ts.map +1 -1
  94. package/dist/src/routes/work.js +2 -1
  95. package/dist/src/routes/work.js.map +1 -1
  96. package/dist/src/server-bootstrap/routes-loader.d.ts +1 -1
  97. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  98. package/dist/src/server-bootstrap/routes-loader.js +3 -3
  99. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  100. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  101. package/dist/src/server-bootstrap/service-init.js +2 -0
  102. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  103. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  104. package/dist/src/server-bootstrap/static-files.js +10 -7
  105. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  106. package/dist/src/server.d.ts.map +1 -1
  107. package/dist/src/server.js +4 -2
  108. package/dist/src/server.js.map +1 -1
  109. package/dist/src/services/capability-invoke.d.ts.map +1 -1
  110. package/dist/src/services/capability-invoke.js +5 -18
  111. package/dist/src/services/capability-invoke.js.map +1 -1
  112. package/dist/src/services/config-schema.d.ts.map +1 -1
  113. package/dist/src/services/config-schema.js +13 -0
  114. package/dist/src/services/config-schema.js.map +1 -1
  115. package/dist/src/services/consent.d.ts.map +1 -1
  116. package/dist/src/services/consent.js +1 -10
  117. package/dist/src/services/consent.js.map +1 -1
  118. package/dist/src/services/directory.d.ts.map +1 -1
  119. package/dist/src/services/directory.js +10 -4
  120. package/dist/src/services/directory.js.map +1 -1
  121. package/dist/src/services/email.d.ts +17 -4
  122. package/dist/src/services/email.d.ts.map +1 -1
  123. package/dist/src/services/email.js +30 -12
  124. package/dist/src/services/email.js.map +1 -1
  125. package/dist/src/services/extension-runtime.d.ts.map +1 -1
  126. package/dist/src/services/extension-runtime.js +5 -0
  127. package/dist/src/services/extension-runtime.js.map +1 -1
  128. package/dist/src/services/federation.d.ts +2 -0
  129. package/dist/src/services/federation.d.ts.map +1 -1
  130. package/dist/src/services/federation.js.map +1 -1
  131. package/dist/src/services/mailbox-notification.d.ts +17 -0
  132. package/dist/src/services/mailbox-notification.d.ts.map +1 -1
  133. package/dist/src/services/mailbox-notification.js +25 -0
  134. package/dist/src/services/mailbox-notification.js.map +1 -1
  135. package/dist/src/services/morsel.d.ts.map +1 -1
  136. package/dist/src/services/morsel.js +10 -9
  137. package/dist/src/services/morsel.js.map +1 -1
  138. package/dist/src/services/password.d.ts +1 -6
  139. package/dist/src/services/password.d.ts.map +1 -1
  140. package/dist/src/services/password.js +19 -10
  141. package/dist/src/services/password.js.map +1 -1
  142. package/dist/src/services/push.d.ts +16 -0
  143. package/dist/src/services/push.d.ts.map +1 -1
  144. package/dist/src/services/push.js +20 -0
  145. package/dist/src/services/push.js.map +1 -1
  146. package/dist/src/services/quota.d.ts.map +1 -1
  147. package/dist/src/services/quota.js +2 -1
  148. package/dist/src/services/quota.js.map +1 -1
  149. package/dist/src/services/scheduler.d.ts.map +1 -1
  150. package/dist/src/services/scheduler.js +17 -1
  151. package/dist/src/services/scheduler.js.map +1 -1
  152. package/dist/src/services/stats.d.ts +42 -2
  153. package/dist/src/services/stats.d.ts.map +1 -1
  154. package/dist/src/services/stats.js +176 -1
  155. package/dist/src/services/stats.js.map +1 -1
  156. package/dist/src/services/totp.js +2 -2
  157. package/dist/src/storage/interface.d.ts +6 -1
  158. package/dist/src/storage/interface.d.ts.map +1 -1
  159. package/dist/src/storage/providers/mongodb/index.d.ts +10 -0
  160. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  161. package/dist/src/storage/providers/mongodb/index.js +81 -2
  162. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  163. package/dist/src/storage/providers/sqlite/index.d.ts +10 -0
  164. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  165. package/dist/src/storage/providers/sqlite/index.js +73 -2
  166. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  167. package/dist/src/storage/providers/sqlite/repos/consent.d.ts +1 -0
  168. package/dist/src/storage/providers/sqlite/repos/consent.d.ts.map +1 -1
  169. package/dist/src/storage/providers/sqlite/repos/consent.js +4 -0
  170. package/dist/src/storage/providers/sqlite/repos/consent.js.map +1 -1
  171. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  172. package/dist/src/storage/providers/sqlite/schema.js +19 -0
  173. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  174. package/dist/src/storage/repositories/consent.repository.d.ts +1 -0
  175. package/dist/src/storage/repositories/consent.repository.d.ts.map +1 -1
  176. package/dist/src/storage/repositories/device-auth.repository.d.ts +1 -0
  177. package/dist/src/storage/repositories/device-auth.repository.d.ts.map +1 -1
  178. package/dist/src/storage/repositories/extension-instance.repository.d.ts +1 -0
  179. package/dist/src/storage/repositories/extension-instance.repository.d.ts.map +1 -1
  180. package/dist/src/storage/repositories/index.d.ts +1 -0
  181. package/dist/src/storage/repositories/index.d.ts.map +1 -1
  182. package/dist/src/storage/repositories/knowledge.repository.d.ts +2 -0
  183. package/dist/src/storage/repositories/knowledge.repository.d.ts.map +1 -1
  184. package/dist/src/storage/repositories/moderation.repository.d.ts +1 -0
  185. package/dist/src/storage/repositories/moderation.repository.d.ts.map +1 -1
  186. package/dist/src/storage/repositories/stats.repository.d.ts +12 -0
  187. package/dist/src/storage/repositories/stats.repository.d.ts.map +1 -0
  188. package/dist/src/storage/repositories/stats.repository.js +7 -0
  189. package/dist/src/storage/repositories/stats.repository.js.map +1 -0
  190. package/dist/src/storage/storage-factory.js +1 -0
  191. package/dist/src/storage/storage-factory.js.map +1 -1
  192. package/dist/src/utils/password-validation.d.ts +8 -0
  193. package/dist/src/utils/password-validation.d.ts.map +1 -0
  194. package/dist/src/utils/password-validation.js +25 -0
  195. package/dist/src/utils/password-validation.js.map +1 -0
  196. package/dist/src/utils/service-summary.d.ts +6 -1
  197. package/dist/src/utils/service-summary.d.ts.map +1 -1
  198. package/dist/src/utils/service-summary.js +6 -1
  199. package/dist/src/utils/service-summary.js.map +1 -1
  200. package/package.json +97 -77
  201. package/prisma/schema.prisma +26 -10
package/dist/.env.example CHANGED
@@ -34,6 +34,21 @@ AIMEAT_PORT=40050
34
34
  # CORS: comma-separated list of allowed origins, or * for all (default: *)
35
35
  # AIMEAT_CORS_ALLOWED_ORIGINS=*
36
36
  # AIMEAT_CORS_ALLOWED_ORIGINS=https://portal.aimeat.fi,https://my-app.example.com
37
+ # ── Security Limits ────────────────────────────────────────────
38
+ # AIMEAT_LOGIN_RATE_LIMIT_MAX=15 # Max login attempts per window (default: 15)
39
+ # AIMEAT_LOGIN_RATE_LIMIT_WINDOW_MS=60000 # Login rate limit window in ms (default: 60s)
40
+ # AIMEAT_REGISTRATION_RATE_LIMIT_MAX=5 # Max registrations per window (default: 5)
41
+ # AIMEAT_REGISTRATION_RATE_LIMIT_WINDOW_MS=60000
42
+ # AIMEAT_ADMIN_AUTH_RATE_LIMIT_MAX=5 # Max admin auth attempts per window (default: 5)
43
+ # AIMEAT_ADMIN_AUTH_RATE_LIMIT_WINDOW_MS=60000
44
+ # AIMEAT_PASSWORD_LOCKOUT_ATTEMPTS=5 # Failed attempts before lockout (default: 5)
45
+ # AIMEAT_PASSWORD_LOCKOUT_MINUTES=15 # Lockout duration in minutes (default: 15)
46
+ # AIMEAT_JSON_BODY_LIMIT_MB=5 # Default JSON body limit (default: 5MB)
47
+ # AIMEAT_JSON_BODY_LIMIT_LARGE_MB=15 # Large body limit for apps/extensions (default: 15MB)
48
+ # AIMEAT_EXT_MAX_DEBIT=100 # Max morsels per extension wallet.consume() call
49
+ # AIMEAT_FEDERATION_AUTH_POLICY=disabled # disabled | all_peers | specific_peers
50
+ # AIMEAT_FEDERATION_DEFAULT_SCOPES=memory:read,catalogue:read
51
+
37
52
  # ── Auth & Tokens ──────────────────────────────────────────────
38
53
  # AIMEAT_JWT_TTL=3600 # JWT lifetime in seconds (default: 1h)
39
54
  # AIMEAT_OTK_TTL_MS=300000 # OTK expiry in ms (default: 5min)
File without changes
@@ -2229,7 +2229,8 @@
2229
2229
  "errUserShort": "Username must be at least 3 characters",
2230
2230
  "errPassShort": "Password must be at least 4 characters",
2231
2231
  "errWrongPass": "Wrong password for that username.",
2232
- "errFederatedLogin": "Federated login is not yet supported. This node is {localNode}, but the identity belongs to {remoteNode}.",
2232
+ "connectingHome": "Connecting to home node...",
2233
+ "errFederatedLogin": "Could not reach home node {remoteNode} for federated login.",
2233
2234
  "loggedIn": "logged in",
2234
2235
  "logoutBtn": "Logout",
2235
2236
  "usernameLabel": "Username",
@@ -2993,6 +2994,63 @@
2993
2994
  "pushUnsubscribeBtn": "Unsubscribe",
2994
2995
  "pushNoBrowserSupport": "This browser does not support push notifications",
2995
2996
 
2997
+ "blocked": "Blocked",
2998
+ "blockedCooldown": "Cooldown",
2999
+ "blockedDisabled": "Disabled",
3000
+ "blockedQuietHours": "Quiet Hours",
3001
+ "breakdownFailed": "Failed",
3002
+ "breakdownRetried": "Retried",
3003
+ "breakdownSent": "Sent",
3004
+ "breakdownType": "Type",
3005
+ "channelEmail": "Email",
3006
+ "channelPush": "Push",
3007
+ "channels": "Channels",
3008
+ "emailDelivery": "Email Delivery",
3009
+ "emailType": {
3010
+ "group_send": "Group Send",
3011
+ "magic_link": "Magic Link",
3012
+ "match_suggestion": "Match Suggestion",
3013
+ "notification": "Notification",
3014
+ "verification": "Verification"
3015
+ },
3016
+ "emailsFailed": "Emails Failed",
3017
+ "emailsRetried": "Emails Retried",
3018
+ "emailsSent": "Emails Sent",
3019
+ "expiredSubs": "Expired Subs",
3020
+ "live": "live",
3021
+ "mailboxNotifBlocked": "Mailbox Blocked",
3022
+ "mailboxNotifFailed": "Mailbox Failed",
3023
+ "mailboxNotifSent": "Mailbox Sent",
3024
+ "mailboxNotifications": "Mailbox Notifications",
3025
+ "period30Days": "30 Days",
3026
+ "period7Days": "7 Days",
3027
+ "periodAll": "All",
3028
+ "periodApply": "Apply",
3029
+ "periodCustom": "Custom",
3030
+ "periodFrom": "From",
3031
+ "periodLabel": "Period",
3032
+ "periodThisWeek": "This Week",
3033
+ "periodTo": "To",
3034
+ "periodToday": "Today",
3035
+ "weekMon": "Mon",
3036
+ "weekTue": "Tue",
3037
+ "weekWed": "Wed",
3038
+ "weekThu": "Thu",
3039
+ "weekFri": "Fri",
3040
+ "weekSat": "Sat",
3041
+ "weekSun": "Sun",
3042
+ "pushDelivery": "Push Notifications",
3043
+ "pushFailed": "Push Failed",
3044
+ "pushSent": "Push Sent",
3045
+ "pushType": {
3046
+ "board": "Board",
3047
+ "federation": "Federation",
3048
+ "general": "General",
3049
+ "organism": "Organism",
3050
+ "test": "Test",
3051
+ "work": "Work"
3052
+ },
3053
+
2996
3054
  "totalEntries": "Total Entries",
2997
3055
  "cities": "Cities",
2998
3056
  "categories": "Categories",
@@ -3211,6 +3269,13 @@
3211
3269
  "fedShareCatalogue": "Share Catalogue",
3212
3270
  "fedReplicateMemory": "Replicate Memory",
3213
3271
  "fedAllowRouting": "Allow Routing",
3272
+ "fedAllowAuth": "Allow Federated Login",
3273
+ "fedAuthPolicyTitle": "Federation Auth Policy",
3274
+ "fedAuthPolicyDesc": "Control whether users from other nodes can log in here and what they can do.",
3275
+ "fedAuthDisabled": "Disabled (no federated logins)",
3276
+ "fedAuthAllPeers": "All peers (any active peer's users can log in)",
3277
+ "fedAuthSpecificPeers": "Specific peers (per-peer control)",
3278
+ "fedDefaultScopes": "Default scopes for federated users:",
3214
3279
  "fedPeerMode": "Peer Mode",
3215
3280
  "fedPeerModeFederation": "Federation",
3216
3281
  "fedPeerModePrivate": "Private P2P",
@@ -3257,6 +3322,17 @@
3257
3322
  "cfgGroup_totp": "TOTP",
3258
3323
  "cfgGroup_matching": "Matching",
3259
3324
  "cfgGroup_extensions": "Extensions",
3325
+ "cfgGroup_security": "Security",
3326
+ "cfg_security_login_rate_limit_max": "Login rate limit (max per window)",
3327
+ "cfg_security_login_rate_limit_window_ms": "Login rate limit window (ms)",
3328
+ "cfg_security_registration_rate_limit_max": "Registration rate limit (max per window)",
3329
+ "cfg_security_registration_rate_limit_window_ms": "Registration rate limit window (ms)",
3330
+ "cfg_security_admin_auth_rate_limit_max": "Admin auth rate limit (max per window)",
3331
+ "cfg_security_admin_auth_rate_limit_window_ms": "Admin auth rate limit window (ms)",
3332
+ "cfg_security_password_lockout_attempts": "Password lockout after N failures",
3333
+ "cfg_security_password_lockout_minutes": "Password lockout duration (minutes)",
3334
+ "cfg_security_json_body_limit_mb": "JSON body size limit (MB)",
3335
+ "cfg_security_json_body_limit_large_mb": "Large body size limit for apps/extensions (MB)",
3260
3336
  "cfgGroup_marketplace": "Marketplace",
3261
3337
  "cfgGroup_push": "Push",
3262
3338
  "cfgGroup_eudiw": "EUDIW",
@@ -3887,7 +3963,8 @@
3887
3963
  "errUserShort": "Username must be at least 3 characters",
3888
3964
  "errPassShort": "Password must be at least 4 characters",
3889
3965
  "errWrongPass": "Wrong password for that username.",
3890
- "errFederatedLogin": "Federated login is not yet supported. This node is {localNode}, but the identity belongs to {remoteNode}.",
3966
+ "connectingHome": "Connecting to home node...",
3967
+ "errFederatedLogin": "Could not reach home node {remoteNode} for federated login.",
3891
3968
  "loggedIn": "logged in",
3892
3969
  "logoutBtn": "Logout",
3893
3970
  "whyTitle": "✨ What do you get?",
@@ -2229,7 +2229,8 @@
2229
2229
  "errUserShort": "Käyttäjänimen tulee olla vähintään 3 merkkiä",
2230
2230
  "errPassShort": "Salasanan tulee olla vähintään 4 merkkiä",
2231
2231
  "errWrongPass": "Väärä salasana tälle käyttäjänimelle.",
2232
- "errFederatedLogin": "Federoitu kirjautuminen ei ole vielä tuettu. Tämä solmu on {localNode}, mutta identiteetti kuuluu solmuun {remoteNode}.",
2232
+ "connectingHome": "Yhdistetään kotisolmuun...",
2233
+ "errFederatedLogin": "Kotisolmuun {remoteNode} ei saatu yhteyttä federoitua kirjautumista varten.",
2233
2234
  "loggedIn": "kirjautuneena",
2234
2235
  "logoutBtn": "Kirjaudu ulos",
2235
2236
  "usernameLabel": "Käyttäjänimi",
@@ -2993,6 +2994,63 @@
2993
2994
  "pushUnsubscribeBtn": "Peru tilaus",
2994
2995
  "pushNoBrowserSupport": "Tämä selain ei tue push-ilmoituksia",
2995
2996
 
2997
+ "blocked": "Estetyt",
2998
+ "blockedCooldown": "Jäähdytys",
2999
+ "blockedDisabled": "Pois käytöstä",
3000
+ "blockedQuietHours": "Hiljaiset tunnit",
3001
+ "breakdownFailed": "Epäonnistuneet",
3002
+ "breakdownRetried": "Uudelleenyritykset",
3003
+ "breakdownSent": "Lähetetyt",
3004
+ "breakdownType": "Tyyppi",
3005
+ "channelEmail": "Sähköposti",
3006
+ "channelPush": "Push",
3007
+ "channels": "Kanavat",
3008
+ "emailDelivery": "Sähköpostin toimitus",
3009
+ "emailType": {
3010
+ "group_send": "Ryhmälähetys",
3011
+ "magic_link": "Kirjautumislinkki",
3012
+ "match_suggestion": "Ehdotus",
3013
+ "notification": "Ilmoitus",
3014
+ "verification": "Vahvistus"
3015
+ },
3016
+ "emailsFailed": "Epäonnistuneet",
3017
+ "emailsRetried": "Uudelleenyritykset",
3018
+ "emailsSent": "Lähetetyt",
3019
+ "expiredSubs": "Vanhentuneet",
3020
+ "live": "live",
3021
+ "mailboxNotifBlocked": "Postilaatikko estetyt",
3022
+ "mailboxNotifFailed": "Postilaatikko epäonn.",
3023
+ "mailboxNotifSent": "Postilaatikko lähet.",
3024
+ "mailboxNotifications": "Postilaatikkoilmoitukset",
3025
+ "period30Days": "30 päivää",
3026
+ "period7Days": "7 päivää",
3027
+ "periodAll": "Kaikki",
3028
+ "periodApply": "Hae",
3029
+ "periodCustom": "Mukautettu",
3030
+ "periodFrom": "Alkaen",
3031
+ "periodLabel": "Ajanjakso",
3032
+ "periodThisWeek": "Tämä viikko",
3033
+ "periodTo": "Asti",
3034
+ "periodToday": "Tänään",
3035
+ "weekMon": "Ma",
3036
+ "weekTue": "Ti",
3037
+ "weekWed": "Ke",
3038
+ "weekThu": "To",
3039
+ "weekFri": "Pe",
3040
+ "weekSat": "La",
3041
+ "weekSun": "Su",
3042
+ "pushDelivery": "Push-ilmoitukset",
3043
+ "pushFailed": "Epäonnistuneet",
3044
+ "pushSent": "Lähetetyt",
3045
+ "pushType": {
3046
+ "board": "Keskustelu",
3047
+ "federation": "Federaatio",
3048
+ "general": "Yleinen",
3049
+ "organism": "Organismi",
3050
+ "test": "Testi",
3051
+ "work": "Työ"
3052
+ },
3053
+
2996
3054
  "totalEntries": "Merkintöjä yhteensä",
2997
3055
  "cities": "Kaupungit",
2998
3056
  "categories": "Kategoriat",
@@ -3211,6 +3269,13 @@
3211
3269
  "fedShareCatalogue": "Jaa katalogi",
3212
3270
  "fedReplicateMemory": "Replikoi muisti",
3213
3271
  "fedAllowRouting": "Salli reititys",
3272
+ "fedAllowAuth": "Salli federaatiokirjautuminen",
3273
+ "fedAuthPolicyTitle": "Federaation todennuspolitiikka",
3274
+ "fedAuthPolicyDesc": "Hallitse voivatko muiden solmujen kayttajat kirjautua tanne ja mita he voivat tehda.",
3275
+ "fedAuthDisabled": "Pois kaytosta (ei federaatiokirjautumisia)",
3276
+ "fedAuthAllPeers": "Kaikki vertaiset (kaikkien aktiivisten vertaisten kayttajat voivat kirjautua)",
3277
+ "fedAuthSpecificPeers": "Tietyt vertaiset (vertaiskohtainen hallinta)",
3278
+ "fedDefaultScopes": "Oletuslaajuudet federoiduille kayttajille:",
3214
3279
  "fedPeerMode": "Vertaistila",
3215
3280
  "fedPeerModeFederation": "Federaatio",
3216
3281
  "fedPeerModePrivate": "Yksityinen P2P",
@@ -3257,6 +3322,17 @@
3257
3322
  "cfgGroup_totp": "TOTP",
3258
3323
  "cfgGroup_matching": "Matchaus",
3259
3324
  "cfgGroup_extensions": "Laajennukset",
3325
+ "cfgGroup_security": "Turvallisuus",
3326
+ "cfg_security_login_rate_limit_max": "Kirjautumisen nopeusrajoitus (max per ikkuna)",
3327
+ "cfg_security_login_rate_limit_window_ms": "Kirjautumisen nopeusrajoitusikkuna (ms)",
3328
+ "cfg_security_registration_rate_limit_max": "Rekisteroinnin nopeusrajoitus (max per ikkuna)",
3329
+ "cfg_security_registration_rate_limit_window_ms": "Rekisteroinnin nopeusrajoitusikkuna (ms)",
3330
+ "cfg_security_admin_auth_rate_limit_max": "Admin-todennuksen nopeusrajoitus (max per ikkuna)",
3331
+ "cfg_security_admin_auth_rate_limit_window_ms": "Admin-todennuksen nopeusrajoitusikkuna (ms)",
3332
+ "cfg_security_password_lockout_attempts": "Salasanalukitus N epaonnistumisen jalkeen",
3333
+ "cfg_security_password_lockout_minutes": "Salasanalukituksen kesto (minuuttia)",
3334
+ "cfg_security_json_body_limit_mb": "JSON-sisallon kokoraja (Mt)",
3335
+ "cfg_security_json_body_limit_large_mb": "Suuri kokoraja sovelluksille/laajennuksille (Mt)",
3260
3336
  "cfgGroup_marketplace": "Kauppapaikka",
3261
3337
  "cfgGroup_push": "Push-ilmoitukset",
3262
3338
  "cfgGroup_eudiw": "EUDIW",
@@ -3887,7 +3963,8 @@
3887
3963
  "errUserShort": "Käyttäjänimen tulee olla vähintään 3 merkkiä",
3888
3964
  "errPassShort": "Salasanan tulee olla vähintään 4 merkkiä",
3889
3965
  "errWrongPass": "Väärä salasana tälle käyttäjänimelle.",
3890
- "errFederatedLogin": "Federoitu kirjautuminen ei ole vielä tuettu. Tämä solmu on {localNode}, mutta identiteetti kuuluu solmuun {remoteNode}.",
3966
+ "connectingHome": "Yhdistetään kotisolmuun...",
3967
+ "errFederatedLogin": "Kotisolmuun {remoteNode} ei saatu yhteyttä federoitua kirjautumista varten.",
3891
3968
  "loggedIn": "kirjautuneena",
3892
3969
  "logoutBtn": "Kirjaudu ulos",
3893
3970
  "whyTitle": "✨ Mitä saat?",
@@ -111,6 +111,7 @@
111
111
  /* Cards */
112
112
  .adm-grid { display: grid; gap: 16px; margin-bottom: 20px; }
113
113
  .adm-grid-4 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
114
+ .adm-grid-3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
114
115
  .adm-grid-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
115
116
  .adm-card {
116
117
  background: var(--glass-bg, #FFFFFF);
@@ -575,3 +576,99 @@
575
576
  .adm-member-add { display: flex; gap: 8px; align-items: center; }
576
577
  .adm-member-add .adm-input { flex: 1; font-size: .85rem; }
577
578
  .adm-font-mono { font-family: 'JetBrains Mono', monospace; }
579
+
580
+ /* ── Stats Time Range Selector ── */
581
+ .adm-time-range {
582
+ display: flex;
583
+ flex-wrap: wrap;
584
+ align-items: center;
585
+ gap: 8px;
586
+ margin-bottom: 20px;
587
+ padding: 12px 16px;
588
+ background: var(--glass-bg, #FFFFFF);
589
+ border: 1px solid var(--glass-border, #E5E7EB);
590
+ border-radius: 8px;
591
+ }
592
+ .adm-time-range-label {
593
+ font-size: 0.8rem;
594
+ color: var(--text-dim);
595
+ margin-right: 4px;
596
+ }
597
+ .adm-time-btn {
598
+ padding: 4px 12px;
599
+ font-size: 0.75rem;
600
+ border: 1px solid var(--glass-border, #E5E7EB);
601
+ border-radius: 6px;
602
+ background: transparent;
603
+ color: #1A1A2E;
604
+ cursor: pointer;
605
+ font-family: inherit;
606
+ transition: all 0.15s;
607
+ }
608
+ .adm-time-btn:hover { border-color: var(--accent); color: var(--accent); }
609
+ .adm-time-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
610
+ .adm-time-custom {
611
+ display: flex;
612
+ align-items: center;
613
+ gap: 6px;
614
+ margin-left: auto;
615
+ }
616
+ .adm-time-custom input[type="date"] {
617
+ padding: 3px 8px;
618
+ font-size: 0.75rem;
619
+ border: 1px solid var(--glass-border, #E5E7EB);
620
+ border-radius: 4px;
621
+ background: #FAFAF8;
622
+ color: #1A1A2E;
623
+ font-family: inherit;
624
+ }
625
+
626
+ /* ── Stats Breakdown Table ── */
627
+ .adm-breakdown-table {
628
+ width: 100%;
629
+ border-collapse: collapse;
630
+ font-size: 0.78rem;
631
+ margin-top: 8px;
632
+ }
633
+ .adm-breakdown-table th {
634
+ text-align: left;
635
+ padding: 6px 10px;
636
+ color: var(--text-dim);
637
+ border-bottom: 1px solid var(--glass-border, #E5E7EB);
638
+ font-weight: 500;
639
+ }
640
+ .adm-breakdown-table td {
641
+ padding: 5px 10px;
642
+ border-bottom: 1px solid #F3F4F6;
643
+ }
644
+ .adm-breakdown-table td.num {
645
+ text-align: right;
646
+ font-variant-numeric: tabular-nums;
647
+ }
648
+
649
+ /* ── Stats Section Header Colors ── */
650
+ .adm-section-purple { color: #a855f7; }
651
+ .adm-section-cyan { color: #06b6d4; }
652
+ .adm-section-violet { color: #8b5cf6; }
653
+ .adm-section-amber { color: #f59e0b; }
654
+ .adm-section-red { color: #ef4444; }
655
+
656
+ /* ── Breakdown Table ── */
657
+ .adm-breakdown-table th.num { text-align: right; }
658
+
659
+ /* ── Stats Inline Breakdown ── */
660
+ .adm-date-separator { color: var(--text-dim); }
661
+ .adm-mailbox-inline { padding: 10px 16px; }
662
+ .adm-blocked-reasons { margin-bottom: 6px; color: var(--text-dim); }
663
+ .adm-channels-summary { color: var(--text-dim); }
664
+
665
+ .adm-badge-live {
666
+ display: inline-block;
667
+ font-size: 0.6rem;
668
+ padding: 1px 6px;
669
+ border-radius: 4px;
670
+ background: var(--accent);
671
+ color: #fff;
672
+ vertical-align: middle;
673
+ margin-left: 6px;
674
+ }
@@ -173,7 +173,13 @@ export const getAdminMemory = (params = {}) => {
173
173
  export const deleteAdminMemory = (owner, key) => apiDelete(`/v1/admin/memory/${encodeURIComponent(owner)}/${encodeURIComponent(key)}`);
174
174
 
175
175
  // ── Stats ──
176
- export const getStats = () => apiGet('/v1/stats');
176
+ export const getStats = (from, to) => {
177
+ const params = new URLSearchParams();
178
+ if (from) params.set('from', from);
179
+ if (to) params.set('to', to);
180
+ const qs = params.toString();
181
+ return apiGet('/v1/stats' + (qs ? '?' + qs : ''));
182
+ };
177
183
 
178
184
  // ── Extensions & Instances ──
179
185
  export const getAvailableExtensions = () => apiGet('/v1/admin/extensions/available');
@@ -9,6 +9,7 @@ import {
9
9
  getFederationPeers, getFederationDirectory, approvePeeringRequest, rejectPeeringRequest,
10
10
  deletePeeringRequest, activatePeer, addPeerDirect, removePeer, removePeerEmergency,
11
11
  testFederationNode, joinGenesisNetwork, updatePeerPolicy, getNetworkDirectory,
12
+ getConfig, saveConfig,
12
13
  } from '/js/services/admin.js';
13
14
  import { useConfirm } from '/components/Modal.js';
14
15
 
@@ -30,6 +31,39 @@ export default function FederationTab({ data, reload }) {
30
31
  const [joining, setJoining] = useState(false);
31
32
  const [joinResult, setJoinResult] = useState(null);
32
33
 
34
+ // ── Federation Auth Policy state ──
35
+ const [authPolicy, setAuthPolicy] = useState('disabled');
36
+ const [defaultScopes, setDefaultScopes] = useState([]);
37
+ const [authPolicyLoading, setAuthPolicyLoading] = useState(true);
38
+
39
+ useEffect(() => {
40
+ getConfig().then(res => {
41
+ if (res.ok && res.data?.config) {
42
+ const c = res.data.config;
43
+ setAuthPolicy(c.federation?.auth_policy ?? c.federationAuthPolicy ?? 'disabled');
44
+ const scopes = c.federation?.default_scopes ?? c.federationDefaultScopes ?? 'memory:read,catalogue:read';
45
+ setDefaultScopes(typeof scopes === 'string' ? scopes.split(',').filter(Boolean) : (scopes || []));
46
+ }
47
+ setAuthPolicyLoading(false);
48
+ });
49
+ }, []);
50
+
51
+ const doSaveAuthPolicy = async (policy, scopes) => {
52
+ const changes = [
53
+ { key: 'federation.auth_policy', value: policy },
54
+ { key: 'federation.default_scopes', value: Array.isArray(scopes) ? scopes.join(',') : scopes },
55
+ ];
56
+ const res = await saveConfig(changes);
57
+ if (res.ok) { setActionSuccess(t('dashboard.fedPolicyUpdated')); setTimeout(() => setActionSuccess(null), 3000); }
58
+ else setActionError(res.error?.message || 'Failed to save');
59
+ };
60
+
61
+ const toggleScope = (scope) => {
62
+ const next = defaultScopes.includes(scope) ? defaultScopes.filter(s => s !== scope) : [...defaultScopes, scope];
63
+ setDefaultScopes(next);
64
+ doSaveAuthPolicy(authPolicy, next);
65
+ };
66
+
33
67
  // ── Network Directory state ──
34
68
  const [dirEntries, setDirEntries] = useState([]);
35
69
  const [dirSearch, setDirSearch] = useState('');
@@ -217,6 +251,31 @@ export default function FederationTab({ data, reload }) {
217
251
  { label: t('dashboard.fedPendingRequests'), value: pendingRequests.length, color: '#06b6d4' },
218
252
  ]} />
219
253
 
254
+ <!-- ═══ Federation Auth Policy ═══ -->
255
+ <div class="adm-card adm-mt-lg">
256
+ <h4 class="adm-mb-sm" style="margin:0">${t('dashboard.fedAuthPolicyTitle')}</h4>
257
+ <p class="adm-text-dim adm-text-base adm-mb-md" style="margin:0">${t('dashboard.fedAuthPolicyDesc')}</p>
258
+ ${authPolicyLoading ? html`<p class="adm-text-dim">...</p>` : html`
259
+ <div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
260
+ <select class="adm-input" style="max-width:240px" value=${authPolicy} onChange=${(e) => { setAuthPolicy(e.target.value); doSaveAuthPolicy(e.target.value, defaultScopes); }}>
261
+ <option value="disabled">${t('dashboard.fedAuthDisabled')}</option>
262
+ <option value="all_peers">${t('dashboard.fedAuthAllPeers')}</option>
263
+ <option value="specific_peers">${t('dashboard.fedAuthSpecificPeers')}</option>
264
+ </select>
265
+ </div>
266
+ ${authPolicy !== 'disabled' && html`
267
+ <div class="adm-mt-md">
268
+ <p class="adm-text-sm adm-text-dim adm-mb-sm">${t('dashboard.fedDefaultScopes')}</p>
269
+ <div style="display:flex;gap:8px;flex-wrap:wrap">
270
+ ${['memory:read','memory:write','catalogue:read','social:read','social:write','work:request','boards:read','boards:write'].map(s => html`
271
+ <label class="adm-text-sm"><input type="checkbox" checked=${defaultScopes.includes(s)} onChange=${() => toggleScope(s)} /> ${s}</label>
272
+ `)}
273
+ </div>
274
+ </div>
275
+ `}
276
+ `}
277
+ </div>
278
+
220
279
  <!-- ═══ Network Directory ═══ -->
221
280
  <div class="adm-card adm-mt-lg">
222
281
  <h4 class="adm-mb-sm" style="margin:0">${t('dashboard.fedNetworkDirectoryTitle')}</h4>
@@ -313,6 +372,7 @@ export default function FederationTab({ data, reload }) {
313
372
  <label class="adm-text-sm"><input type="checkbox" checked=${p.share_catalogue !== false} onChange=${(e) => doUpdatePolicy(p.node_id, 'share_catalogue', e.target.checked)} /> ${t('dashboard.fedShareCatalogue')}</label>
314
373
  <label class="adm-text-sm"><input type="checkbox" checked=${p.replicate_memory !== false} onChange=${(e) => doUpdatePolicy(p.node_id, 'replicate_memory', e.target.checked)} /> ${t('dashboard.fedReplicateMemory')}</label>
315
374
  <label class="adm-text-sm"><input type="checkbox" checked=${p.allow_routing !== false} onChange=${(e) => doUpdatePolicy(p.node_id, 'allow_routing', e.target.checked)} /> ${t('dashboard.fedAllowRouting')}</label>
375
+ <label class="adm-text-sm"><input type="checkbox" checked=${!!p.allow_federated_auth} onChange=${(e) => doUpdatePolicy(p.node_id, 'allow_federated_auth', e.target.checked)} /> ${t('dashboard.fedAllowAuth')}</label>
316
376
  <select class="adm-input" style="font-size:.75rem;padding:2px 4px;margin-top:4px" value=${p.peer_mode || 'federation'} onChange=${(e) => doUpdatePolicy(p.node_id, 'peer_mode', e.target.value)}>
317
377
  <option value="federation">${t('dashboard.fedPeerModeFederation')}</option>
318
378
  <option value="private">${t('dashboard.fedPeerModePrivate')}</option>