omegga 1.5.4 → 1.6.0

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 (89) hide show
  1. package/CHANGELOG.md +14 -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 +219 -7
  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 +74 -0
  12. package/dist/webserver/backend/permissions.js.map +1 -0
  13. package/dist/webserver/backend/router/chat.js +9 -7
  14. package/dist/webserver/backend/router/chat.js.map +1 -1
  15. package/dist/webserver/backend/router/index.js +3 -1
  16. package/dist/webserver/backend/router/index.js.map +1 -1
  17. package/dist/webserver/backend/router/mfa.js +162 -0
  18. package/dist/webserver/backend/router/mfa.js.map +1 -0
  19. package/dist/webserver/backend/router/player.js +7 -6
  20. package/dist/webserver/backend/router/player.js.map +1 -1
  21. package/dist/webserver/backend/router/plugin.js +14 -10
  22. package/dist/webserver/backend/router/plugin.js.map +1 -1
  23. package/dist/webserver/backend/router/role.js +2 -1
  24. package/dist/webserver/backend/router/role.js.map +1 -1
  25. package/dist/webserver/backend/router/server.js +85 -71
  26. package/dist/webserver/backend/router/server.js.map +1 -1
  27. package/dist/webserver/backend/router/session.js +11 -2
  28. package/dist/webserver/backend/router/session.js.map +1 -1
  29. package/dist/webserver/backend/router/user.js +175 -13
  30. package/dist/webserver/backend/router/user.js.map +1 -1
  31. package/dist/webserver/backend/router/world.js +10 -9
  32. package/dist/webserver/backend/router/world.js.map +1 -1
  33. package/dist/webserver/backend/scopes.js +303 -0
  34. package/dist/webserver/backend/scopes.js.map +1 -0
  35. package/dist/webserver/backend/totp.js +32 -0
  36. package/dist/webserver/backend/totp.js.map +1 -0
  37. package/dist/webserver/backend/trpc.js +47 -4
  38. package/dist/webserver/backend/trpc.js.map +1 -1
  39. package/doc/permissions.md +193 -0
  40. package/package.json +7 -2
  41. package/public/app.html +932 -526
  42. package/public/assets/IconShieldCheck-Bjlm8mX2.js +2 -0
  43. package/public/assets/IconShieldCheck-Bjlm8mX2.js.map +1 -0
  44. package/public/assets/IconWorld-XmQNg6JI.js +2 -0
  45. package/public/assets/IconWorld-XmQNg6JI.js.map +1 -0
  46. package/public/assets/IconWorldPlus-CxGGv2eD.js +2 -0
  47. package/public/assets/IconWorldPlus-CxGGv2eD.js.map +1 -0
  48. package/public/assets/app-DMr_-wik.js +7 -0
  49. package/public/assets/app-DMr_-wik.js.map +1 -0
  50. package/public/assets/auth-CEYiizOt.js +2 -0
  51. package/public/assets/auth-CEYiizOt.js.map +1 -0
  52. package/public/assets/index-BDkarAkX.js +3 -0
  53. package/public/assets/index-BDkarAkX.js.map +1 -0
  54. package/public/assets/{index-2cgXLhrt.js → index-DEL-PD_8.js} +2 -2
  55. package/public/assets/{index-2cgXLhrt.js.map → index-DEL-PD_8.js.map} +1 -1
  56. package/public/assets/index-D_STNela.js +2 -0
  57. package/public/assets/index-D_STNela.js.map +1 -0
  58. package/public/assets/index-Dkh3HiBz.js +4 -0
  59. package/public/assets/index-Dkh3HiBz.js.map +1 -0
  60. package/public/assets/shared/@components-CgsSYMe6.js +2 -0
  61. package/public/assets/shared/@components-CgsSYMe6.js.map +1 -0
  62. package/public/assets/shared/@hooks-DT93N7wd.js +2 -0
  63. package/public/assets/shared/{@hooks-Ch_hJYh0.js.map → @hooks-DT93N7wd.js.map} +1 -1
  64. package/public/assets/shared/@tabler_icons-react-DrVFX3uL.js +3 -0
  65. package/public/assets/shared/{@tabler_icons-react-iQk11eD_.js.map → @tabler_icons-react-DrVFX3uL.js.map} +1 -1
  66. package/public/assets/shared/{wouter-CUC1caxu.js → wouter-CNISJ5i6.js} +2 -2
  67. package/public/assets/shared/wouter-CNISJ5i6.js.map +1 -0
  68. package/public/assets/useHasScope-BOdFktko.js +2 -0
  69. package/public/assets/useHasScope-BOdFktko.js.map +1 -0
  70. package/public/auth.html +97 -33
  71. package/public/assets/IconArrowRight-BuVxhqKX.js +0 -2
  72. package/public/assets/IconArrowRight-BuVxhqKX.js.map +0 -1
  73. package/public/assets/IconWorld-B6GK5qpx.js +0 -2
  74. package/public/assets/IconWorld-B6GK5qpx.js.map +0 -1
  75. package/public/assets/IconWorldPlus-Dkos72Ws.js +0 -2
  76. package/public/assets/IconWorldPlus-Dkos72Ws.js.map +0 -1
  77. package/public/assets/app-CLZ-5FIi.js +0 -6
  78. package/public/assets/app-CLZ-5FIi.js.map +0 -1
  79. package/public/assets/auth-6N37o421.js +0 -2
  80. package/public/assets/auth-6N37o421.js.map +0 -1
  81. package/public/assets/index-DLZ8-XzO.js +0 -4
  82. package/public/assets/index-DLZ8-XzO.js.map +0 -1
  83. package/public/assets/index-DY750sgZ.js +0 -2
  84. package/public/assets/index-DY750sgZ.js.map +0 -1
  85. package/public/assets/shared/@components-DPHARroA.js +0 -2
  86. package/public/assets/shared/@components-DPHARroA.js.map +0 -1
  87. package/public/assets/shared/@hooks-Ch_hJYh0.js +0 -2
  88. package/public/assets/shared/@tabler_icons-react-iQk11eD_.js +0 -3
  89. package/public/assets/shared/wouter-CUC1caxu.js.map +0 -1
@@ -6,6 +6,7 @@ const EventEmitter = require("events");
6
6
  const v4 = require("zod/v4");
7
7
  const events = require("../events.js");
8
8
  const metrics = require("../metrics.js");
9
+ const scopes = require("../scopes.js");
9
10
  const trpc = require("../trpc.js");
10
11
  let _server = null;
11
12
  function setWebserver(s) {
@@ -29,11 +30,13 @@ const autoRestartConfigSchema = v4.z.object({
29
30
  const serverRouter = trpc.router({
30
31
  server: trpc.router({
31
32
  autoRestart: trpc.router({
32
- get: trpc.protectedProcedure().query(async () => {
33
- const { database } = trpc.getContextDeps();
34
- return await database.getAutoRestartConfig();
35
- }),
36
- set: trpc.protectedProcedure().input(autoRestartConfigSchema).mutation(async ({ input: config }) => {
33
+ get: trpc.protectedProcedure(scopes.ScopeName.ServerAutorestartGet).query(
34
+ async () => {
35
+ const { database } = trpc.getContextDeps();
36
+ return await database.getAutoRestartConfig();
37
+ }
38
+ ),
39
+ set: trpc.protectedProcedure(scopes.ScopeName.ServerAutorestartSet).input(autoRestartConfigSchema).mutation(async ({ input: config }) => {
37
40
  const { database } = trpc.getContextDeps();
38
41
  if (typeof config !== "object" || !Object.entries({
39
42
  type: "string",
@@ -57,13 +60,13 @@ const serverRouter = trpc.router({
57
60
  return true;
58
61
  })
59
62
  }),
60
- status: trpc.protectedProcedure().query(() => {
63
+ status: trpc.protectedProcedure(scopes.ScopeName.ServerStatus).query(() => {
61
64
  return _server?.lastReportedStatus ?? null;
62
65
  }),
63
- utilization: trpc.protectedProcedure().query(() => {
66
+ utilization: trpc.protectedProcedure(scopes.ScopeName.ServerUtilization).query(() => {
64
67
  return metrics.getLastUtilization();
65
68
  }),
66
- started: trpc.protectedProcedure().query(() => {
69
+ started: trpc.protectedProcedure(scopes.ScopeName.ServerStatus).query(() => {
67
70
  const { omegga } = trpc.getContextDeps();
68
71
  return {
69
72
  started: omegga.started,
@@ -71,41 +74,45 @@ const serverRouter = trpc.router({
71
74
  stopping: omegga.stopping
72
75
  };
73
76
  }),
74
- start: trpc.protectedProcedure().mutation(async ({ ctx }) => {
75
- const { omegga } = trpc.getContextDeps();
76
- if (omegga.starting || omegga.stopping || omegga.started) return;
77
- ctx.log("Starting server...");
78
- await omegga.start();
79
- }),
80
- stop: trpc.protectedProcedure().mutation(async ({ ctx }) => {
77
+ start: trpc.protectedProcedure(scopes.ScopeName.ServerStart).mutation(
78
+ async ({ ctx }) => {
79
+ const { omegga } = trpc.getContextDeps();
80
+ if (omegga.starting || omegga.stopping || omegga.started) return;
81
+ ctx.log("Starting server...");
82
+ await omegga.start();
83
+ }
84
+ ),
85
+ stop: trpc.protectedProcedure(scopes.ScopeName.ServerStop).mutation(async ({ ctx }) => {
81
86
  const { omegga } = trpc.getContextDeps();
82
87
  if (omegga.starting || omegga.stopping || !omegga.started) return;
83
88
  ctx.log("Stopping server...");
84
89
  await omegga.stop();
85
90
  }),
86
- restart: trpc.protectedProcedure().mutation(async ({ ctx }) => {
87
- const { database, omegga } = trpc.getContextDeps();
88
- if (omegga.starting || omegga.stopping) return;
89
- try {
90
- const config = await database.getAutoRestartConfig();
91
- await omegga.saveServer({
92
- players: config.playersEnabled,
93
- saveWorld: config.saveWorld ?? true,
94
- announcement: config.announcementEnabled
95
- });
96
- } catch (err) {
97
- ctx.error("Error while saving server setup", err);
91
+ restart: trpc.protectedProcedure(scopes.ScopeName.ServerRestart).mutation(
92
+ async ({ ctx }) => {
93
+ const { database, omegga } = trpc.getContextDeps();
94
+ if (omegga.starting || omegga.stopping) return;
95
+ try {
96
+ const config = await database.getAutoRestartConfig();
97
+ await omegga.saveServer({
98
+ players: config.playersEnabled,
99
+ saveWorld: config.saveWorld ?? true,
100
+ announcement: config.announcementEnabled
101
+ });
102
+ } catch (err) {
103
+ ctx.error("Error while saving server setup", err);
104
+ }
105
+ database.addChatLog("server", {}, "Restarting in 5 seconds...");
106
+ logger.default.logp("Restarting in 5 seconds...");
107
+ omegga.broadcast(
108
+ `<size="20">Server restart in <b><color="ffffbb">${5} seconds</></></>`
109
+ );
110
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
111
+ await omegga.restartServer();
98
112
  }
99
- database.addChatLog("server", {}, "Restarting in 5 seconds...");
100
- logger.default.logp("Restarting in 5 seconds...");
101
- omegga.broadcast(
102
- `<size="20">Server restart in <b><color="ffffbb">${5} seconds</></></>`
103
- );
104
- await new Promise((resolve) => setTimeout(resolve, 5e3));
105
- await omegga.restartServer();
106
- }),
113
+ ),
107
114
  update: trpc.router({
108
- check: trpc.protectedProcedure().query(async () => {
115
+ check: trpc.protectedProcedure(scopes.ScopeName.ServerUpdateCheck).query(async () => {
109
116
  const { omegga } = trpc.getContextDeps();
110
117
  if (!omegga.config.__STEAM) return null;
111
118
  const lastUpdate = steam.getLastSteamUpdateCheck();
@@ -116,37 +123,42 @@ const serverRouter = trpc.router({
116
123
  logger.default.verbose("[rpc] Checking for steam update");
117
124
  return await steam.hasSteamUpdate(omegga.config.server?.steambeta);
118
125
  }),
119
- run: trpc.protectedProcedure().mutation(async ({ ctx }) => {
120
- const { omegga } = trpc.getContextDeps();
121
- if (!omegga.config.__STEAM) return false;
122
- if (omegga.stopping || omegga.starting) return false;
123
- const wasStarted = omegga.started;
124
- if (wasStarted) {
125
- ctx.log("Stopping server to update...");
126
- await omegga.stop();
127
- }
128
- ctx.log("Updating server...");
129
- let ok = false;
130
- try {
131
- steam.steamcmdDownloadGame({
132
- steambeta: omegga.config.server?.steambeta,
133
- steambetaPassword: omegga.config.server?.steambetaPassword
134
- });
135
- ok = true;
136
- ctx.log("Server updated successfully");
137
- } catch (err) {
138
- ctx.error("Error updating server", err);
139
- }
140
- if (wasStarted) {
141
- ctx.log("Starting server...");
142
- await omegga.start();
126
+ run: trpc.protectedProcedure(scopes.ScopeName.ServerUpdateRun).mutation(
127
+ async ({ ctx }) => {
128
+ const { omegga } = trpc.getContextDeps();
129
+ if (!omegga.config.__STEAM) return false;
130
+ if (omegga.stopping || omegga.starting) return false;
131
+ const wasStarted = omegga.started;
132
+ if (wasStarted) {
133
+ ctx.log("Stopping server to update...");
134
+ await omegga.stop();
135
+ }
136
+ ctx.log("Updating server...");
137
+ let ok = false;
138
+ try {
139
+ steam.steamcmdDownloadGame({
140
+ steambeta: omegga.config.server?.steambeta,
141
+ steambetaPassword: omegga.config.server?.steambetaPassword
142
+ });
143
+ ok = true;
144
+ ctx.log("Server updated successfully");
145
+ } catch (err) {
146
+ ctx.error("Error updating server", err);
147
+ }
148
+ if (wasStarted) {
149
+ ctx.log("Starting server...");
150
+ await omegga.start();
151
+ }
152
+ return ok;
143
153
  }
144
- return ok;
145
- })
154
+ )
146
155
  }),
147
- onStatus: trpc.protectedProcedure().subscription(
148
- async function* ({ signal }) {
149
- for await (const [_] of EventEmitter.on(events.serverEvents, "serverStatus", { signal })) {
156
+ onStatus: trpc.protectedProcedure(scopes.ScopeName.ServerStatus).subscription(
157
+ async function* ({ signal, ctx }) {
158
+ const combined = AbortSignal.any([signal, ctx.userAbort.signal]);
159
+ for await (const [_] of EventEmitter.on(events.serverEvents, "serverStatus", {
160
+ signal: combined
161
+ })) {
150
162
  const { omegga } = trpc.getContextDeps();
151
163
  yield {
152
164
  started: omegga.started,
@@ -156,19 +168,21 @@ const serverRouter = trpc.router({
156
168
  }
157
169
  }
158
170
  ),
159
- onHeartbeat: trpc.protectedProcedure().subscription(
160
- async function* ({ signal }) {
171
+ onHeartbeat: trpc.protectedProcedure(scopes.ScopeName.ServerStatus).subscription(
172
+ async function* ({ signal, ctx }) {
173
+ const combined = AbortSignal.any([signal, ctx.userAbort.signal]);
161
174
  for await (const [status] of EventEmitter.on(events.serverEvents, "heartbeat", {
162
- signal
175
+ signal: combined
163
176
  })) {
164
177
  yield status;
165
178
  }
166
179
  }
167
180
  ),
168
- onUtilization: trpc.protectedProcedure().subscription(
169
- async function* ({ signal }) {
181
+ onUtilization: trpc.protectedProcedure(scopes.ScopeName.ServerUtilization).subscription(
182
+ async function* ({ signal, ctx }) {
183
+ const combined = AbortSignal.any([signal, ctx.userAbort.signal]);
170
184
  for await (const [utilization] of EventEmitter.on(events.serverEvents, "utilization", {
171
- signal
185
+ signal: combined
172
186
  })) {
173
187
  yield utilization;
174
188
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sources":["../../../../src/webserver/backend/router/server.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { steamcmdDownloadGame } from '@/updater';\nimport { getLastSteamUpdateCheck, hasSteamUpdate } from '@/updater/steam';\nimport { on } from 'events';\nimport { z } from 'zod/v4';\nimport { serverEvents } from '../events';\nimport type Webserver from '../index';\nimport { getLastUtilization } from '../metrics';\nimport { getContextDeps, protectedProcedure, router } from '../trpc';\n\nlet _server: Webserver | null = null;\nexport function setWebserver(s: Webserver) {\n _server = s;\n}\n\nconst autoRestartConfigSchema = z.object({\n type: z.literal('autoRestartConfig'),\n maxUptime: z.number(),\n maxUptimeEnabled: z.boolean(),\n emptyUptime: z.number(),\n emptyUptimeEnabled: z.boolean(),\n dailyHour: z.number(),\n dailyHourEnabled: z.boolean(),\n announcementEnabled: z.boolean(),\n playersEnabled: z.boolean(),\n saveWorld: z.boolean(),\n autoUpdateEnabled: z.boolean(),\n autoUpdateIntervalMins: z.number(),\n crashRestartEnabled: z.boolean(),\n});\n\nexport const serverRouter = router({\n server: router({\n autoRestart: router({\n get: protectedProcedure('server.autorestart.get').query(async () => {\n const { database } = getContextDeps();\n return await database.getAutoRestartConfig();\n }),\n\n set: protectedProcedure('server.autorestart.set')\n .input(autoRestartConfigSchema)\n .mutation(async ({ input: config }) => {\n const { database } = getContextDeps();\n\n if (\n typeof config !== 'object' ||\n !Object.entries({\n type: 'string',\n maxUptime: 'number',\n maxUptimeEnabled: 'boolean',\n emptyUptime: 'number',\n emptyUptimeEnabled: 'boolean',\n dailyHour: 'number',\n dailyHourEnabled: 'boolean',\n announcementEnabled: 'boolean',\n playersEnabled: 'boolean',\n saveWorld: 'boolean',\n autoUpdateEnabled: 'boolean',\n autoUpdateIntervalMins: 'number',\n crashRestartEnabled: 'boolean',\n }).every(\n ([k, v]) =>\n k in config &&\n typeof config[k as keyof typeof config] === v &&\n !Number.isNaN(config[k as keyof typeof config]),\n ) ||\n config.type !== 'autoRestartConfig'\n )\n return false;\n\n await database.setAutoRestartConfig(config);\n return true;\n }),\n }),\n\n status: protectedProcedure('server.status').query(() => {\n return _server?.lastReportedStatus ?? null;\n }),\n\n utilization: protectedProcedure('server.utilization').query(() => {\n return getLastUtilization();\n }),\n\n started: protectedProcedure('server.started').query(() => {\n const { omegga } = getContextDeps();\n return {\n started: omegga.started,\n starting: omegga.starting,\n stopping: omegga.stopping,\n };\n }),\n\n start: protectedProcedure('server.start').mutation(async ({ ctx }) => {\n const { omegga } = getContextDeps();\n if (omegga.starting || omegga.stopping || omegga.started) return;\n ctx.log('Starting server...');\n await omegga.start();\n }),\n\n stop: protectedProcedure('server.stop').mutation(async ({ ctx }) => {\n const { omegga } = getContextDeps();\n if (omegga.starting || omegga.stopping || !omegga.started) return;\n ctx.log('Stopping server...');\n await omegga.stop();\n }),\n\n restart: protectedProcedure('server.restart').mutation(async ({ ctx }) => {\n const { database, omegga } = getContextDeps();\n if (omegga.starting || omegga.stopping) return;\n\n try {\n const config = await database.getAutoRestartConfig();\n await omegga.saveServer({\n players: config.playersEnabled,\n saveWorld: config.saveWorld ?? true,\n announcement: config.announcementEnabled,\n });\n } catch (err) {\n ctx.error('Error while saving server setup', err);\n }\n\n database.addChatLog('server', {}, 'Restarting in 5 seconds...');\n Logger.logp('Restarting in 5 seconds...');\n omegga.broadcast(\n `<size=\"20\">Server restart in <b><color=\"ffffbb\">${5} seconds</></></>`,\n );\n\n await new Promise(resolve => setTimeout(resolve, 5000));\n await omegga.restartServer();\n }),\n\n update: router({\n check: protectedProcedure('server.update.check').query(async () => {\n const { omegga } = getContextDeps();\n if (!omegga.config.__STEAM) return null;\n\n const lastUpdate = getLastSteamUpdateCheck();\n if (Date.now() - lastUpdate.attempt < 1000 * 5) {\n Logger.verbose('[rpc] Using cached steam update check');\n return lastUpdate.result;\n }\n\n Logger.verbose('[rpc] Checking for steam update');\n return await hasSteamUpdate(omegga.config.server?.steambeta);\n }),\n\n run: protectedProcedure('server.update.run').mutation(async ({ ctx }) => {\n const { omegga } = getContextDeps();\n if (!omegga.config.__STEAM) return false;\n if (omegga.stopping || omegga.starting) return false;\n\n const wasStarted = omegga.started;\n if (wasStarted) {\n ctx.log('Stopping server to update...');\n await omegga.stop();\n }\n\n ctx.log('Updating server...');\n let ok = false;\n try {\n steamcmdDownloadGame({\n steambeta: omegga.config.server?.steambeta,\n steambetaPassword: omegga.config.server?.steambetaPassword,\n });\n ok = true;\n ctx.log('Server updated successfully');\n } catch (err) {\n ctx.error('Error updating server', err);\n }\n\n if (wasStarted) {\n ctx.log('Starting server...');\n await omegga.start();\n }\n\n return ok;\n }),\n }),\n\n onStatus: protectedProcedure('server.onStatus').subscription(\n async function* ({ signal }) {\n for await (const [_] of on(serverEvents, 'serverStatus', { signal })) {\n const { omegga } = getContextDeps();\n yield {\n started: omegga.started,\n starting: omegga.starting,\n stopping: omegga.stopping,\n };\n }\n },\n ),\n\n onHeartbeat: protectedProcedure('server.onHeartbeat').subscription(\n async function* ({ signal }) {\n for await (const [status] of on(serverEvents, 'heartbeat', {\n signal,\n })) {\n yield status;\n }\n },\n ),\n\n onUtilization: protectedProcedure('server.onUtilization').subscription(\n async function* ({ signal }) {\n for await (const [utilization] of on(serverEvents, 'utilization', {\n signal,\n })) {\n yield utilization as import('../types').SystemUtilization;\n }\n },\n ),\n }),\n});\n"],"names":["z","router","protectedProcedure","getContextDeps","getLastUtilization","Logger","getLastSteamUpdateCheck","hasSteamUpdate","steamcmdDownloadGame","on","serverEvents"],"mappings":";;;;;;;;;AAUA,IAAI,UAA4B;AACzB,SAAS,aAAa,GAAc;AACzC,YAAU;AACZ;AAEA,MAAM,0BAA0BA,GAAAA,EAAE,OAAO;AAAA,EACvC,MAAMA,GAAAA,EAAE,QAAQ,mBAAmB;AAAA,EACnC,WAAWA,GAAAA,EAAE,OAAA;AAAA,EACb,kBAAkBA,GAAAA,EAAE,QAAA;AAAA,EACpB,aAAaA,GAAAA,EAAE,OAAA;AAAA,EACf,oBAAoBA,GAAAA,EAAE,QAAA;AAAA,EACtB,WAAWA,GAAAA,EAAE,OAAA;AAAA,EACb,kBAAkBA,GAAAA,EAAE,QAAA;AAAA,EACpB,qBAAqBA,GAAAA,EAAE,QAAA;AAAA,EACvB,gBAAgBA,GAAAA,EAAE,QAAA;AAAA,EAClB,WAAWA,GAAAA,EAAE,QAAA;AAAA,EACb,mBAAmBA,GAAAA,EAAE,QAAA;AAAA,EACrB,wBAAwBA,GAAAA,EAAE,OAAA;AAAA,EAC1B,qBAAqBA,GAAAA,EAAE,QAAA;AACzB,CAAC;AAEM,MAAM,eAAeC,KAAAA,OAAO;AAAA,EACjC,QAAQA,KAAAA,OAAO;AAAA,IACb,aAAaA,KAAAA,OAAO;AAAA,MAClB,KAAKC,KAAAA,mBAA2C,EAAE,MAAM,YAAY;AAClE,cAAM,EAAE,SAAA,IAAaC,oBAAA;AACrB,eAAO,MAAM,SAAS,qBAAA;AAAA,MACxB,CAAC;AAAA,MAED,KAAKD,KAAAA,mBAA2C,EAC7C,MAAM,uBAAuB,EAC7B,SAAS,OAAO,EAAE,OAAO,aAAa;AACrC,cAAM,EAAE,SAAA,IAAaC,oBAAA;AAErB,YACE,OAAO,WAAW,YAClB,CAAC,OAAO,QAAQ;AAAA,UACd,MAAM;AAAA,UACN,WAAW;AAAA,UACX,kBAAkB;AAAA,UAClB,aAAa;AAAA,UACb,oBAAoB;AAAA,UACpB,WAAW;AAAA,UACX,kBAAkB;AAAA,UAClB,qBAAqB;AAAA,UACrB,gBAAgB;AAAA,UAChB,WAAW;AAAA,UACX,mBAAmB;AAAA,UACnB,wBAAwB;AAAA,UACxB,qBAAqB;AAAA,QAAA,CACtB,EAAE;AAAA,UACD,CAAC,CAAC,GAAG,CAAC,MACJ,KAAK,UACL,OAAO,OAAO,CAAwB,MAAM,KAC5C,CAAC,OAAO,MAAM,OAAO,CAAwB,CAAC;AAAA,QAAA,KAElD,OAAO,SAAS;AAEhB,iBAAO;AAET,cAAM,SAAS,qBAAqB,MAAM;AAC1C,eAAO;AAAA,MACT,CAAC;AAAA,IAAA,CACJ;AAAA,IAED,QAAQD,KAAAA,mBAAkC,EAAE,MAAM,MAAM;AACtD,aAAO,SAAS,sBAAsB;AAAA,IACxC,CAAC;AAAA,IAED,aAAaA,KAAAA,mBAAuC,EAAE,MAAM,MAAM;AAChE,aAAOE,2BAAA;AAAA,IACT,CAAC;AAAA,IAED,SAASF,KAAAA,mBAAmC,EAAE,MAAM,MAAM;AACxD,YAAM,EAAE,OAAA,IAAWC,oBAAA;AACnB,aAAO;AAAA,QACL,SAAS,OAAO;AAAA,QAChB,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,MAAA;AAAA,IAErB,CAAC;AAAA,IAED,OAAOD,KAAAA,mBAAiC,EAAE,SAAS,OAAO,EAAE,UAAU;AACpE,YAAM,EAAE,OAAA,IAAWC,oBAAA;AACnB,UAAI,OAAO,YAAY,OAAO,YAAY,OAAO,QAAS;AAC1D,UAAI,IAAI,oBAAoB;AAC5B,YAAM,OAAO,MAAA;AAAA,IACf,CAAC;AAAA,IAED,MAAMD,KAAAA,mBAAgC,EAAE,SAAS,OAAO,EAAE,UAAU;AAClE,YAAM,EAAE,OAAA,IAAWC,oBAAA;AACnB,UAAI,OAAO,YAAY,OAAO,YAAY,CAAC,OAAO,QAAS;AAC3D,UAAI,IAAI,oBAAoB;AAC5B,YAAM,OAAO,KAAA;AAAA,IACf,CAAC;AAAA,IAED,SAASD,KAAAA,mBAAmC,EAAE,SAAS,OAAO,EAAE,UAAU;AACxE,YAAM,EAAE,UAAU,OAAA,IAAWC,oBAAA;AAC7B,UAAI,OAAO,YAAY,OAAO,SAAU;AAExC,UAAI;AACF,cAAM,SAAS,MAAM,SAAS,qBAAA;AAC9B,cAAM,OAAO,WAAW;AAAA,UACtB,SAAS,OAAO;AAAA,UAChB,WAAW,OAAO,aAAa;AAAA,UAC/B,cAAc,OAAO;AAAA,QAAA,CACtB;AAAA,MACH,SAAS,KAAK;AACZ,YAAI,MAAM,mCAAmC,GAAG;AAAA,MAClD;AAEA,eAAS,WAAW,UAAU,CAAA,GAAI,4BAA4B;AAC9DE,aAAAA,QAAO,KAAK,4BAA4B;AACxC,aAAO;AAAA,QACL,mDAAmD,CAAC;AAAA,MAAA;AAGtD,YAAM,IAAI,QAAQ,CAAA,YAAW,WAAW,SAAS,GAAI,CAAC;AACtD,YAAM,OAAO,cAAA;AAAA,IACf,CAAC;AAAA,IAED,QAAQJ,KAAAA,OAAO;AAAA,MACb,OAAOC,KAAAA,mBAAwC,EAAE,MAAM,YAAY;AACjE,cAAM,EAAE,OAAA,IAAWC,oBAAA;AACnB,YAAI,CAAC,OAAO,OAAO,QAAS,QAAO;AAEnC,cAAM,aAAaG,MAAAA,wBAAA;AACnB,YAAI,KAAK,IAAA,IAAQ,WAAW,UAAU,MAAO,GAAG;AAC9CD,iBAAAA,QAAO,QAAQ,uCAAuC;AACtD,iBAAO,WAAW;AAAA,QACpB;AAEAA,eAAAA,QAAO,QAAQ,iCAAiC;AAChD,eAAO,MAAME,MAAAA,eAAe,OAAO,OAAO,QAAQ,SAAS;AAAA,MAC7D,CAAC;AAAA,MAED,KAAKL,KAAAA,mBAAsC,EAAE,SAAS,OAAO,EAAE,UAAU;AACvE,cAAM,EAAE,OAAA,IAAWC,oBAAA;AACnB,YAAI,CAAC,OAAO,OAAO,QAAS,QAAO;AACnC,YAAI,OAAO,YAAY,OAAO,SAAU,QAAO;AAE/C,cAAM,aAAa,OAAO;AAC1B,YAAI,YAAY;AACd,cAAI,IAAI,8BAA8B;AACtC,gBAAM,OAAO,KAAA;AAAA,QACf;AAEA,YAAI,IAAI,oBAAoB;AAC5B,YAAI,KAAK;AACT,YAAI;AACFK,qCAAqB;AAAA,YACnB,WAAW,OAAO,OAAO,QAAQ;AAAA,YACjC,mBAAmB,OAAO,OAAO,QAAQ;AAAA,UAAA,CAC1C;AACD,eAAK;AACL,cAAI,IAAI,6BAA6B;AAAA,QACvC,SAAS,KAAK;AACZ,cAAI,MAAM,yBAAyB,GAAG;AAAA,QACxC;AAEA,YAAI,YAAY;AACd,cAAI,IAAI,oBAAoB;AAC5B,gBAAM,OAAO,MAAA;AAAA,QACf;AAEA,eAAO;AAAA,MACT,CAAC;AAAA,IAAA,CACF;AAAA,IAED,UAAUN,KAAAA,mBAAoC,EAAE;AAAA,MAC9C,iBAAiB,EAAE,OAAA,GAAU;AAC3B,yBAAiB,CAAC,CAAC,KAAKO,aAAAA,GAAGC,OAAAA,cAAc,gBAAgB,EAAE,OAAA,CAAQ,GAAG;AACpE,gBAAM,EAAE,OAAA,IAAWP,oBAAA;AACnB,gBAAM;AAAA,YACJ,SAAS,OAAO;AAAA,YAChB,UAAU,OAAO;AAAA,YACjB,UAAU,OAAO;AAAA,UAAA;AAAA,QAErB;AAAA,MACF;AAAA,IAAA;AAAA,IAGF,aAAaD,KAAAA,mBAAuC,EAAE;AAAA,MACpD,iBAAiB,EAAE,OAAA,GAAU;AAC3B,yBAAiB,CAAC,MAAM,KAAKO,aAAAA,GAAGC,OAAAA,cAAc,aAAa;AAAA,UACzD;AAAA,QAAA,CACD,GAAG;AACF,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAAA,IAGF,eAAeR,KAAAA,mBAAyC,EAAE;AAAA,MACxD,iBAAiB,EAAE,OAAA,GAAU;AAC3B,yBAAiB,CAAC,WAAW,KAAKO,aAAAA,GAAGC,OAAAA,cAAc,eAAe;AAAA,UAChE;AAAA,QAAA,CACD,GAAG;AACF,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAAA,EACF,CACD;AACH,CAAC;;;"}
1
+ {"version":3,"file":"server.js","sources":["../../../../src/webserver/backend/router/server.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { steamcmdDownloadGame } from '@/updater';\nimport { getLastSteamUpdateCheck, hasSteamUpdate } from '@/updater/steam';\nimport { on } from 'events';\nimport { z } from 'zod/v4';\nimport { serverEvents } from '../events';\nimport type Webserver from '../index';\nimport { getLastUtilization } from '../metrics';\nimport { ScopeName } from '../scopes';\nimport { getContextDeps, protectedProcedure, router } from '../trpc';\n\nlet _server: Webserver | null = null;\nexport function setWebserver(s: Webserver) {\n _server = s;\n}\n\nconst autoRestartConfigSchema = z.object({\n type: z.literal('autoRestartConfig'),\n maxUptime: z.number(),\n maxUptimeEnabled: z.boolean(),\n emptyUptime: z.number(),\n emptyUptimeEnabled: z.boolean(),\n dailyHour: z.number(),\n dailyHourEnabled: z.boolean(),\n announcementEnabled: z.boolean(),\n playersEnabled: z.boolean(),\n saveWorld: z.boolean(),\n autoUpdateEnabled: z.boolean(),\n autoUpdateIntervalMins: z.number(),\n crashRestartEnabled: z.boolean(),\n});\n\nexport const serverRouter = router({\n server: router({\n autoRestart: router({\n get: protectedProcedure(ScopeName.ServerAutorestartGet).query(\n async () => {\n const { database } = getContextDeps();\n return await database.getAutoRestartConfig();\n },\n ),\n\n set: protectedProcedure(ScopeName.ServerAutorestartSet)\n .input(autoRestartConfigSchema)\n .mutation(async ({ input: config }) => {\n const { database } = getContextDeps();\n\n if (\n typeof config !== 'object' ||\n !Object.entries({\n type: 'string',\n maxUptime: 'number',\n maxUptimeEnabled: 'boolean',\n emptyUptime: 'number',\n emptyUptimeEnabled: 'boolean',\n dailyHour: 'number',\n dailyHourEnabled: 'boolean',\n announcementEnabled: 'boolean',\n playersEnabled: 'boolean',\n saveWorld: 'boolean',\n autoUpdateEnabled: 'boolean',\n autoUpdateIntervalMins: 'number',\n crashRestartEnabled: 'boolean',\n }).every(\n ([k, v]) =>\n k in config &&\n typeof config[k as keyof typeof config] === v &&\n !Number.isNaN(config[k as keyof typeof config]),\n ) ||\n config.type !== 'autoRestartConfig'\n )\n return false;\n\n await database.setAutoRestartConfig(config);\n return true;\n }),\n }),\n\n status: protectedProcedure(ScopeName.ServerStatus).query(() => {\n return _server?.lastReportedStatus ?? null;\n }),\n\n utilization: protectedProcedure(ScopeName.ServerUtilization).query(() => {\n return getLastUtilization();\n }),\n\n started: protectedProcedure(ScopeName.ServerStatus).query(() => {\n const { omegga } = getContextDeps();\n return {\n started: omegga.started,\n starting: omegga.starting,\n stopping: omegga.stopping,\n };\n }),\n\n start: protectedProcedure(ScopeName.ServerStart).mutation(\n async ({ ctx }) => {\n const { omegga } = getContextDeps();\n if (omegga.starting || omegga.stopping || omegga.started) return;\n ctx.log('Starting server...');\n await omegga.start();\n },\n ),\n\n stop: protectedProcedure(ScopeName.ServerStop).mutation(async ({ ctx }) => {\n const { omegga } = getContextDeps();\n if (omegga.starting || omegga.stopping || !omegga.started) return;\n ctx.log('Stopping server...');\n await omegga.stop();\n }),\n\n restart: protectedProcedure(ScopeName.ServerRestart).mutation(\n async ({ ctx }) => {\n const { database, omegga } = getContextDeps();\n if (omegga.starting || omegga.stopping) return;\n\n try {\n const config = await database.getAutoRestartConfig();\n await omegga.saveServer({\n players: config.playersEnabled,\n saveWorld: config.saveWorld ?? true,\n announcement: config.announcementEnabled,\n });\n } catch (err) {\n ctx.error('Error while saving server setup', err);\n }\n\n database.addChatLog('server', {}, 'Restarting in 5 seconds...');\n Logger.logp('Restarting in 5 seconds...');\n omegga.broadcast(\n `<size=\"20\">Server restart in <b><color=\"ffffbb\">${5} seconds</></></>`,\n );\n\n await new Promise(resolve => setTimeout(resolve, 5000));\n await omegga.restartServer();\n },\n ),\n\n update: router({\n check: protectedProcedure(ScopeName.ServerUpdateCheck).query(async () => {\n const { omegga } = getContextDeps();\n if (!omegga.config.__STEAM) return null;\n\n const lastUpdate = getLastSteamUpdateCheck();\n if (Date.now() - lastUpdate.attempt < 1000 * 5) {\n Logger.verbose('[rpc] Using cached steam update check');\n return lastUpdate.result;\n }\n\n Logger.verbose('[rpc] Checking for steam update');\n return await hasSteamUpdate(omegga.config.server?.steambeta);\n }),\n\n run: protectedProcedure(ScopeName.ServerUpdateRun).mutation(\n async ({ ctx }) => {\n const { omegga } = getContextDeps();\n if (!omegga.config.__STEAM) return false;\n if (omegga.stopping || omegga.starting) return false;\n\n const wasStarted = omegga.started;\n if (wasStarted) {\n ctx.log('Stopping server to update...');\n await omegga.stop();\n }\n\n ctx.log('Updating server...');\n let ok = false;\n try {\n steamcmdDownloadGame({\n steambeta: omegga.config.server?.steambeta,\n steambetaPassword: omegga.config.server?.steambetaPassword,\n });\n ok = true;\n ctx.log('Server updated successfully');\n } catch (err) {\n ctx.error('Error updating server', err);\n }\n\n if (wasStarted) {\n ctx.log('Starting server...');\n await omegga.start();\n }\n\n return ok;\n },\n ),\n }),\n\n onStatus: protectedProcedure(ScopeName.ServerStatus).subscription(\n async function* ({ signal, ctx }) {\n const combined = AbortSignal.any([signal!, ctx.userAbort.signal]);\n for await (const [_] of on(serverEvents, 'serverStatus', {\n signal: combined,\n })) {\n const { omegga } = getContextDeps();\n yield {\n started: omegga.started,\n starting: omegga.starting,\n stopping: omegga.stopping,\n };\n }\n },\n ),\n\n onHeartbeat: protectedProcedure(ScopeName.ServerStatus).subscription(\n async function* ({ signal, ctx }) {\n const combined = AbortSignal.any([signal!, ctx.userAbort.signal]);\n for await (const [status] of on(serverEvents, 'heartbeat', {\n signal: combined,\n })) {\n yield status;\n }\n },\n ),\n\n onUtilization: protectedProcedure(ScopeName.ServerUtilization).subscription(\n async function* ({ signal, ctx }) {\n const combined = AbortSignal.any([signal!, ctx.userAbort.signal]);\n for await (const [utilization] of on(serverEvents, 'utilization', {\n signal: combined,\n })) {\n yield utilization as import('../types').SystemUtilization;\n }\n },\n ),\n }),\n});\n"],"names":["z","router","protectedProcedure","ScopeName","getContextDeps","getLastUtilization","Logger","getLastSteamUpdateCheck","hasSteamUpdate","steamcmdDownloadGame","on","serverEvents"],"mappings":"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"}
@@ -2,12 +2,16 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const steam = require("../../../updater/steam.js");
4
4
  const version = require("../../../version.js");
5
+ const permissions = require("../permissions.js");
6
+ const scopes = require("../scopes.js");
5
7
  const trpc = require("../trpc.js");
6
8
  const sessionRouter = trpc.router({
7
9
  session: trpc.router({
8
- info: trpc.protectedProcedure().query(async ({ ctx }) => {
10
+ info: trpc.protectedProcedure(scopes.ScopeName.SessionInfo).query(async ({ ctx }) => {
9
11
  const { database, omegga } = trpc.getContextDeps();
10
12
  const roles = await database.getRoles();
13
+ const defaults = await database.getDefaultPermissions();
14
+ const userPerms = ctx.user.permissions ?? permissions.EMPTY_PERMISSIONS;
11
15
  return {
12
16
  roles,
13
17
  version: version.VERSION,
@@ -18,7 +22,12 @@ const sessionRouter = trpc.router({
18
22
  user: {
19
23
  username: ctx.user.username || "Admin",
20
24
  isOwner: ctx.user.isOwner,
21
- roles: ctx.user.roles
25
+ roles: ctx.user.roles,
26
+ permissions: userPerms,
27
+ resolvedScopes: ctx.user.isOwner ? permissions.resolveAllScopes(
28
+ { root: permissions.RootLevel.All, domains: {}, scopes: {} },
29
+ null
30
+ ) : permissions.resolveAllScopes(userPerms, defaults)
22
31
  },
23
32
  isSteam: Boolean(omegga.config.__STEAM),
24
33
  update: {
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sources":["../../../../src/webserver/backend/router/session.ts"],"sourcesContent":["import { getLastSteamUpdateCheck } from '@/updater/steam';\nimport { VERSION } from '@/version';\nimport { router, protectedProcedure, getContextDeps } from '../trpc';\n\nexport const sessionRouter = router({\n session: router({\n info: protectedProcedure('session.info').query(async ({ ctx }) => {\n const { database, omegga } = getContextDeps();\n const roles = await database.getRoles();\n\n return {\n roles,\n version: VERSION,\n brickadiaVersion: omegga.version ?? null,\n canLogOut: ctx.user.username !== '',\n now: Date.now(),\n userless: !ctx.user.username,\n user: {\n username: ctx.user.username || 'Admin',\n isOwner: ctx.user.isOwner,\n roles: ctx.user.roles,\n },\n isSteam: Boolean(omegga.config.__STEAM),\n update: {\n canCheck: Boolean(omegga.config.__STEAM),\n lastCheck: getLastSteamUpdateCheck()?.result ?? null,\n },\n };\n }),\n }),\n});\n"],"names":["router","protectedProcedure","getContextDeps","VERSION","getLastSteamUpdateCheck"],"mappings":";;;;;AAIO,MAAM,gBAAgBA,KAAAA,OAAO;AAAA,EAClC,SAASA,KAAAA,OAAO;AAAA,IACd,MAAMC,KAAAA,mBAAiC,EAAE,MAAM,OAAO,EAAE,UAAU;AAChE,YAAM,EAAE,UAAU,OAAA,IAAWC,oBAAA;AAC7B,YAAM,QAAQ,MAAM,SAAS,SAAA;AAE7B,aAAO;AAAA,QACL;AAAA,QACA,SAASC,QAAAA;AAAAA,QACT,kBAAkB,OAAO,WAAW;AAAA,QACpC,WAAW,IAAI,KAAK,aAAa;AAAA,QACjC,KAAK,KAAK,IAAA;AAAA,QACV,UAAU,CAAC,IAAI,KAAK;AAAA,QACpB,MAAM;AAAA,UACJ,UAAU,IAAI,KAAK,YAAY;AAAA,UAC/B,SAAS,IAAI,KAAK;AAAA,UAClB,OAAO,IAAI,KAAK;AAAA,QAAA;AAAA,QAElB,SAAS,QAAQ,OAAO,OAAO,OAAO;AAAA,QACtC,QAAQ;AAAA,UACN,UAAU,QAAQ,OAAO,OAAO,OAAO;AAAA,UACvC,WAAWC,MAAAA,wBAAA,GAA2B,UAAU;AAAA,QAAA;AAAA,MAClD;AAAA,IAEJ,CAAC;AAAA,EAAA,CACF;AACH,CAAC;;"}
1
+ {"version":3,"file":"session.js","sources":["../../../../src/webserver/backend/router/session.ts"],"sourcesContent":["import { getLastSteamUpdateCheck } from '@/updater/steam';\nimport { VERSION } from '@/version';\nimport { EMPTY_PERMISSIONS, RootLevel, resolveAllScopes } from '../permissions';\nimport { ScopeName } from '../scopes';\nimport { router, protectedProcedure, getContextDeps } from '../trpc';\n\nexport const sessionRouter = router({\n session: router({\n info: protectedProcedure(ScopeName.SessionInfo).query(async ({ ctx }) => {\n const { database, omegga } = getContextDeps();\n const roles = await database.getRoles();\n const defaults = await database.getDefaultPermissions();\n const userPerms = ctx.user.permissions ?? EMPTY_PERMISSIONS;\n\n return {\n roles,\n version: VERSION,\n brickadiaVersion: omegga.version ?? null,\n canLogOut: ctx.user.username !== '',\n now: Date.now(),\n userless: !ctx.user.username,\n user: {\n username: ctx.user.username || 'Admin',\n isOwner: ctx.user.isOwner,\n roles: ctx.user.roles,\n permissions: userPerms,\n resolvedScopes: ctx.user.isOwner\n ? resolveAllScopes(\n { root: RootLevel.All, domains: {}, scopes: {} },\n null,\n )\n : resolveAllScopes(userPerms, defaults),\n },\n isSteam: Boolean(omegga.config.__STEAM),\n update: {\n canCheck: Boolean(omegga.config.__STEAM),\n lastCheck: getLastSteamUpdateCheck()?.result ?? null,\n },\n };\n }),\n }),\n});\n"],"names":["router","protectedProcedure","ScopeName","getContextDeps","EMPTY_PERMISSIONS","VERSION","resolveAllScopes","RootLevel","getLastSteamUpdateCheck"],"mappings":";;;;;;;AAMO,MAAM,gBAAgBA,KAAAA,OAAO;AAAA,EAClC,SAASA,KAAAA,OAAO;AAAA,IACd,MAAMC,KAAAA,mBAAmBC,iBAAU,WAAW,EAAE,MAAM,OAAO,EAAE,UAAU;AACvE,YAAM,EAAE,UAAU,OAAA,IAAWC,oBAAA;AAC7B,YAAM,QAAQ,MAAM,SAAS,SAAA;AAC7B,YAAM,WAAW,MAAM,SAAS,sBAAA;AAChC,YAAM,YAAY,IAAI,KAAK,eAAeC,YAAAA;AAE1C,aAAO;AAAA,QACL;AAAA,QACA,SAASC,QAAAA;AAAAA,QACT,kBAAkB,OAAO,WAAW;AAAA,QACpC,WAAW,IAAI,KAAK,aAAa;AAAA,QACjC,KAAK,KAAK,IAAA;AAAA,QACV,UAAU,CAAC,IAAI,KAAK;AAAA,QACpB,MAAM;AAAA,UACJ,UAAU,IAAI,KAAK,YAAY;AAAA,UAC/B,SAAS,IAAI,KAAK;AAAA,UAClB,OAAO,IAAI,KAAK;AAAA,UAChB,aAAa;AAAA,UACb,gBAAgB,IAAI,KAAK,UACrBC,YAAAA;AAAAA,YACE,EAAE,MAAMC,YAAAA,UAAU,KAAK,SAAS,CAAA,GAAI,QAAQ,GAAC;AAAA,YAC7C;AAAA,UAAA,IAEFD,YAAAA,iBAAiB,WAAW,QAAQ;AAAA,QAAA;AAAA,QAE1C,SAAS,QAAQ,OAAO,OAAO,OAAO;AAAA,QACtC,QAAQ;AAAA,UACN,UAAU,QAAQ,OAAO,OAAO,OAAO;AAAA,UACvC,WAAWE,MAAAA,wBAAA,GAA2B,UAAU;AAAA,QAAA;AAAA,MAClD;AAAA,IAEJ,CAAC;AAAA,EAAA,CACF;AACH,CAAC;;"}
@@ -1,10 +1,39 @@
1
1
  "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
2
24
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
25
  const v4 = require("zod/v4");
26
+ const permissions = require("../permissions.js");
27
+ const scopes = require("../scopes.js");
4
28
  const trpc = require("../trpc.js");
29
+ const PermissionSetSchema = v4.z.object({
30
+ root: v4.z.enum(["all", "read", "off"]),
31
+ domains: v4.z.record(v4.z.string(), v4.z.enum(["all", "read", "none"])),
32
+ scopes: v4.z.record(v4.z.string(), v4.z.boolean())
33
+ });
5
34
  const userRouter = trpc.router({
6
35
  user: trpc.router({
7
- list: trpc.protectedProcedure().input(
36
+ list: trpc.protectedProcedure(scopes.ScopeName.UserList).input(
8
37
  v4.z.object({
9
38
  page: v4.z.number().optional().default(0),
10
39
  search: v4.z.string().optional().default(""),
@@ -16,15 +45,42 @@ const userRouter = trpc.router({
16
45
  const { page, search, sort, direction } = input;
17
46
  const resp = await database.getUsers({ page, search, sort, direction });
18
47
  const now = Date.now();
19
- resp.users = resp.users.map(({ hash: _, ...user }) => ({
20
- ...user,
21
- hash: "",
22
- seenAgo: user.lastOnline ? now - user.lastOnline : Infinity,
23
- createdAgo: now - user.created
24
- }));
48
+ resp.users = resp.users.map(
49
+ ({
50
+ hash: _h,
51
+ totpSecret: _ts,
52
+ recoveryCodes: _rc,
53
+ passkeys: _pk,
54
+ permissions: _perms,
55
+ ...user
56
+ }) => ({
57
+ ...user,
58
+ hash: "",
59
+ permissions: permissions.decodePermissions(_perms),
60
+ totpEnabled: user.totpEnabled ?? false,
61
+ passkeyCount: _pk?.length ?? 0,
62
+ seenAgo: user.lastOnline ? now - user.lastOnline : Infinity,
63
+ createdAgo: now - user.created
64
+ })
65
+ );
25
66
  return resp;
26
67
  }),
27
- create: trpc.protectedProcedure().input(
68
+ self: trpc.protectedProcedure(scopes.ScopeName.SessionInfo).query(async ({ ctx }) => {
69
+ const now = Date.now();
70
+ return {
71
+ username: ctx.user.username || "Admin",
72
+ isOwner: ctx.user.isOwner,
73
+ isBanned: ctx.user.isBanned ?? false,
74
+ lastOnline: ctx.user.lastOnline ?? 0,
75
+ created: ctx.user.created ?? 0,
76
+ seenAgo: ctx.user.lastOnline ? now - ctx.user.lastOnline : Infinity,
77
+ createdAgo: now - (ctx.user.created ?? now),
78
+ permissions: ctx.user.permissions,
79
+ totpEnabled: ctx.user.totpEnabled ?? false,
80
+ passkeyCount: ctx.user.passkeys?.length ?? 0
81
+ };
82
+ }),
83
+ create: trpc.protectedProcedure(scopes.ScopeName.UserCreate).input(
28
84
  v4.z.object({
29
85
  username: v4.z.string(),
30
86
  password: v4.z.string()
@@ -33,7 +89,6 @@ const userRouter = trpc.router({
33
89
  const { database } = trpc.getContextDeps();
34
90
  const { username, password } = input;
35
91
  const { log, error } = ctx;
36
- if (!ctx.user.isOwner) return "missing permission";
37
92
  if (typeof username !== "string" || typeof password !== "string")
38
93
  return "username/password not a string";
39
94
  if (!username.match(/^\w{0,32}$/)) return "username is not allowed";
@@ -62,17 +117,29 @@ const userRouter = trpc.router({
62
117
  log(`created new user "${username.yellow}"`);
63
118
  return "";
64
119
  }),
65
- passwd: trpc.protectedProcedure().input(
120
+ // self-service: any authenticated user can change their own password
121
+ // changing another user's password requires user.passwd permission (enforced here)
122
+ passwd: trpc.protectedProcedure(scopes.ScopeName.SessionInfo).input(
66
123
  v4.z.object({
67
124
  username: v4.z.string(),
68
- password: v4.z.string()
125
+ password: v4.z.string(),
126
+ currentPassword: v4.z.string().optional()
69
127
  })
70
128
  ).mutation(async ({ input, ctx }) => {
71
129
  const { database } = trpc.getContextDeps();
72
130
  const { username, password } = input;
73
131
  const { log, error } = ctx;
74
- if (!ctx.user.isOwner && username !== ctx.user.username)
75
- return "missing permission";
132
+ const isSelf = username === ctx.user.username;
133
+ if (isSelf) {
134
+ if (!input.currentPassword) return "current password required";
135
+ const bcryptLib = await import("bcryptjs");
136
+ if (!await bcryptLib.compare(input.currentPassword, ctx.user.hash))
137
+ return "incorrect current password";
138
+ } else {
139
+ const defaults = await database.getDefaultPermissions();
140
+ if (!permissions.userHasScope(ctx.user, scopes.ScopeName.UserPasswd, defaults))
141
+ return "missing permission";
142
+ }
76
143
  if (typeof username !== "string" || typeof password !== "string")
77
144
  return "username/password not a string";
78
145
  if (!username.match(/^\w{0,32}$/)) return "username is not allowed";
@@ -86,6 +153,101 @@ const userRouter = trpc.router({
86
153
  }
87
154
  log(`changed password for "${username.yellow}"`);
88
155
  return "";
156
+ }),
157
+ ban: trpc.protectedProcedure(scopes.ScopeName.UserBan).input(
158
+ v4.z.object({
159
+ username: v4.z.string(),
160
+ banned: v4.z.boolean()
161
+ })
162
+ ).mutation(async ({ input, ctx }) => {
163
+ const { database } = trpc.getContextDeps();
164
+ const { username, banned } = input;
165
+ const { log } = ctx;
166
+ if (username === ctx.user.username) return "cannot disable yourself";
167
+ if (!await database.userExists(username))
168
+ return "user does not exist";
169
+ const target = await database.stores.users.findOne({ type: "user", username });
170
+ if (target?.isOwner) return "cannot disable the owner";
171
+ await database.banUser(username, banned);
172
+ log(`${banned ? "disabled" : "enabled"} user "${username.yellow}"`);
173
+ return "";
174
+ }),
175
+ delete: trpc.protectedProcedure(scopes.ScopeName.UserDelete).input(
176
+ v4.z.object({
177
+ username: v4.z.string()
178
+ })
179
+ ).mutation(async ({ input, ctx }) => {
180
+ const { database } = trpc.getContextDeps();
181
+ const { username } = input;
182
+ const { log } = ctx;
183
+ if (username === ctx.user.username) return "cannot delete yourself";
184
+ if (!await database.userExists(username))
185
+ return "user does not exist";
186
+ const target = await database.stores.users.findOne({ type: "user", username });
187
+ if (target?.isOwner) return "cannot delete the owner";
188
+ await database.deleteUser(username);
189
+ log(`deleted user "${username.yellow}"`);
190
+ return "";
191
+ }),
192
+ permissions: trpc.protectedProcedure(scopes.ScopeName.UserPermissions).input(
193
+ v4.z.object({
194
+ username: v4.z.string(),
195
+ permissions: PermissionSetSchema
196
+ })
197
+ ).mutation(async ({ input, ctx }) => {
198
+ const { database } = trpc.getContextDeps();
199
+ const { username } = input;
200
+ const permissions$1 = input.permissions;
201
+ const { log } = ctx;
202
+ if (username === ctx.user.username)
203
+ return "cannot edit own permissions";
204
+ const target = await database.stores.users.findOne({ type: "user", username });
205
+ if (!target) return "user does not exist";
206
+ if (target.isOwner) return "cannot edit the owner";
207
+ if (!ctx.user.isOwner) {
208
+ const defaults = await database.getDefaultPermissions();
209
+ const myScopes = permissions.resolveAllScopes(ctx.user.permissions, defaults);
210
+ const grantedScopes = permissions.resolveAllScopes(permissions$1, defaults);
211
+ for (const [scope, granted] of Object.entries(grantedScopes)) {
212
+ if (granted && !myScopes[scope])
213
+ return `cannot grant permission you do not have: ${scope}`;
214
+ }
215
+ }
216
+ await database.setUserPermissions(username, permissions$1);
217
+ log(`updated permissions for "${username.yellow}"`);
218
+ return "";
219
+ }),
220
+ defaultPermissions: trpc.router({
221
+ get: trpc.protectedProcedure(scopes.ScopeName.UserPermissions).query(async () => {
222
+ const { database } = trpc.getContextDeps();
223
+ const defaults = await database.getDefaultPermissions();
224
+ return {
225
+ root: defaults.root,
226
+ domains: defaults.domains,
227
+ scopes: defaults.scopes
228
+ };
229
+ }),
230
+ set: trpc.protectedProcedure(scopes.ScopeName.UserPermissions).input(PermissionSetSchema).mutation(async ({ input, ctx }) => {
231
+ const { database } = trpc.getContextDeps();
232
+ const { log } = ctx;
233
+ await database.setDefaultPermissions(
234
+ input
235
+ );
236
+ log(`updated default permissions`);
237
+ return "";
238
+ })
239
+ }),
240
+ resetMfa: trpc.protectedProcedure(scopes.ScopeName.UserResetMfa).input(v4.z.object({ username: v4.z.string() })).mutation(async ({ input, ctx }) => {
241
+ const { database } = trpc.getContextDeps();
242
+ const { username } = input;
243
+ const { log } = ctx;
244
+ if (username === ctx.user.username) return "cannot reset own MFA";
245
+ const target = await database.stores.users.findOne({ type: "user", username });
246
+ if (!target) return "user does not exist";
247
+ if (target.isOwner) return "cannot reset the owner";
248
+ await database.resetUserMfa(username);
249
+ log(`reset MFA for "${username.yellow}"`);
250
+ return "";
89
251
  })
90
252
  })
91
253
  });
@@ -1 +1 @@
1
- {"version":3,"file":"user.js","sources":["../../../../src/webserver/backend/router/user.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { router, protectedProcedure, getContextDeps } from '../trpc';\n\nexport const userRouter = router({\n user: router({\n list: protectedProcedure('user.list')\n .input(\n z\n .object({\n page: z.number().optional().default(0),\n search: z.string().optional().default(''),\n sort: z.string().optional().default('name'),\n direction: z.number().optional().default(1),\n })\n .optional()\n .default({ page: 0, search: '', sort: 'name', direction: 1 }),\n )\n .query(async ({ input }) => {\n const { database } = getContextDeps();\n const { page, search, sort, direction } = input;\n const resp = await database.getUsers({ page, search, sort, direction });\n const now = Date.now();\n resp.users = resp.users.map(({ hash: _, ...user }) => ({\n ...user,\n hash: '',\n seenAgo: user.lastOnline ? now - user.lastOnline : Infinity,\n createdAgo: now - user.created,\n }));\n return resp;\n }),\n\n create: protectedProcedure('user.create')\n .input(\n z.object({\n username: z.string(),\n password: z.string(),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username, password } = input;\n const { log, error } = ctx;\n\n if (!ctx.user.isOwner) return 'missing permission';\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n if (password.length === 0 || password.length > 128)\n return 'invalid password size';\n\n if (ctx.user.isOwner && ctx.user.username === '') {\n try {\n await database.stores.users.update(\n { _id: ctx.user._id },\n { username, hash: await database.hash(password) },\n );\n } catch (e) {\n error('error setting owner password', e);\n return 'error setting owner password';\n }\n log(`created account as \"${username.yellow}\"`);\n return '';\n }\n\n if (await database.userExists(username)) return 'user already exists';\n\n try {\n await database.createUser(username, password);\n } catch (e) {\n error('error creating new user', e);\n return 'error creating new user';\n }\n log(`created new user \"${username.yellow}\"`);\n return '';\n }),\n\n passwd: protectedProcedure('user.passwd')\n .input(\n z.object({\n username: z.string(),\n password: z.string(),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username, password } = input;\n const { log, error } = ctx;\n\n if (!ctx.user.isOwner && username !== ctx.user.username)\n return 'missing permission';\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n if (!(await database.userExists(username)))\n return 'user does not exist';\n\n try {\n await database.userPasswd(username, password);\n } catch (e) {\n error('error setting user password', e);\n return \"error setting user's password\";\n }\n log(`changed password for \"${username.yellow}\"`);\n return '';\n }),\n }),\n});\n"],"names":["router","protectedProcedure","z","getContextDeps"],"mappings":"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"}
1
+ {"version":3,"file":"user.js","sources":["../../../../src/webserver/backend/router/user.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport {\n decodePermissions,\n resolveAllScopes,\n userHasScope,\n type PermissionSet,\n} from '../permissions';\nimport { ScopeName } from '../scopes';\nimport { router, protectedProcedure, getContextDeps } from '../trpc';\nimport type { IStoreUser } from '../types';\n\nconst PermissionSetSchema = z.object({\n root: z.enum(['all', 'read', 'off']),\n domains: z.record(z.string(), z.enum(['all', 'read', 'none'])),\n scopes: z.record(z.string(), z.boolean()),\n});\n\nexport const userRouter = router({\n user: router({\n list: protectedProcedure(ScopeName.UserList)\n .input(\n z\n .object({\n page: z.number().optional().default(0),\n search: z.string().optional().default(''),\n sort: z.string().optional().default('name'),\n direction: z.number().optional().default(1),\n })\n .optional()\n .default({ page: 0, search: '', sort: 'name', direction: 1 }),\n )\n .query(async ({ input }) => {\n const { database } = getContextDeps();\n const { page, search, sort, direction } = input;\n const resp = await database.getUsers({ page, search, sort, direction });\n const now = Date.now();\n resp.users = resp.users.map(\n ({\n hash: _h,\n totpSecret: _ts,\n recoveryCodes: _rc,\n passkeys: _pk,\n permissions: _perms,\n ...user\n }) => ({\n ...user,\n hash: '',\n permissions: decodePermissions(_perms as any),\n totpEnabled: user.totpEnabled ?? false,\n passkeyCount: _pk?.length ?? 0,\n seenAgo: user.lastOnline ? now - user.lastOnline : Infinity,\n createdAgo: now - user.created,\n }),\n );\n return resp;\n }),\n\n self: protectedProcedure(ScopeName.SessionInfo).query(async ({ ctx }) => {\n const now = Date.now();\n return {\n username: ctx.user.username || 'Admin',\n isOwner: ctx.user.isOwner,\n isBanned: ctx.user.isBanned ?? false,\n lastOnline: ctx.user.lastOnline ?? 0,\n created: ctx.user.created ?? 0,\n seenAgo: ctx.user.lastOnline ? now - ctx.user.lastOnline : Infinity,\n createdAgo: now - (ctx.user.created ?? now),\n permissions: ctx.user.permissions,\n totpEnabled: ctx.user.totpEnabled ?? false,\n passkeyCount: ctx.user.passkeys?.length ?? 0,\n };\n }),\n\n create: protectedProcedure(ScopeName.UserCreate)\n .input(\n z.object({\n username: z.string(),\n password: z.string(),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username, password } = input;\n const { log, error } = ctx;\n\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n if (password.length === 0 || password.length > 128)\n return 'invalid password size';\n\n if (ctx.user.isOwner && ctx.user.username === '') {\n try {\n await database.stores.users.update(\n { _id: ctx.user._id },\n { username, hash: await database.hash(password) },\n );\n } catch (e) {\n error('error setting owner password', e);\n return 'error setting owner password';\n }\n log(`created account as \"${username.yellow}\"`);\n return '';\n }\n\n if (await database.userExists(username)) return 'user already exists';\n\n try {\n await database.createUser(username, password);\n } catch (e) {\n error('error creating new user', e);\n return 'error creating new user';\n }\n log(`created new user \"${username.yellow}\"`);\n return '';\n }),\n\n // self-service: any authenticated user can change their own password\n // changing another user's password requires user.passwd permission (enforced here)\n passwd: protectedProcedure(ScopeName.SessionInfo)\n .input(\n z.object({\n username: z.string(),\n password: z.string(),\n currentPassword: z.string().optional(),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username, password } = input;\n const { log, error } = ctx;\n\n const isSelf = username === ctx.user.username;\n if (isSelf) {\n if (!input.currentPassword) return 'current password required';\n const bcryptLib = await import('bcryptjs');\n if (!(await bcryptLib.compare(input.currentPassword, ctx.user.hash)))\n return 'incorrect current password';\n } else {\n const defaults = await database.getDefaultPermissions();\n if (!userHasScope(ctx.user, ScopeName.UserPasswd, defaults))\n return 'missing permission';\n }\n\n if (typeof username !== 'string' || typeof password !== 'string')\n return 'username/password not a string';\n if (!username.match(/^\\w{0,32}$/)) return 'username is not allowed';\n if (!(await database.userExists(username)))\n return 'user does not exist';\n\n try {\n await database.userPasswd(username, password);\n } catch (e) {\n error('error setting user password', e);\n return \"error setting user's password\";\n }\n log(`changed password for \"${username.yellow}\"`);\n return '';\n }),\n\n ban: protectedProcedure(ScopeName.UserBan)\n .input(\n z.object({\n username: z.string(),\n banned: z.boolean(),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username, banned } = input;\n const { log } = ctx;\n\n if (username === ctx.user.username) return 'cannot disable yourself';\n if (!(await database.userExists(username)))\n return 'user does not exist';\n\n const target = await database.stores.users.findOne<\n IStoreUser & { _id: string }\n >({ type: 'user', username });\n if (target?.isOwner) return 'cannot disable the owner';\n\n await database.banUser(username, banned);\n log(`${banned ? 'disabled' : 'enabled'} user \"${username.yellow}\"`);\n return '';\n }),\n\n delete: protectedProcedure(ScopeName.UserDelete)\n .input(\n z.object({\n username: z.string(),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username } = input;\n const { log } = ctx;\n\n if (username === ctx.user.username) return 'cannot delete yourself';\n if (!(await database.userExists(username)))\n return 'user does not exist';\n\n const target = await database.stores.users.findOne<\n IStoreUser & { _id: string }\n >({ type: 'user', username });\n if (target?.isOwner) return 'cannot delete the owner';\n\n await database.deleteUser(username);\n log(`deleted user \"${username.yellow}\"`);\n return '';\n }),\n\n permissions: protectedProcedure(ScopeName.UserPermissions)\n .input(\n z.object({\n username: z.string(),\n permissions: PermissionSetSchema,\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username } = input;\n const permissions = input.permissions as PermissionSet;\n const { log } = ctx;\n\n if (username === ctx.user.username)\n return 'cannot edit own permissions';\n\n const target = await database.stores.users.findOne<\n IStoreUser & { _id: string }\n >({ type: 'user', username });\n if (!target) return 'user does not exist';\n if (target.isOwner) return 'cannot edit the owner';\n\n // privilege escalation check: non-owner can't grant scopes they don't have\n if (!ctx.user.isOwner) {\n const defaults = await database.getDefaultPermissions();\n const myScopes = resolveAllScopes(ctx.user.permissions!, defaults);\n const grantedScopes = resolveAllScopes(permissions, defaults);\n for (const [scope, granted] of Object.entries(grantedScopes)) {\n if (granted && !myScopes[scope as keyof typeof myScopes])\n return `cannot grant permission you do not have: ${scope}`;\n }\n }\n\n await database.setUserPermissions(username, permissions);\n log(`updated permissions for \"${username.yellow}\"`);\n return '';\n }),\n\n defaultPermissions: router({\n get: protectedProcedure(ScopeName.UserPermissions).query(async () => {\n const { database } = getContextDeps();\n const defaults = await database.getDefaultPermissions();\n return {\n root: defaults.root,\n domains: defaults.domains,\n scopes: defaults.scopes,\n };\n }),\n\n set: protectedProcedure(ScopeName.UserPermissions)\n .input(PermissionSetSchema)\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { log } = ctx;\n await database.setDefaultPermissions(\n input as unknown as PermissionSet,\n );\n log(`updated default permissions`);\n return '';\n }),\n }),\n\n resetMfa: protectedProcedure(ScopeName.UserResetMfa)\n .input(z.object({ username: z.string() }))\n .mutation(async ({ input, ctx }) => {\n const { database } = getContextDeps();\n const { username } = input;\n const { log } = ctx;\n\n if (username === ctx.user.username) return 'cannot reset own MFA';\n\n const target = await database.stores.users.findOne<\n IStoreUser & { _id: string }\n >({ type: 'user', username });\n if (!target) return 'user does not exist';\n if (target.isOwner) return 'cannot reset the owner';\n\n await database.resetUserMfa(username);\n log(`reset MFA for \"${username.yellow}\"`);\n return '';\n }),\n }),\n});\n"],"names":["z","router","protectedProcedure","ScopeName","getContextDeps","decodePermissions","userHasScope","permissions","resolveAllScopes"],"mappings":"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"}