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.
- package/CHANGELOG.md +14 -1
- package/bin/omegga +1 -1
- package/dist/cli/terminal.js +46 -0
- package/dist/cli/terminal.js.map +1 -1
- package/dist/webserver/backend/database.js +219 -7
- package/dist/webserver/backend/database.js.map +1 -1
- package/dist/webserver/backend/events.js +1 -1
- package/dist/webserver/backend/events.js.map +1 -1
- package/dist/webserver/backend/index.js +120 -2
- package/dist/webserver/backend/index.js.map +1 -1
- package/dist/webserver/backend/permissions.js +74 -0
- package/dist/webserver/backend/permissions.js.map +1 -0
- package/dist/webserver/backend/router/chat.js +9 -7
- package/dist/webserver/backend/router/chat.js.map +1 -1
- package/dist/webserver/backend/router/index.js +3 -1
- package/dist/webserver/backend/router/index.js.map +1 -1
- package/dist/webserver/backend/router/mfa.js +162 -0
- package/dist/webserver/backend/router/mfa.js.map +1 -0
- package/dist/webserver/backend/router/player.js +7 -6
- package/dist/webserver/backend/router/player.js.map +1 -1
- package/dist/webserver/backend/router/plugin.js +14 -10
- package/dist/webserver/backend/router/plugin.js.map +1 -1
- package/dist/webserver/backend/router/role.js +2 -1
- package/dist/webserver/backend/router/role.js.map +1 -1
- package/dist/webserver/backend/router/server.js +85 -71
- package/dist/webserver/backend/router/server.js.map +1 -1
- package/dist/webserver/backend/router/session.js +11 -2
- package/dist/webserver/backend/router/session.js.map +1 -1
- package/dist/webserver/backend/router/user.js +175 -13
- package/dist/webserver/backend/router/user.js.map +1 -1
- package/dist/webserver/backend/router/world.js +10 -9
- package/dist/webserver/backend/router/world.js.map +1 -1
- package/dist/webserver/backend/scopes.js +303 -0
- package/dist/webserver/backend/scopes.js.map +1 -0
- package/dist/webserver/backend/totp.js +32 -0
- package/dist/webserver/backend/totp.js.map +1 -0
- package/dist/webserver/backend/trpc.js +47 -4
- package/dist/webserver/backend/trpc.js.map +1 -1
- package/doc/permissions.md +193 -0
- package/package.json +7 -2
- package/public/app.html +932 -526
- package/public/assets/IconShieldCheck-Bjlm8mX2.js +2 -0
- package/public/assets/IconShieldCheck-Bjlm8mX2.js.map +1 -0
- package/public/assets/IconWorld-XmQNg6JI.js +2 -0
- package/public/assets/IconWorld-XmQNg6JI.js.map +1 -0
- package/public/assets/IconWorldPlus-CxGGv2eD.js +2 -0
- package/public/assets/IconWorldPlus-CxGGv2eD.js.map +1 -0
- package/public/assets/app-DMr_-wik.js +7 -0
- package/public/assets/app-DMr_-wik.js.map +1 -0
- package/public/assets/auth-CEYiizOt.js +2 -0
- package/public/assets/auth-CEYiizOt.js.map +1 -0
- package/public/assets/index-BDkarAkX.js +3 -0
- package/public/assets/index-BDkarAkX.js.map +1 -0
- package/public/assets/{index-2cgXLhrt.js → index-DEL-PD_8.js} +2 -2
- package/public/assets/{index-2cgXLhrt.js.map → index-DEL-PD_8.js.map} +1 -1
- package/public/assets/index-D_STNela.js +2 -0
- package/public/assets/index-D_STNela.js.map +1 -0
- package/public/assets/index-Dkh3HiBz.js +4 -0
- package/public/assets/index-Dkh3HiBz.js.map +1 -0
- package/public/assets/shared/@components-CgsSYMe6.js +2 -0
- package/public/assets/shared/@components-CgsSYMe6.js.map +1 -0
- package/public/assets/shared/@hooks-DT93N7wd.js +2 -0
- package/public/assets/shared/{@hooks-Ch_hJYh0.js.map → @hooks-DT93N7wd.js.map} +1 -1
- package/public/assets/shared/@tabler_icons-react-DrVFX3uL.js +3 -0
- package/public/assets/shared/{@tabler_icons-react-iQk11eD_.js.map → @tabler_icons-react-DrVFX3uL.js.map} +1 -1
- package/public/assets/shared/{wouter-CUC1caxu.js → wouter-CNISJ5i6.js} +2 -2
- package/public/assets/shared/wouter-CNISJ5i6.js.map +1 -0
- package/public/assets/useHasScope-BOdFktko.js +2 -0
- package/public/assets/useHasScope-BOdFktko.js.map +1 -0
- package/public/auth.html +97 -33
- package/public/assets/IconArrowRight-BuVxhqKX.js +0 -2
- package/public/assets/IconArrowRight-BuVxhqKX.js.map +0 -1
- package/public/assets/IconWorld-B6GK5qpx.js +0 -2
- package/public/assets/IconWorld-B6GK5qpx.js.map +0 -1
- package/public/assets/IconWorldPlus-Dkos72Ws.js +0 -2
- package/public/assets/IconWorldPlus-Dkos72Ws.js.map +0 -1
- package/public/assets/app-CLZ-5FIi.js +0 -6
- package/public/assets/app-CLZ-5FIi.js.map +0 -1
- package/public/assets/auth-6N37o421.js +0 -2
- package/public/assets/auth-6N37o421.js.map +0 -1
- package/public/assets/index-DLZ8-XzO.js +0 -4
- package/public/assets/index-DLZ8-XzO.js.map +0 -1
- package/public/assets/index-DY750sgZ.js +0 -2
- package/public/assets/index-DY750sgZ.js.map +0 -1
- package/public/assets/shared/@components-DPHARroA.js +0 -2
- package/public/assets/shared/@components-DPHARroA.js.map +0 -1
- package/public/assets/shared/@hooks-Ch_hJYh0.js +0 -2
- package/public/assets/shared/@tabler_icons-react-iQk11eD_.js +0 -3
- 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(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
})
|
|
154
|
+
)
|
|
146
155
|
}),
|
|
147
|
-
onStatus: trpc.protectedProcedure().subscription(
|
|
148
|
-
async function* ({ signal }) {
|
|
149
|
-
|
|
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":"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"}
|
|
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":";;;;;;;;;;AAWA,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,mBAAmBC,iBAAU,oBAAoB,EAAE;AAAA,QACtD,YAAY;AACV,gBAAM,EAAE,SAAA,IAAaC,oBAAA;AACrB,iBAAO,MAAM,SAAS,qBAAA;AAAA,QACxB;AAAA,MAAA;AAAA,MAGF,KAAKF,KAAAA,mBAAmBC,iBAAU,oBAAoB,EACnD,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,QAAQF,KAAAA,mBAAmBC,OAAAA,UAAU,YAAY,EAAE,MAAM,MAAM;AAC7D,aAAO,SAAS,sBAAsB;AAAA,IACxC,CAAC;AAAA,IAED,aAAaD,KAAAA,mBAAmBC,OAAAA,UAAU,iBAAiB,EAAE,MAAM,MAAM;AACvE,aAAOE,2BAAA;AAAA,IACT,CAAC;AAAA,IAED,SAASH,KAAAA,mBAAmBC,OAAAA,UAAU,YAAY,EAAE,MAAM,MAAM;AAC9D,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,OAAOF,KAAAA,mBAAmBC,iBAAU,WAAW,EAAE;AAAA,MAC/C,OAAO,EAAE,IAAA,MAAU;AACjB,cAAM,EAAE,OAAA,IAAWC,oBAAA;AACnB,YAAI,OAAO,YAAY,OAAO,YAAY,OAAO,QAAS;AAC1D,YAAI,IAAI,oBAAoB;AAC5B,cAAM,OAAO,MAAA;AAAA,MACf;AAAA,IAAA;AAAA,IAGF,MAAMF,KAAAA,mBAAmBC,iBAAU,UAAU,EAAE,SAAS,OAAO,EAAE,UAAU;AACzE,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,SAASF,KAAAA,mBAAmBC,iBAAU,aAAa,EAAE;AAAA,MACnD,OAAO,EAAE,IAAA,MAAU;AACjB,cAAM,EAAE,UAAU,OAAA,IAAWC,oBAAA;AAC7B,YAAI,OAAO,YAAY,OAAO,SAAU;AAExC,YAAI;AACF,gBAAM,SAAS,MAAM,SAAS,qBAAA;AAC9B,gBAAM,OAAO,WAAW;AAAA,YACtB,SAAS,OAAO;AAAA,YAChB,WAAW,OAAO,aAAa;AAAA,YAC/B,cAAc,OAAO;AAAA,UAAA,CACtB;AAAA,QACH,SAAS,KAAK;AACZ,cAAI,MAAM,mCAAmC,GAAG;AAAA,QAClD;AAEA,iBAAS,WAAW,UAAU,CAAA,GAAI,4BAA4B;AAC9DE,eAAAA,QAAO,KAAK,4BAA4B;AACxC,eAAO;AAAA,UACL,mDAAmD,CAAC;AAAA,QAAA;AAGtD,cAAM,IAAI,QAAQ,CAAA,YAAW,WAAW,SAAS,GAAI,CAAC;AACtD,cAAM,OAAO,cAAA;AAAA,MACf;AAAA,IAAA;AAAA,IAGF,QAAQL,KAAAA,OAAO;AAAA,MACb,OAAOC,KAAAA,mBAAmBC,OAAAA,UAAU,iBAAiB,EAAE,MAAM,YAAY;AACvE,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,KAAKN,KAAAA,mBAAmBC,iBAAU,eAAe,EAAE;AAAA,QACjD,OAAO,EAAE,IAAA,MAAU;AACjB,gBAAM,EAAE,OAAA,IAAWC,oBAAA;AACnB,cAAI,CAAC,OAAO,OAAO,QAAS,QAAO;AACnC,cAAI,OAAO,YAAY,OAAO,SAAU,QAAO;AAE/C,gBAAM,aAAa,OAAO;AAC1B,cAAI,YAAY;AACd,gBAAI,IAAI,8BAA8B;AACtC,kBAAM,OAAO,KAAA;AAAA,UACf;AAEA,cAAI,IAAI,oBAAoB;AAC5B,cAAI,KAAK;AACT,cAAI;AACFK,uCAAqB;AAAA,cACnB,WAAW,OAAO,OAAO,QAAQ;AAAA,cACjC,mBAAmB,OAAO,OAAO,QAAQ;AAAA,YAAA,CAC1C;AACD,iBAAK;AACL,gBAAI,IAAI,6BAA6B;AAAA,UACvC,SAAS,KAAK;AACZ,gBAAI,MAAM,yBAAyB,GAAG;AAAA,UACxC;AAEA,cAAI,YAAY;AACd,gBAAI,IAAI,oBAAoB;AAC5B,kBAAM,OAAO,MAAA;AAAA,UACf;AAEA,iBAAO;AAAA,QACT;AAAA,MAAA;AAAA,IACF,CACD;AAAA,IAED,UAAUP,KAAAA,mBAAmBC,iBAAU,YAAY,EAAE;AAAA,MACnD,iBAAiB,EAAE,QAAQ,OAAO;AAChC,cAAM,WAAW,YAAY,IAAI,CAAC,QAAS,IAAI,UAAU,MAAM,CAAC;AAChE,yBAAiB,CAAC,CAAC,KAAKO,aAAAA,GAAGC,OAAAA,cAAc,gBAAgB;AAAA,UACvD,QAAQ;AAAA,QAAA,CACT,GAAG;AACF,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,aAAaF,KAAAA,mBAAmBC,iBAAU,YAAY,EAAE;AAAA,MACtD,iBAAiB,EAAE,QAAQ,OAAO;AAChC,cAAM,WAAW,YAAY,IAAI,CAAC,QAAS,IAAI,UAAU,MAAM,CAAC;AAChE,yBAAiB,CAAC,MAAM,KAAKO,aAAAA,GAAGC,OAAAA,cAAc,aAAa;AAAA,UACzD,QAAQ;AAAA,QAAA,CACT,GAAG;AACF,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAAA,IAGF,eAAeT,KAAAA,mBAAmBC,iBAAU,iBAAiB,EAAE;AAAA,MAC7D,iBAAiB,EAAE,QAAQ,OAAO;AAChC,cAAM,WAAW,YAAY,IAAI,CAAC,QAAS,IAAI,UAAU,MAAM,CAAC;AAChE,yBAAiB,CAAC,WAAW,KAAKO,aAAAA,GAAGC,OAAAA,cAAc,eAAe;AAAA,UAChE,QAAQ;AAAA,QAAA,CACT,GAAG;AACF,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAAA,EACF,CACD;AACH,CAAC;;;"}
|
|
@@ -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(
|
|
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(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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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"}
|