omegga 1.5.4 → 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 (91) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/bin/omegga +1 -1
  3. package/dist/cli/terminal.js +46 -0
  4. package/dist/cli/terminal.js.map +1 -1
  5. package/dist/webserver/backend/database.js +309 -11
  6. package/dist/webserver/backend/database.js.map +1 -1
  7. package/dist/webserver/backend/events.js +1 -1
  8. package/dist/webserver/backend/events.js.map +1 -1
  9. package/dist/webserver/backend/index.js +120 -2
  10. package/dist/webserver/backend/index.js.map +1 -1
  11. package/dist/webserver/backend/permissions.js +133 -0
  12. package/dist/webserver/backend/permissions.js.map +1 -0
  13. package/dist/webserver/backend/roleHierarchy.js +108 -0
  14. package/dist/webserver/backend/roleHierarchy.js.map +1 -0
  15. package/dist/webserver/backend/router/chat.js +9 -7
  16. package/dist/webserver/backend/router/chat.js.map +1 -1
  17. package/dist/webserver/backend/router/index.js +3 -1
  18. package/dist/webserver/backend/router/index.js.map +1 -1
  19. package/dist/webserver/backend/router/mfa.js +162 -0
  20. package/dist/webserver/backend/router/mfa.js.map +1 -0
  21. package/dist/webserver/backend/router/player.js +16 -6
  22. package/dist/webserver/backend/router/player.js.map +1 -1
  23. package/dist/webserver/backend/router/plugin.js +14 -10
  24. package/dist/webserver/backend/router/plugin.js.map +1 -1
  25. package/dist/webserver/backend/router/role.js +201 -6
  26. package/dist/webserver/backend/router/role.js.map +1 -1
  27. package/dist/webserver/backend/router/server.js +85 -71
  28. package/dist/webserver/backend/router/server.js.map +1 -1
  29. package/dist/webserver/backend/router/session.js +11 -4
  30. package/dist/webserver/backend/router/session.js.map +1 -1
  31. package/dist/webserver/backend/router/user.js +230 -15
  32. package/dist/webserver/backend/router/user.js.map +1 -1
  33. package/dist/webserver/backend/router/world.js +10 -9
  34. package/dist/webserver/backend/router/world.js.map +1 -1
  35. package/dist/webserver/backend/scopes.js +332 -0
  36. package/dist/webserver/backend/scopes.js.map +1 -0
  37. package/dist/webserver/backend/totp.js +32 -0
  38. package/dist/webserver/backend/totp.js.map +1 -0
  39. package/dist/webserver/backend/trpc.js +47 -4
  40. package/dist/webserver/backend/trpc.js.map +1 -1
  41. package/doc/permissions.md +264 -0
  42. package/package.json +7 -1
  43. package/public/app.html +1012 -527
  44. package/public/assets/IconShieldCheck-Bjlm8mX2.js +2 -0
  45. package/public/assets/IconShieldCheck-Bjlm8mX2.js.map +1 -0
  46. package/public/assets/IconWorld-Bx3cKMms.js +2 -0
  47. package/public/assets/IconWorld-Bx3cKMms.js.map +1 -0
  48. package/public/assets/IconWorldPlus-BHSmzWsZ.js +2 -0
  49. package/public/assets/IconWorldPlus-BHSmzWsZ.js.map +1 -0
  50. package/public/assets/app-J90GEVdo.js +7 -0
  51. package/public/assets/app-J90GEVdo.js.map +1 -0
  52. package/public/assets/auth-nn5rDnJ9.js +2 -0
  53. package/public/assets/auth-nn5rDnJ9.js.map +1 -0
  54. package/public/assets/index-B2DCW1zi.js +2 -0
  55. package/public/assets/index-B2DCW1zi.js.map +1 -0
  56. package/public/assets/index-BDkarAkX.js +3 -0
  57. package/public/assets/index-BDkarAkX.js.map +1 -0
  58. package/public/assets/{index-2cgXLhrt.js → index-DEL-PD_8.js} +2 -2
  59. package/public/assets/{index-2cgXLhrt.js.map → index-DEL-PD_8.js.map} +1 -1
  60. package/public/assets/index-wYDkzaAO.js +12 -0
  61. package/public/assets/index-wYDkzaAO.js.map +1 -0
  62. package/public/assets/shared/@components-BWkdUJhC.js +2 -0
  63. package/public/assets/shared/@components-BWkdUJhC.js.map +1 -0
  64. package/public/assets/shared/@hooks-BuRTblPY.js +2 -0
  65. package/public/assets/shared/{@hooks-Ch_hJYh0.js.map → @hooks-BuRTblPY.js.map} +1 -1
  66. package/public/assets/shared/@tabler_icons-react-CdTxTJVq.js +3 -0
  67. package/public/assets/shared/{@tabler_icons-react-iQk11eD_.js.map → @tabler_icons-react-CdTxTJVq.js.map} +1 -1
  68. package/public/assets/shared/{wouter-CUC1caxu.js → wouter-CNISJ5i6.js} +2 -2
  69. package/public/assets/shared/wouter-CNISJ5i6.js.map +1 -0
  70. package/public/assets/useHasScope-DZPCXNQE.js +2 -0
  71. package/public/assets/useHasScope-DZPCXNQE.js.map +1 -0
  72. package/public/auth.html +153 -40
  73. package/public/assets/IconArrowRight-BuVxhqKX.js +0 -2
  74. package/public/assets/IconArrowRight-BuVxhqKX.js.map +0 -1
  75. package/public/assets/IconWorld-B6GK5qpx.js +0 -2
  76. package/public/assets/IconWorld-B6GK5qpx.js.map +0 -1
  77. package/public/assets/IconWorldPlus-Dkos72Ws.js +0 -2
  78. package/public/assets/IconWorldPlus-Dkos72Ws.js.map +0 -1
  79. package/public/assets/app-CLZ-5FIi.js +0 -6
  80. package/public/assets/app-CLZ-5FIi.js.map +0 -1
  81. package/public/assets/auth-6N37o421.js +0 -2
  82. package/public/assets/auth-6N37o421.js.map +0 -1
  83. package/public/assets/index-DLZ8-XzO.js +0 -4
  84. package/public/assets/index-DLZ8-XzO.js.map +0 -1
  85. package/public/assets/index-DY750sgZ.js +0 -2
  86. package/public/assets/index-DY750sgZ.js.map +0 -1
  87. package/public/assets/shared/@components-DPHARroA.js +0 -2
  88. package/public/assets/shared/@components-DPHARroA.js.map +0 -1
  89. package/public/assets/shared/@hooks-Ch_hJYh0.js +0 -2
  90. package/public/assets/shared/@tabler_icons-react-iQk11eD_.js +0 -3
  91. package/public/assets/shared/wouter-CUC1caxu.js.map +0 -1
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const ScopeDomain = {
4
+ Chat: "chat",
5
+ Player: "player",
6
+ Plugin: "plugin",
7
+ Server: "server",
8
+ User: "user",
9
+ World: "world",
10
+ Role: "role",
11
+ Session: "session"
12
+ };
13
+ const ScopeName = {
14
+ ChatSend: "chat.send",
15
+ ChatRecent: "chat.recent",
16
+ ChatHistory: "chat.history",
17
+ ChatCalendar: "chat.calendar",
18
+ PlayerList: "player.list",
19
+ PlayerGet: "player.get",
20
+ PlayerBan: "player.ban",
21
+ PlayerKick: "player.kick",
22
+ PlayerUnban: "player.unban",
23
+ PlayerClearBricks: "player.clearBricks",
24
+ PluginList: "plugin.list",
25
+ PluginGet: "plugin.get",
26
+ PluginConfig: "plugin.config",
27
+ PluginLoad: "plugin.load",
28
+ PluginUnload: "plugin.unload",
29
+ PluginToggle: "plugin.toggle",
30
+ PluginReloadAll: "plugin.reloadAll",
31
+ ServerStatus: "server.status",
32
+ ServerStart: "server.start",
33
+ ServerStop: "server.stop",
34
+ ServerRestart: "server.restart",
35
+ ServerUpdateCheck: "server.update.check",
36
+ ServerUpdateRun: "server.update.run",
37
+ ServerAutorestartGet: "server.autorestart.get",
38
+ ServerAutorestartSet: "server.autorestart.set",
39
+ ServerUtilization: "server.utilization",
40
+ UserList: "user.list",
41
+ UserCreate: "user.create",
42
+ UserPasswd: "user.passwd",
43
+ UserBan: "user.ban",
44
+ UserDelete: "user.delete",
45
+ UserPermissions: "user.permissions",
46
+ UserGrantRole: "user.grantRole",
47
+ UserReadMfa: "user.readMfa",
48
+ UserResetMfa: "user.resetMfa",
49
+ WorldList: "world.list",
50
+ WorldActive: "world.active",
51
+ WorldNext: "world.next",
52
+ WorldRevisions: "world.revisions",
53
+ WorldMeta: "world.meta",
54
+ WorldLoad: "world.load",
55
+ WorldUse: "world.use",
56
+ WorldSave: "world.save",
57
+ WorldCreate: "world.create",
58
+ RoleList: "role.list",
59
+ RoleEdit: "role.edit",
60
+ RoleDefaultPermissions: "role.defaultPermissions",
61
+ RoleGrantPermission: "role.grantPermission",
62
+ SessionInfo: "session.info"
63
+ };
64
+ const S = ScopeName;
65
+ const D = ScopeDomain;
66
+ const SCOPES = {
67
+ // Chat
68
+ [S.ChatSend]: {
69
+ description: "Send messages in the dashboard chat widget",
70
+ readOnly: false,
71
+ domain: D.Chat
72
+ },
73
+ [S.ChatRecent]: {
74
+ description: "View recent chat on the dashboard",
75
+ readOnly: true,
76
+ domain: D.Chat
77
+ },
78
+ [S.ChatHistory]: {
79
+ description: "Browse past chat logs in the history view",
80
+ readOnly: true,
81
+ domain: D.Chat
82
+ },
83
+ [S.ChatCalendar]: {
84
+ description: "Navigate chat by date in the history view",
85
+ readOnly: true,
86
+ domain: D.Chat
87
+ },
88
+ // Player
89
+ [S.PlayerList]: {
90
+ description: "View the player list in the players view",
91
+ readOnly: true,
92
+ domain: D.Player
93
+ },
94
+ [S.PlayerGet]: {
95
+ description: "Inspect player details and history",
96
+ readOnly: true,
97
+ domain: D.Player
98
+ },
99
+ [S.PlayerBan]: {
100
+ description: "Ban players from the player inspector",
101
+ readOnly: false,
102
+ domain: D.Player
103
+ },
104
+ [S.PlayerKick]: {
105
+ description: "Kick players from the player inspector",
106
+ readOnly: false,
107
+ domain: D.Player
108
+ },
109
+ [S.PlayerUnban]: {
110
+ description: "Unban players from the player inspector",
111
+ readOnly: false,
112
+ domain: D.Player
113
+ },
114
+ [S.PlayerClearBricks]: {
115
+ description: "Clear a player's bricks from the player inspector",
116
+ readOnly: false,
117
+ domain: D.Player
118
+ },
119
+ // Plugin
120
+ [S.PluginList]: {
121
+ description: "View installed plugins in the plugins view",
122
+ readOnly: true,
123
+ domain: D.Plugin
124
+ },
125
+ [S.PluginGet]: {
126
+ description: "Inspect plugin details and configuration",
127
+ readOnly: true,
128
+ domain: D.Plugin
129
+ },
130
+ [S.PluginConfig]: {
131
+ description: "Edit plugin settings in the plugin inspector",
132
+ readOnly: false,
133
+ domain: D.Plugin
134
+ },
135
+ [S.PluginLoad]: {
136
+ description: "Load plugins from the plugin inspector",
137
+ readOnly: false,
138
+ domain: D.Plugin
139
+ },
140
+ [S.PluginUnload]: {
141
+ description: "Unload plugins from the plugin inspector",
142
+ readOnly: false,
143
+ domain: D.Plugin
144
+ },
145
+ [S.PluginToggle]: {
146
+ description: "Enable or disable plugins in the plugins view",
147
+ readOnly: false,
148
+ domain: D.Plugin
149
+ },
150
+ [S.PluginReloadAll]: {
151
+ description: "Reload all plugins from the plugins view",
152
+ readOnly: false,
153
+ domain: D.Plugin
154
+ },
155
+ // Server
156
+ [S.ServerStatus]: {
157
+ description: "View server status on the dashboard and server view",
158
+ readOnly: true,
159
+ domain: D.Server
160
+ },
161
+ [S.ServerStart]: {
162
+ description: "Start the server from the server view",
163
+ readOnly: false,
164
+ domain: D.Server
165
+ },
166
+ [S.ServerStop]: {
167
+ description: "Stop the server from the server view",
168
+ readOnly: false,
169
+ domain: D.Server
170
+ },
171
+ [S.ServerRestart]: {
172
+ description: "Restart the server from the server view",
173
+ readOnly: false,
174
+ domain: D.Server
175
+ },
176
+ [S.ServerUpdateCheck]: {
177
+ description: "Check for server updates in the server view",
178
+ readOnly: false,
179
+ domain: D.Server
180
+ },
181
+ [S.ServerUpdateRun]: {
182
+ description: "Run server updates from the server view",
183
+ readOnly: false,
184
+ domain: D.Server
185
+ },
186
+ [S.ServerAutorestartGet]: {
187
+ description: "View auto-restart settings in the server view",
188
+ readOnly: true,
189
+ domain: D.Server
190
+ },
191
+ [S.ServerAutorestartSet]: {
192
+ description: "Change auto-restart settings in the server view",
193
+ readOnly: false,
194
+ domain: D.Server
195
+ },
196
+ [S.ServerUtilization]: {
197
+ description: "View CPU, memory, and disk usage on the dashboard",
198
+ readOnly: true,
199
+ domain: D.Server
200
+ },
201
+ // User
202
+ [S.UserList]: {
203
+ description: "View web UI user accounts in the users view",
204
+ readOnly: true,
205
+ domain: D.User
206
+ },
207
+ [S.UserCreate]: {
208
+ description: "Create new user accounts in the users view",
209
+ readOnly: false,
210
+ domain: D.User
211
+ },
212
+ [S.UserPasswd]: {
213
+ description: "Change user passwords in the users view",
214
+ readOnly: false,
215
+ domain: D.User
216
+ },
217
+ [S.UserBan]: {
218
+ description: "Disable or re-enable users in the user inspector",
219
+ readOnly: false,
220
+ domain: D.User
221
+ },
222
+ [S.UserDelete]: {
223
+ description: "Permanently delete user accounts",
224
+ readOnly: false,
225
+ domain: D.User
226
+ },
227
+ [S.UserPermissions]: {
228
+ description: "Edit user permissions in the users view",
229
+ readOnly: false,
230
+ domain: D.User
231
+ },
232
+ [S.UserGrantRole]: {
233
+ description: "Assign and revoke roles to/from users",
234
+ readOnly: false,
235
+ domain: D.User
236
+ },
237
+ [S.UserReadMfa]: {
238
+ description: "View MFA status of other users in the user inspector",
239
+ readOnly: true,
240
+ domain: D.User
241
+ },
242
+ [S.UserResetMfa]: {
243
+ description: "Reset MFA for other users in the user inspector",
244
+ readOnly: false,
245
+ domain: D.User
246
+ },
247
+ // World
248
+ [S.WorldList]: {
249
+ description: "View available worlds in the worlds view",
250
+ readOnly: true,
251
+ domain: D.World
252
+ },
253
+ [S.WorldActive]: {
254
+ description: "See which world is currently loaded",
255
+ readOnly: true,
256
+ domain: D.World
257
+ },
258
+ [S.WorldNext]: {
259
+ description: "See which world will load next",
260
+ readOnly: true,
261
+ domain: D.World
262
+ },
263
+ [S.WorldRevisions]: {
264
+ description: "View world save revisions in the world inspector",
265
+ readOnly: true,
266
+ domain: D.World
267
+ },
268
+ [S.WorldMeta]: {
269
+ description: "View world metadata in the world inspector",
270
+ readOnly: true,
271
+ domain: D.World
272
+ },
273
+ [S.WorldLoad]: {
274
+ description: "Load worlds from the world inspector",
275
+ readOnly: false,
276
+ domain: D.World
277
+ },
278
+ [S.WorldUse]: {
279
+ description: "Set the default world in the worlds view",
280
+ readOnly: false,
281
+ domain: D.World
282
+ },
283
+ [S.WorldSave]: {
284
+ description: "Save the current world from the worlds or server view",
285
+ readOnly: false,
286
+ domain: D.World
287
+ },
288
+ [S.WorldCreate]: {
289
+ description: "Create new worlds in the worlds view",
290
+ readOnly: false,
291
+ domain: D.World
292
+ },
293
+ // Role
294
+ [S.RoleList]: {
295
+ description: "View roles in the roles view",
296
+ readOnly: true,
297
+ domain: D.Role
298
+ },
299
+ [S.RoleEdit]: {
300
+ description: "Create, edit, delete, and reorder roles",
301
+ readOnly: false,
302
+ domain: D.Role
303
+ },
304
+ [S.RoleDefaultPermissions]: {
305
+ description: "Edit the default permissions that apply to all users",
306
+ readOnly: false,
307
+ domain: D.Role
308
+ },
309
+ [S.RoleGrantPermission]: {
310
+ description: "Add or remove permissions within roles",
311
+ readOnly: false,
312
+ domain: D.Role
313
+ },
314
+ // Session
315
+ [S.SessionInfo]: {
316
+ description: "View session info",
317
+ readOnly: true,
318
+ domain: D.Session
319
+ }
320
+ };
321
+ [
322
+ ...new Set(Object.values(SCOPES).map((s) => s.domain))
323
+ ];
324
+ const SCOPES_BY_DOMAIN = {};
325
+ for (const [scope, info] of Object.entries(SCOPES)) {
326
+ (SCOPES_BY_DOMAIN[info.domain] ??= []).push(scope);
327
+ }
328
+ exports.SCOPES = SCOPES;
329
+ exports.SCOPES_BY_DOMAIN = SCOPES_BY_DOMAIN;
330
+ exports.ScopeDomain = ScopeDomain;
331
+ exports.ScopeName = ScopeName;
332
+ //# sourceMappingURL=scopes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopes.js","sources":["../../../src/webserver/backend/scopes.ts"],"sourcesContent":["export const ScopeDomain = {\n Chat: 'chat',\n Player: 'player',\n Plugin: 'plugin',\n Server: 'server',\n User: 'user',\n World: 'world',\n Role: 'role',\n Session: 'session',\n} as const;\n\nexport const ScopeName = {\n ChatSend: 'chat.send',\n ChatRecent: 'chat.recent',\n ChatHistory: 'chat.history',\n ChatCalendar: 'chat.calendar',\n\n PlayerList: 'player.list',\n PlayerGet: 'player.get',\n PlayerBan: 'player.ban',\n PlayerKick: 'player.kick',\n PlayerUnban: 'player.unban',\n PlayerClearBricks: 'player.clearBricks',\n\n PluginList: 'plugin.list',\n PluginGet: 'plugin.get',\n PluginConfig: 'plugin.config',\n PluginLoad: 'plugin.load',\n PluginUnload: 'plugin.unload',\n PluginToggle: 'plugin.toggle',\n PluginReloadAll: 'plugin.reloadAll',\n\n ServerStatus: 'server.status',\n ServerStart: 'server.start',\n ServerStop: 'server.stop',\n ServerRestart: 'server.restart',\n ServerUpdateCheck: 'server.update.check',\n ServerUpdateRun: 'server.update.run',\n ServerAutorestartGet: 'server.autorestart.get',\n ServerAutorestartSet: 'server.autorestart.set',\n ServerUtilization: 'server.utilization',\n\n UserList: 'user.list',\n UserCreate: 'user.create',\n UserPasswd: 'user.passwd',\n UserBan: 'user.ban',\n UserDelete: 'user.delete',\n UserPermissions: 'user.permissions',\n UserGrantRole: 'user.grantRole',\n UserReadMfa: 'user.readMfa',\n UserResetMfa: 'user.resetMfa',\n\n WorldList: 'world.list',\n WorldActive: 'world.active',\n WorldNext: 'world.next',\n WorldRevisions: 'world.revisions',\n WorldMeta: 'world.meta',\n WorldLoad: 'world.load',\n WorldUse: 'world.use',\n WorldSave: 'world.save',\n WorldCreate: 'world.create',\n\n RoleList: 'role.list',\n RoleEdit: 'role.edit',\n RoleDefaultPermissions: 'role.defaultPermissions',\n RoleGrantPermission: 'role.grantPermission',\n\n SessionInfo: 'session.info',\n} as const;\n\nconst S = ScopeName;\nconst D = ScopeDomain;\n\nexport const SCOPES = {\n // Chat\n [S.ChatSend]: {\n description: 'Send messages in the dashboard chat widget',\n readOnly: false,\n domain: D.Chat,\n },\n [S.ChatRecent]: {\n description: 'View recent chat on the dashboard',\n readOnly: true,\n domain: D.Chat,\n },\n [S.ChatHistory]: {\n description: 'Browse past chat logs in the history view',\n readOnly: true,\n domain: D.Chat,\n },\n [S.ChatCalendar]: {\n description: 'Navigate chat by date in the history view',\n readOnly: true,\n domain: D.Chat,\n },\n\n // Player\n [S.PlayerList]: {\n description: 'View the player list in the players view',\n readOnly: true,\n domain: D.Player,\n },\n [S.PlayerGet]: {\n description: 'Inspect player details and history',\n readOnly: true,\n domain: D.Player,\n },\n [S.PlayerBan]: {\n description: 'Ban players from the player inspector',\n readOnly: false,\n domain: D.Player,\n },\n [S.PlayerKick]: {\n description: 'Kick players from the player inspector',\n readOnly: false,\n domain: D.Player,\n },\n [S.PlayerUnban]: {\n description: 'Unban players from the player inspector',\n readOnly: false,\n domain: D.Player,\n },\n [S.PlayerClearBricks]: {\n description: \"Clear a player's bricks from the player inspector\",\n readOnly: false,\n domain: D.Player,\n },\n\n // Plugin\n [S.PluginList]: {\n description: 'View installed plugins in the plugins view',\n readOnly: true,\n domain: D.Plugin,\n },\n [S.PluginGet]: {\n description: 'Inspect plugin details and configuration',\n readOnly: true,\n domain: D.Plugin,\n },\n [S.PluginConfig]: {\n description: 'Edit plugin settings in the plugin inspector',\n readOnly: false,\n domain: D.Plugin,\n },\n [S.PluginLoad]: {\n description: 'Load plugins from the plugin inspector',\n readOnly: false,\n domain: D.Plugin,\n },\n [S.PluginUnload]: {\n description: 'Unload plugins from the plugin inspector',\n readOnly: false,\n domain: D.Plugin,\n },\n [S.PluginToggle]: {\n description: 'Enable or disable plugins in the plugins view',\n readOnly: false,\n domain: D.Plugin,\n },\n [S.PluginReloadAll]: {\n description: 'Reload all plugins from the plugins view',\n readOnly: false,\n domain: D.Plugin,\n },\n\n // Server\n [S.ServerStatus]: {\n description: 'View server status on the dashboard and server view',\n readOnly: true,\n domain: D.Server,\n },\n [S.ServerStart]: {\n description: 'Start the server from the server view',\n readOnly: false,\n domain: D.Server,\n },\n [S.ServerStop]: {\n description: 'Stop the server from the server view',\n readOnly: false,\n domain: D.Server,\n },\n [S.ServerRestart]: {\n description: 'Restart the server from the server view',\n readOnly: false,\n domain: D.Server,\n },\n [S.ServerUpdateCheck]: {\n description: 'Check for server updates in the server view',\n readOnly: false,\n domain: D.Server,\n },\n [S.ServerUpdateRun]: {\n description: 'Run server updates from the server view',\n readOnly: false,\n domain: D.Server,\n },\n [S.ServerAutorestartGet]: {\n description: 'View auto-restart settings in the server view',\n readOnly: true,\n domain: D.Server,\n },\n [S.ServerAutorestartSet]: {\n description: 'Change auto-restart settings in the server view',\n readOnly: false,\n domain: D.Server,\n },\n [S.ServerUtilization]: {\n description: 'View CPU, memory, and disk usage on the dashboard',\n readOnly: true,\n domain: D.Server,\n },\n\n // User\n [S.UserList]: {\n description: 'View web UI user accounts in the users view',\n readOnly: true,\n domain: D.User,\n },\n [S.UserCreate]: {\n description: 'Create new user accounts in the users view',\n readOnly: false,\n domain: D.User,\n },\n [S.UserPasswd]: {\n description: 'Change user passwords in the users view',\n readOnly: false,\n domain: D.User,\n },\n [S.UserBan]: {\n description: 'Disable or re-enable users in the user inspector',\n readOnly: false,\n domain: D.User,\n },\n [S.UserDelete]: {\n description: 'Permanently delete user accounts',\n readOnly: false,\n domain: D.User,\n },\n [S.UserPermissions]: {\n description: 'Edit user permissions in the users view',\n readOnly: false,\n domain: D.User,\n },\n [S.UserGrantRole]: {\n description: 'Assign and revoke roles to/from users',\n readOnly: false,\n domain: D.User,\n },\n [S.UserReadMfa]: {\n description: 'View MFA status of other users in the user inspector',\n readOnly: true,\n domain: D.User,\n },\n [S.UserResetMfa]: {\n description: 'Reset MFA for other users in the user inspector',\n readOnly: false,\n domain: D.User,\n },\n\n // World\n [S.WorldList]: {\n description: 'View available worlds in the worlds view',\n readOnly: true,\n domain: D.World,\n },\n [S.WorldActive]: {\n description: 'See which world is currently loaded',\n readOnly: true,\n domain: D.World,\n },\n [S.WorldNext]: {\n description: 'See which world will load next',\n readOnly: true,\n domain: D.World,\n },\n [S.WorldRevisions]: {\n description: 'View world save revisions in the world inspector',\n readOnly: true,\n domain: D.World,\n },\n [S.WorldMeta]: {\n description: 'View world metadata in the world inspector',\n readOnly: true,\n domain: D.World,\n },\n [S.WorldLoad]: {\n description: 'Load worlds from the world inspector',\n readOnly: false,\n domain: D.World,\n },\n [S.WorldUse]: {\n description: 'Set the default world in the worlds view',\n readOnly: false,\n domain: D.World,\n },\n [S.WorldSave]: {\n description: 'Save the current world from the worlds or server view',\n readOnly: false,\n domain: D.World,\n },\n [S.WorldCreate]: {\n description: 'Create new worlds in the worlds view',\n readOnly: false,\n domain: D.World,\n },\n\n // Role\n [S.RoleList]: {\n description: 'View roles in the roles view',\n readOnly: true,\n domain: D.Role,\n },\n [S.RoleEdit]: {\n description: 'Create, edit, delete, and reorder roles',\n readOnly: false,\n domain: D.Role,\n },\n [S.RoleDefaultPermissions]: {\n description: 'Edit the default permissions that apply to all users',\n readOnly: false,\n domain: D.Role,\n },\n [S.RoleGrantPermission]: {\n description: 'Add or remove permissions within roles',\n readOnly: false,\n domain: D.Role,\n },\n\n // Session\n [S.SessionInfo]: {\n description: 'View session info',\n readOnly: true,\n domain: D.Session,\n },\n} as const;\n\nexport type Scope = keyof typeof SCOPES;\n\nexport const DOMAIN_LABELS: Record<string, string> = {\n [D.Chat]: 'Chat',\n [D.Player]: 'Player',\n [D.Plugin]: 'Plugin',\n [D.Server]: 'Server',\n [D.User]: 'User',\n [D.World]: 'World',\n [D.Role]: 'Role',\n};\n\nexport const DOMAIN_ORDER = [\n D.Chat,\n D.Player,\n D.Plugin,\n D.Server,\n D.User,\n D.World,\n D.Role,\n] as const;\n\nexport const DOMAINS = [\n ...new Set(Object.values(SCOPES).map(s => s.domain)),\n] as const;\n\nexport type Domain = (typeof DOMAINS)[number];\n\nexport const SCOPES_BY_DOMAIN: Record<string, Scope[]> = {};\nfor (const [scope, info] of Object.entries(SCOPES)) {\n (SCOPES_BY_DOMAIN[info.domain] ??= []).push(scope as Scope);\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const otpauth = require("otpauth");
4
+ const ISSUER = "Omegga";
5
+ const DIGITS = 6;
6
+ const PERIOD = 30;
7
+ const ALGORITHM = "SHA1";
8
+ function makeTOTP(secret, label = "") {
9
+ return new otpauth.TOTP({
10
+ issuer: ISSUER,
11
+ label,
12
+ algorithm: ALGORITHM,
13
+ digits: DIGITS,
14
+ period: PERIOD,
15
+ secret: otpauth.Secret.fromBase32(secret)
16
+ });
17
+ }
18
+ function generateSecret() {
19
+ return new otpauth.Secret().base32;
20
+ }
21
+ function verifyToken(token, secret, window = 1) {
22
+ if (!/^\d{6}$/.test(token)) return false;
23
+ const delta = makeTOTP(secret).validate({ token, window });
24
+ return delta !== null;
25
+ }
26
+ function generateURI(secret, username) {
27
+ return makeTOTP(secret, username).toString();
28
+ }
29
+ exports.generateSecret = generateSecret;
30
+ exports.generateURI = generateURI;
31
+ exports.verifyToken = verifyToken;
32
+ //# sourceMappingURL=totp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.js","sources":["../../../src/webserver/backend/totp.ts"],"sourcesContent":["import { TOTP, Secret } from 'otpauth';\n\nconst ISSUER = 'Omegga';\nconst DIGITS = 6;\nconst PERIOD = 30;\nconst ALGORITHM = 'SHA1';\n\nfunction makeTOTP(secret: string, label = ''): TOTP {\n return new TOTP({\n issuer: ISSUER,\n label,\n algorithm: ALGORITHM,\n digits: DIGITS,\n period: PERIOD,\n secret: Secret.fromBase32(secret),\n });\n}\n\nexport function generateSecret(): string {\n return new Secret().base32;\n}\n\nexport function generateToken(secret: string): string {\n return makeTOTP(secret).generate();\n}\n\nexport function verifyToken(\n token: string,\n secret: string,\n window = 1,\n): boolean {\n if (!/^\\d{6}$/.test(token)) return false;\n const delta = makeTOTP(secret).validate({ token, window });\n return delta !== null;\n}\n\nexport function generateURI(secret: string, username: string): string {\n return makeTOTP(secret, username).toString();\n}\n"],"names":["TOTP","Secret"],"mappings":";;;AAEA,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,YAAY;AAElB,SAAS,SAAS,QAAgB,QAAQ,IAAU;AAClD,SAAO,IAAIA,QAAAA,KAAK;AAAA,IACd,QAAQ;AAAA,IACR;AAAA,IACA,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQC,QAAAA,OAAO,WAAW,MAAM;AAAA,EAAA,CACjC;AACH;AAEO,SAAS,iBAAyB;AACvC,SAAO,IAAIA,QAAAA,SAAS;AACtB;AAMO,SAAS,YACd,OACA,QACA,SAAS,GACA;AACT,MAAI,CAAC,UAAU,KAAK,KAAK,EAAG,QAAO;AACnC,QAAM,QAAQ,SAAS,MAAM,EAAE,SAAS,EAAE,OAAO,QAAQ;AACzD,SAAO,UAAU;AACnB;AAEO,SAAS,YAAY,QAAgB,UAA0B;AACpE,SAAO,SAAS,QAAQ,QAAQ,EAAE,SAAA;AACpC;;;;"}
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const logger = require("../../logger.js");
4
4
  const server = require("@trpc/server");
5
+ const events = require("./events.js");
6
+ const permissions = require("./permissions.js");
5
7
  let _deps = null;
6
8
  function setContextDeps(deps) {
7
9
  _deps = deps;
@@ -13,7 +15,11 @@ function getContextDeps() {
13
15
  async function createContext(opts) {
14
16
  const { database } = getContextDeps();
15
17
  const req = opts.req;
16
- const userId = req.session?.userId;
18
+ const session = req.session;
19
+ if (session?.mfaPending) {
20
+ throw new server.TRPCError({ code: "UNAUTHORIZED" });
21
+ }
22
+ const userId = session?.userId;
17
23
  const user = await database.findUserById(userId);
18
24
  if (!user || user.isBanned) {
19
25
  throw new server.TRPCError({ code: "UNAUTHORIZED" });
@@ -23,21 +29,58 @@ async function createContext(opts) {
23
29
  { $set: { lastOnline: Date.now() } }
24
30
  );
25
31
  const usernameText = `[${(user.username || "Admin").brightMagenta}]`;
32
+ let _userAbort = null;
33
+ const getUserAbort = () => {
34
+ if (!_userAbort) {
35
+ _userAbort = new AbortController();
36
+ const ac = _userAbort;
37
+ const onInvalidated = (name) => {
38
+ if (name === user.username) ac.abort();
39
+ };
40
+ events.serverEvents.on("userInvalidated", onInvalidated);
41
+ ac.signal.addEventListener("abort", () => {
42
+ events.serverEvents.off("userInvalidated", onInvalidated);
43
+ });
44
+ }
45
+ return _userAbort;
46
+ };
26
47
  return {
27
48
  user,
49
+ req,
50
+ get userAbort() {
51
+ return getUserAbort();
52
+ },
28
53
  log: (...args) => logger.default.logp(usernameText, ...args),
29
54
  error: (...args) => logger.default.errorp(usernameText, ...args)
30
55
  };
31
56
  }
32
- const t = server.initTRPC.context().create();
57
+ const t = server.initTRPC.context().create({
58
+ errorFormatter({ shape, error, path, ctx }) {
59
+ const user = ctx?.user?.username || "?";
60
+ logger.default.errorp(
61
+ `[${user.brightMagenta}]`,
62
+ `${error.code} ${path ?? "?"}: ${error.message}`
63
+ );
64
+ return shape;
65
+ }
66
+ });
33
67
  const router = t.router;
34
68
  t.procedure;
35
69
  const mergeRouters = t.mergeRouters;
36
- const requireScope = (scope) => t.middleware(({ ctx, next }) => {
70
+ const requireScope = (scope) => t.middleware(async ({ ctx, next }) => {
37
71
  if (!ctx.user) throw new server.TRPCError({ code: "UNAUTHORIZED" });
72
+ if (ctx.user.isOwner) return next({ ctx });
73
+ const { database } = getContextDeps();
74
+ const rolePermissions = await database.getUserRolePermissions(ctx.user);
75
+ if (!permissions.userHasScope(ctx.user, scope, rolePermissions)) {
76
+ throw new server.TRPCError({
77
+ code: "FORBIDDEN",
78
+ message: `missing scope: ${scope}`
79
+ });
80
+ }
38
81
  return next({ ctx });
39
82
  });
40
- const protectedProcedure = (scope) => t.procedure.use(requireScope());
83
+ const protectedProcedure = (scope) => t.procedure.use(requireScope(scope));
41
84
  exports.createContext = createContext;
42
85
  exports.getContextDeps = getContextDeps;
43
86
  exports.mergeRouters = mergeRouters;
@@ -1 +1 @@
1
- {"version":3,"file":"trpc.js","sources":["../../../src/webserver/backend/trpc.ts"],"sourcesContent":["import Logger from '@/logger';\nimport type Omegga from '@omegga/server';\nimport { initTRPC, TRPCError } from '@trpc/server';\nimport type { CreateExpressContextOptions } from '@trpc/server/adapters/express';\nimport type Database from './database';\nimport type { Scope } from './scopes';\nimport type { IStoreUser } from './types';\n\nexport type ContextDeps = {\n database: Database;\n omegga: Omegga;\n};\n\nlet _deps: ContextDeps | null = null;\n\nexport function setContextDeps(deps: ContextDeps) {\n _deps = deps;\n}\n\nexport function getContextDeps(): ContextDeps {\n if (!_deps) throw new Error('tRPC context deps not initialized');\n return _deps;\n}\n\nexport type Context = {\n user: IStoreUser & { _id: string };\n log: (...args: any[]) => void;\n error: (...args: any[]) => void;\n};\n\nexport async function createContext(\n opts: CreateExpressContextOptions,\n): Promise<Context> {\n const { database } = getContextDeps();\n const req = opts.req;\n\n const userId = (req as any).session?.userId;\n const user = await database.findUserById(userId);\n if (!user || user.isBanned) {\n throw new TRPCError({ code: 'UNAUTHORIZED' });\n }\n\n // Update lastOnline (migrated from Socket.IO connect handler)\n await database.stores.users.update<IStoreUser>(\n { _id: user._id },\n { $set: { lastOnline: Date.now() } },\n );\n\n const usernameText = `[${(user.username || 'Admin').brightMagenta}]`;\n\n return {\n user,\n log: (...args: any[]) => Logger.logp(usernameText, ...args),\n error: (...args: any[]) => Logger.errorp(usernameText, ...args),\n };\n}\n\nconst t = initTRPC.context<Context>().create();\n\nexport const router = t.router;\nexport const publicProcedure = t.procedure;\nexport const mergeRouters = t.mergeRouters;\n\nexport const requireScope = (scope: Scope) =>\n t.middleware(({ ctx, next }) => {\n if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });\n // TODO: check ctx.user.roles against scope\n // For now, all authenticated users pass\n return next({ ctx });\n });\n\nexport const protectedProcedure = (scope: Scope) =>\n t.procedure.use(requireScope(scope));\n"],"names":["TRPCError","Logger","initTRPC"],"mappings":";;;;AAaA,IAAI,QAA4B;AAEzB,SAAS,eAAe,MAAmB;AAChD,UAAQ;AACV;AAEO,SAAS,iBAA8B;AAC5C,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,mCAAmC;AAC/D,SAAO;AACT;AAQA,eAAsB,cACpB,MACkB;AAClB,QAAM,EAAE,SAAA,IAAa,eAAA;AACrB,QAAM,MAAM,KAAK;AAEjB,QAAM,SAAU,IAAY,SAAS;AACrC,QAAM,OAAO,MAAM,SAAS,aAAa,MAAM;AAC/C,MAAI,CAAC,QAAQ,KAAK,UAAU;AAC1B,UAAM,IAAIA,OAAAA,UAAU,EAAE,MAAM,gBAAgB;AAAA,EAC9C;AAGA,QAAM,SAAS,OAAO,MAAM;AAAA,IAC1B,EAAE,KAAK,KAAK,IAAA;AAAA,IACZ,EAAE,MAAM,EAAE,YAAY,KAAK,IAAA,IAAM;AAAA,EAAE;AAGrC,QAAM,eAAe,KAAK,KAAK,YAAY,SAAS,aAAa;AAEjE,SAAO;AAAA,IACL;AAAA,IACA,KAAK,IAAI,SAAgBC,OAAAA,QAAO,KAAK,cAAc,GAAG,IAAI;AAAA,IAC1D,OAAO,IAAI,SAAgBA,OAAAA,QAAO,OAAO,cAAc,GAAG,IAAI;AAAA,EAAA;AAElE;AAEA,MAAM,IAAIC,OAAAA,SAAS,QAAA,EAAmB,OAAA;AAE/B,MAAM,SAAS,EAAE;AACO,EAAE;AAC1B,MAAM,eAAe,EAAE;AAEvB,MAAM,eAAe,CAAC,UAC3B,EAAE,WAAW,CAAC,EAAE,KAAK,WAAW;AAC9B,MAAI,CAAC,IAAI,KAAM,OAAM,IAAIF,OAAAA,UAAU,EAAE,MAAM,gBAAgB;AAG3D,SAAO,KAAK,EAAE,KAAK;AACrB,CAAC;AAEI,MAAM,qBAAqB,CAAC,UACjC,EAAE,UAAU,IAAI,aAAkB,CAAC;;;;;;;;"}
1
+ {"version":3,"file":"trpc.js","sources":["../../../src/webserver/backend/trpc.ts"],"sourcesContent":["import Logger from '@/logger';\nimport type Omegga from '@omegga/server';\nimport { initTRPC, TRPCError } from '@trpc/server';\nimport type { CreateExpressContextOptions } from '@trpc/server/adapters/express';\nimport type Database from './database';\nimport { serverEvents } from './events';\nimport { userHasScope } from './permissions';\nimport type { Scope } from './scopes';\nimport type { IStoreUser } from './types';\n\nexport type ContextDeps = {\n database: Database;\n omegga: Omegga;\n};\n\nlet _deps: ContextDeps | null = null;\n\nexport function setContextDeps(deps: ContextDeps) {\n _deps = deps;\n}\n\nexport function getContextDeps(): ContextDeps {\n if (!_deps) throw new Error('tRPC context deps not initialized');\n return _deps;\n}\n\nexport type Context = {\n user: IStoreUser & { _id: string };\n req: import('express').Request;\n userAbort: AbortController;\n log: (...args: any[]) => void;\n error: (...args: any[]) => void;\n};\n\nexport async function createContext(\n opts: CreateExpressContextOptions,\n): Promise<Context> {\n const { database } = getContextDeps();\n const req = opts.req;\n\n const session = (req as any).session;\n if (session?.mfaPending) {\n throw new TRPCError({ code: 'UNAUTHORIZED' });\n }\n const userId = session?.userId;\n const user = await database.findUserById(userId);\n if (!user || user.isBanned) {\n throw new TRPCError({ code: 'UNAUTHORIZED' });\n }\n\n // Update lastOnline (migrated from Socket.IO connect handler)\n await database.stores.users.update<IStoreUser>(\n { _id: user._id },\n { $set: { lastOnline: Date.now() } },\n );\n\n const usernameText = `[${(user.username || 'Admin').brightMagenta}]`;\n\n let _userAbort: AbortController | null = null;\n const getUserAbort = () => {\n if (!_userAbort) {\n _userAbort = new AbortController();\n const ac = _userAbort;\n const onInvalidated = (name: string) => {\n if (name === user.username) ac.abort();\n };\n serverEvents.on('userInvalidated', onInvalidated);\n ac.signal.addEventListener('abort', () => {\n serverEvents.off('userInvalidated', onInvalidated);\n });\n }\n return _userAbort;\n };\n\n return {\n user,\n req,\n get userAbort() {\n return getUserAbort();\n },\n log: (...args: any[]) => Logger.logp(usernameText, ...args),\n error: (...args: any[]) => Logger.errorp(usernameText, ...args),\n };\n}\n\nconst t = initTRPC.context<Context>().create({\n errorFormatter({ shape, error, path, ctx }) {\n const user = (ctx as Context | undefined)?.user?.username || '?';\n Logger.errorp(\n `[${user.brightMagenta}]`,\n `${error.code} ${path ?? '?'}: ${error.message}`,\n );\n return shape;\n },\n});\n\nexport const router = t.router;\nexport const publicProcedure = t.procedure;\nexport const mergeRouters = t.mergeRouters;\n\nexport const requireScope = (scope: Scope) =>\n t.middleware(async ({ ctx, next }) => {\n if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });\n if (ctx.user.isOwner) return next({ ctx });\n const { database } = getContextDeps();\n const rolePermissions = await database.getUserRolePermissions(ctx.user);\n if (!userHasScope(ctx.user, scope, rolePermissions)) {\n throw new TRPCError({\n code: 'FORBIDDEN',\n message: `missing scope: ${scope}`,\n });\n }\n return next({ ctx });\n });\n\nexport const protectedProcedure = (scope: Scope) =>\n t.procedure.use(requireScope(scope));\n"],"names":["TRPCError","serverEvents","Logger","initTRPC","userHasScope"],"mappings":";;;;;;AAeA,IAAI,QAA4B;AAEzB,SAAS,eAAe,MAAmB;AAChD,UAAQ;AACV;AAEO,SAAS,iBAA8B;AAC5C,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,mCAAmC;AAC/D,SAAO;AACT;AAUA,eAAsB,cACpB,MACkB;AAClB,QAAM,EAAE,SAAA,IAAa,eAAA;AACrB,QAAM,MAAM,KAAK;AAEjB,QAAM,UAAW,IAAY;AAC7B,MAAI,SAAS,YAAY;AACvB,UAAM,IAAIA,OAAAA,UAAU,EAAE,MAAM,gBAAgB;AAAA,EAC9C;AACA,QAAM,SAAS,SAAS;AACxB,QAAM,OAAO,MAAM,SAAS,aAAa,MAAM;AAC/C,MAAI,CAAC,QAAQ,KAAK,UAAU;AAC1B,UAAM,IAAIA,OAAAA,UAAU,EAAE,MAAM,gBAAgB;AAAA,EAC9C;AAGA,QAAM,SAAS,OAAO,MAAM;AAAA,IAC1B,EAAE,KAAK,KAAK,IAAA;AAAA,IACZ,EAAE,MAAM,EAAE,YAAY,KAAK,IAAA,IAAM;AAAA,EAAE;AAGrC,QAAM,eAAe,KAAK,KAAK,YAAY,SAAS,aAAa;AAEjE,MAAI,aAAqC;AACzC,QAAM,eAAe,MAAM;AACzB,QAAI,CAAC,YAAY;AACf,mBAAa,IAAI,gBAAA;AACjB,YAAM,KAAK;AACX,YAAM,gBAAgB,CAAC,SAAiB;AACtC,YAAI,SAAS,KAAK,SAAU,IAAG,MAAA;AAAA,MACjC;AACAC,0BAAa,GAAG,mBAAmB,aAAa;AAChD,SAAG,OAAO,iBAAiB,SAAS,MAAM;AACxCA,4BAAa,IAAI,mBAAmB,aAAa;AAAA,MACnD,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,IAAI,YAAY;AACd,aAAO,aAAA;AAAA,IACT;AAAA,IACA,KAAK,IAAI,SAAgBC,OAAAA,QAAO,KAAK,cAAc,GAAG,IAAI;AAAA,IAC1D,OAAO,IAAI,SAAgBA,OAAAA,QAAO,OAAO,cAAc,GAAG,IAAI;AAAA,EAAA;AAElE;AAEA,MAAM,IAAIC,OAAAA,SAAS,QAAA,EAAmB,OAAO;AAAA,EAC3C,eAAe,EAAE,OAAO,OAAO,MAAM,OAAO;AAC1C,UAAM,OAAQ,KAA6B,MAAM,YAAY;AAC7DD,WAAAA,QAAO;AAAA,MACL,IAAI,KAAK,aAAa;AAAA,MACtB,GAAG,MAAM,IAAI,IAAI,QAAQ,GAAG,KAAK,MAAM,OAAO;AAAA,IAAA;AAEhD,WAAO;AAAA,EACT;AACF,CAAC;AAEM,MAAM,SAAS,EAAE;AACO,EAAE;AAC1B,MAAM,eAAe,EAAE;AAEvB,MAAM,eAAe,CAAC,UAC3B,EAAE,WAAW,OAAO,EAAE,KAAK,WAAW;AACpC,MAAI,CAAC,IAAI,KAAM,OAAM,IAAIF,OAAAA,UAAU,EAAE,MAAM,gBAAgB;AAC3D,MAAI,IAAI,KAAK,gBAAgB,KAAK,EAAE,KAAK;AACzC,QAAM,EAAE,SAAA,IAAa,eAAA;AACrB,QAAM,kBAAkB,MAAM,SAAS,uBAAuB,IAAI,IAAI;AACtE,MAAI,CAACI,YAAAA,aAAa,IAAI,MAAM,OAAO,eAAe,GAAG;AACnD,UAAM,IAAIJ,OAAAA,UAAU;AAAA,MAClB,MAAM;AAAA,MACN,SAAS,kBAAkB,KAAK;AAAA,IAAA,CACjC;AAAA,EACH;AACA,SAAO,KAAK,EAAE,KAAK;AACrB,CAAC;AAEI,MAAM,qBAAqB,CAAC,UACjC,EAAE,UAAU,IAAI,aAAa,KAAK,CAAC;;;;;;;;"}