返回提交历史
Added
src/controllers/custom/getRegisteredLosersController.ts
+12
-0
Modified
src/routes/custom.ts
+2
-0
Modified
src/routes/webui.ts
+1
-0
Modified
src/services/loginService.ts
+15
-5
Modified
src/services/wsService.ts
+15
-5
Modified
static/webui/index.html
+23
-5
Modified
static/webui/script.js
+120
-31
Modified
static/webui/style.css
+4
-0
Modified
static/webui/translations/de.js
+5
-0
Modified
static/webui/translations/en.js
+5
-0
Modified
static/webui/translations/es.js
+5
-0
Modified
static/webui/translations/fr.js
+5
-0
Modified
static/webui/translations/ru.js
+5
-0
Modified
static/webui/translations/uk.js
+5
-0
Modified
static/webui/translations/zh.js
+5
-0
XFEstudio/SpaceNinjaServer
feat(webui): possession (#3739)
Adds a new tab to the webui for admins to see registered losers with the option to possess them Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3739 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
c59a097a
代码差异
15 个文件
+227
-46
@@ -0,0 +1,12 @@
1
import type { RequestHandler } from "express";
2
import { getAccountForRequest, isAdministrator } from "../../services/loginService.ts";
3
import { Account } from "../../models/loginModel.ts";
4
5
export const getRegisteredLosersController: RequestHandler = async (req, res) => {
6
const account = await getAccountForRequest(req);
7
if (isAdministrator(account)) {
8
res.json(await Account.find({}, "id DisplayName"));
9
} else {
10
res.status(401).end();
11
}
12
};
@@ -30,6 +30,7 @@ import { removeIsNewController } from "../controllers/custom/removeIsNewControll
30
30
import { removeItemsController } from "../controllers/custom/removeItemsController.ts";
31
31
import { retroactivelyApplyCheatController } from "../controllers/api/retroactivelyApplyCheatController.ts";
32
32
import { retroactivelyApplyGuildCheatController } from "../controllers/custom/retroactivelyApplyGuildCheatController.ts";
33
import { getRegisteredLosersController } from "../controllers/custom/getRegisteredLosersController.ts";
33
34
34
35
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
35
36
import { createAccountController } from "../controllers/custom/createAccountController.ts";
@@ -88,6 +89,7 @@ customRouter.get("/removeIsNew", removeIsNewController);
88
89
customRouter.get("/removeItems", removeItemsController);
89
90
customRouter.get("/retroactivelyApplyCheat", retroactivelyApplyCheatController);
90
91
customRouter.get("/retroactivelyApplyGuildCheat", retroactivelyApplyGuildCheatController);
92
customRouter.get("/getRegisteredLosers", getRegisteredLosersController);
91
93
92
94
customRouter.post("/abilityOverride", abilityOverrideController);
93
95
customRouter.post("/createAccount", createAccountController);
@@ -43,6 +43,7 @@ webuiRouter.get("/webui/quests", virtualRouteController);
43
43
webuiRouter.get("/webui/cheats", virtualRouteController);
44
44
webuiRouter.get("/webui/import", virtualRouteController);
45
45
webuiRouter.get("/webui/guildView", virtualRouteController);
46
webuiRouter.get("/webui/admin", virtualRouteController);
46
47
47
48
// Serve static files
48
49
webuiRouter.use("/webui", express.static(path.join(baseDir, "static/webui")));
@@ -105,13 +105,23 @@ export const getAccountForQuery = async (
105
105
throw new Error("Request is missing nonce parameter");
106
106
}
107
107
const account = await Account.findById(query.accountId);
108
if (!account || account.Nonce != nonce) {
108
if (!account) {
109
109
throw new Error("Invalid accountId-nonce pair");
110
110
}
111
if (account.Dropped && query.ct) {
112
logger.debug(`removing dropped mark from ${query.accountId}`);
113
account.Dropped = undefined;
114
await account.save();
111
if (query.possesser) {
112
const possesser = await Account.findOne({ _id: query.possesser, Nonce: nonce });
113
if (!possesser || !isAdministrator(possesser)) {
114
throw new Error(`Invalid accountId-nonce pair`);
115
}
116
} else {
117
if (account.Nonce != nonce) {
118
throw new Error("Invalid accountId-nonce pair");
119
}
120
if (account.Dropped && query.ct) {
121
logger.debug(`removing dropped mark from ${query.accountId}`);
122
account.Dropped = undefined;
123
await account.save();
124
}
115
125
}
116
126
return account;
117
127
};
@@ -62,6 +62,7 @@ interface IWsCustomData extends WebSocket {
62
62
address: string;
63
63
reflexiveAddress: string;
64
64
accountId?: string;
65
realAccountId?: string;
65
66
isGame?: boolean;
66
67
}
67
68
@@ -69,6 +70,7 @@ interface IWsMsgFromClient {
69
70
auth?: {
70
71
email: string;
71
72
password: string;
73
possessing?: string;
72
74
isRegister: boolean;
73
75
};
74
76
auth_game?:
@@ -135,6 +137,7 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
135
137
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
136
138
if (data.auth) {
137
139
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
140
let accessedAccount = account;
138
141
if (account) {
139
142
if (isCorrectPassword(data.auth.password, account.password)) {
140
143
if (!account.Nonce) {
@@ -142,6 +145,11 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
142
145
account.Nonce = createNonce();
143
146
await (account as HydratedDocument<IDatabaseAccountJson>).save();
144
147
}
148
if (data.auth.possessing) {
149
accessedAccount = isAdministrator(account)
150
? await Account.findById(data.auth.possessing)
151
: null;
152
}
145
153
} else {
146
154
account = null;
147
155
}
@@ -155,18 +163,20 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
155
163
DisplayName: name,
156
164
Nonce: createNonce()
157
165
});
166
accessedAccount = account;
158
167
}
159
if (account) {
160
(ws as IWsCustomData).accountId = account.id;
168
if (account && accessedAccount) {
169
(ws as IWsCustomData).accountId = accessedAccount.id;
170
(ws as IWsCustomData).realAccountId = account.id;
161
171
if (!config.webui?.adminOnly || isAdministrator(account)) {
162
172
ws.send(
163
173
JSON.stringify({
164
174
auth_succ: {
165
175
id: account.id,
166
DisplayName: account.DisplayName,
176
DisplayName: accessedAccount.DisplayName,
167
177
Nonce: account.Nonce
168
178
},
169
have_game_ws: haveGameWs(account.id)
179
have_game_ws: haveGameWs(accessedAccount.id)
170
180
} satisfies IWsMsgToClient)
171
181
);
172
182
} else {
@@ -339,7 +349,7 @@ export const handleNonceInvalidation = (accountId: string): void => {
339
349
export const bootNonAdminsFromWebui = (): void => {
340
350
forEachWsClient(client => {
341
351
if (client.accountId && !client.isGame) {
342
void Account.findById(client.accountId).then(account => {
352
void Account.findById(client.realAccountId ?? client.accountId).then(account => {
343
353
if (!account || !isAdministrator(account)) {
344
354
client.send(JSON.stringify({ logged_out: true } satisfies IWsMsgToClientWebui));
345
355
client.close();
@@ -39,7 +39,7 @@
39
39
<div class="nav-item dropdown user-dropdown">
40
40
<button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
41
41
<ul class="dropdown-menu dropdown-menu-end">
42
<li><a class="dropdown-item" href="/webui/" onclick="doLogout();" data-loc="navbar_logout"></a></li>
42
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();doLogout();" data-loc="navbar_logout"></a></li>
43
43
<li><hr class="dropdown-divider"></li>
44
44
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();changeAccountPassword();" data-loc="navbar_changePassword"></a></li>
45
45
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();changeAccountEmail();" data-loc="navbar_changeEmail"></a></li>
@@ -73,9 +73,12 @@
73
73
<li class="nav-item">
74
74
<a class="nav-link" href="/webui/import" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_import"></a>
75
75
</li>
76
<li class="nav-item" id="nav-guildView">
76
<li class="nav-item">
77
77
<a class="nav-link" href="/webui/guildView" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_guildView"></a>
78
78
</li>
79
<li class="nav-item">
80
<a class="nav-link" href="/webui/admin" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_admin"></a>
81
</li>
79
82
</ul>
80
83
</div>
81
84
</div>
@@ -1200,10 +1203,10 @@
1200
1203
<div class="card mb-3">
1201
1204
<h5 class="card-header" data-loc="cheats_server"></h5>
1202
1205
<div class="card-body">
1203
<div class="d-none config-admin-hide">
1206
<div class="d-none admin-hide">
1204
1207
<p class="card-text" data-loc="cheats_administratorRequirement"></p>
1205
1208
</div>
1206
<div class="d-none config-admin-show config-form">
1209
<div class="d-none admin-show config-form">
1207
1210
<div class="form-check">
1208
1211
<input class="form-check-input" type="checkbox" id="skipTutorial" />
1209
1212
<label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
@@ -1223,7 +1226,7 @@
1223
1226
</div>
1224
1227
</div>
1225
1228
</div>
1226
<div class="card d-none config-admin-show config-form">
1229
<div class="card d-none admin-show config-form">
1227
1230
<h5 class="card-header" data-loc="worldState"></h5>
1228
1231
<div class="card-body">
1229
1232
<div class="form-check">
@@ -1705,6 +1708,21 @@
1705
1708
<li><a href="#" onclick="event.preventDefault();setImportSample('eidolonLocPins');" data-loc="import_samples_eidolonLocPins"></a></li>
1706
1709
</ul>
1707
1710
</div>
1711
<div data-route="/webui/admin" data-title-tag="navbar_admin">
1712
<div class="d-none admin-hide">
1713
<p class="card-text" data-loc="cheats_administratorRequirement"></p>
1714
</div>
1715
<div class="d-none admin-show">
1716
<div class="card">
1717
<h5 class="card-header" data-loc="admin_users"></h5>
1718
<div class="card-body">
1719
<table class="table table-small table-hover mb-0">
1720
<tbody id="registered-losers"></tbody>
1721
</table>
1722
</div>
1723
</div>
1724
</div>
1725
</div>
1708
1726
</div>
1709
1727
<div class="toast-container position-fixed bottom-0 end-0 p-3"></div>
1710
1728
</div>
@@ -21,6 +21,7 @@ const sendAuth = isRegister => {
21
21
auth: {
22
22
email: localStorage.getItem("email").toLowerCase(),
23
23
password: wp.encSync(localStorage.getItem("password")),
24
possessing: localStorage.getItem("possessing"),
24
25
isRegister
25
26
}
26
27
})
@@ -65,7 +66,17 @@ function openWebSocket() {
65
66
$("#password").val("");
66
67
$(".displayname").text(data.DisplayName);
67
68
window.accountId = data.id;
68
window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce + "&wsid=" + wsid;
69
window.authz = "accountId=";
70
let possessing = localStorage.getItem("possessing");
71
if (possessing == data.id) {
72
localStorage.removeItem("possessing");
73
possessing = null;
74
}
75
if (possessing) {
76
window.accountId = possessing;
77
window.authz += possessing + "&possesser=";
78
}
79
window.authz += data.id + "&nonce=" + data.Nonce + "&wsid=" + wsid;
69
80
if (window.dict) {
70
81
updateLocElements();
71
82
}
@@ -81,21 +92,26 @@ function openWebSocket() {
81
92
}
82
93
if ("auth_fail" in msg) {
83
94
auth_pending = false;
84
logout();
85
if (single.getCurrentPath() == "/webui/") {
86
if (msg.auth_fail == "bad login") {
87
alert(loc("code_loginFail"));
88
} else if (msg.auth_fail == "bad register") {
89
alert(loc("code_regFail"));
90
} else if (msg.auth_fail == "admin only") {
91
alert(loc("code_adminOnlyLogin"));
92
} else if (msg.auth_fail == "registered but admin only") {
93
alert(loc("code_adminOnlyRegister"));
95
if (localStorage.getItem("possessing")) {
96
localStorage.removeItem("possessing");
97
sendAuth();
98
} else {
99
logout();
100
if (single.getCurrentPath() == "/webui/") {
101
if (msg.auth_fail == "bad login") {
102
alert(loc("code_loginFail"));
103
} else if (msg.auth_fail == "bad register") {
104
alert(loc("code_regFail"));
105
} else if (msg.auth_fail == "admin only") {
106
alert(loc("code_adminOnlyLogin"));
107
} else if (msg.auth_fail == "registered but admin only") {
108
alert(loc("code_adminOnlyRegister"));
109
} else {
110
alert(msg.auth_fail);
111
}
94
112
} else {
95
alert(msg.auth_fail);
113
single.loadRoute("/webui/");
96
114
}
97
} else {
98
single.loadRoute("/webui/");
99
115
}
100
116
}
101
117
if ("nonce_updated" in msg) {
@@ -107,8 +123,13 @@ function openWebSocket() {
107
123
updateInventory();
108
124
}
109
125
if ("logged_out" in msg) {
110
logout();
111
single.loadRoute("/webui/"); // Show login screen
126
if (localStorage.getItem("possessing")) {
127
localStorage.removeItem("possessing");
128
sendAuth();
129
} else {
130
logout();
131
single.loadRoute("/webui/"); // Show login screen
132
}
112
133
}
113
134
if ("have_game_ws" in msg) {
114
135
window.have_game_ws = msg.have_game_ws;
@@ -129,8 +150,8 @@ openWebSocket();
129
150
130
151
function refreshServerConfig() {
131
152
//window.is_admin = undefined;
132
if (single.getCurrentPath() == "/webui/cheats") {
133
single.loadRoute("/webui/cheats");
153
if (single.getCurrentPath() == "/webui/cheats" || single.getCurrentPath() == "/webui/admin") {
154
single.loadRoute(single.getCurrentPath());
134
155
}
135
156
}
136
157
@@ -177,10 +198,16 @@ function logout() {
177
198
}
178
199
179
200
function doLogout() {
180
logout();
181
if (ws_is_open) {
182
// Unsubscribe from notifications about nonce invalidation
183
window.ws.send(JSON.stringify({ logout: true }));
201
if (localStorage.getItem("possessing")) {
202
localStorage.removeItem("possessing");
203
location.href = "/webui/inventory";
204
} else {
205
logout();
206
if (ws_is_open) {
207
// Unsubscribe from notifications about nonce invalidation
208
window.ws.send(JSON.stringify({ logout: true }));
209
}
210
location.href = "/webui/";
184
211
}
185
212
}
186
213
@@ -206,12 +233,13 @@ function renameAccount(taken_name) {
206
233
}
207
234
208
235
function deleteAccount() {
209
if (window.confirm(loc("code_deleteAccountConfirm"))) {
236
if (
237
window.confirm(
238
loc(localStorage.getItem("possessing") ? "code_deletePosssedAccountConfirm" : "code_deleteAccountConfirm")
239
)
240
) {
210
241
revalidateAuthz().then(() => {
211
fetch("/custom/deleteAccount?" + window.authz).then(() => {
212
logout();
213
single.loadRoute("/webui/"); // Show login screen
214
});
242
fetch("/custom/deleteAccount?" + window.authz);
215
243
});
216
244
}
217
245
}
@@ -3940,8 +3968,8 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
3940
3968
})
3941
3969
.done(json => {
3942
3970
//window.is_admin = true;
3943
$(".config-admin-hide").addClass("d-none");
3944
$(".config-admin-show").removeClass("d-none");
3971
$(".admin-hide").addClass("d-none");
3972
$(".admin-show").removeClass("d-none");
3945
3973
Object.entries(json).forEach(entry => {
3946
3974
const [key, value] = entry;
3947
3975
const elm = document.getElementById(key);
@@ -3968,8 +3996,8 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
3968
3996
});
3969
3997
} else {
3970
3998
//window.is_admin = false;
3971
$(".config-admin-hide").removeClass("d-none");
3972
$(".config-admin-show").addClass("d-none");
3999
$(".admin-hide").removeClass("d-none");
4000
$(".admin-show").addClass("d-none");
3973
4001
}
3974
4002
});
3975
4003
}
@@ -5150,3 +5178,64 @@ function removeItems(category) {
5150
5178
}
5151
5179
});
5152
5180
}
5181
5182
single.getRoute("/webui/admin").on("beforeload", function () {
5183
let interval;
5184
interval = setInterval(() => {
5185
if (window.authz) {
5186
clearInterval(interval);
5187
$.get("/custom/getRegisteredLosers?" + window.authz)
5188
.done(users => {
5189
//window.is_admin = true;
5190
$(".admin-hide").addClass("d-none");
5191
$(".admin-show").removeClass("d-none");
5192
document.getElementById("registered-losers").innerHTML = "";
5193
for (const user of users) {
5194
const tr = document.createElement("tr");
5195
{
5196
const td = document.createElement("td");
5197
td.textContent = user.DisplayName;
5198
tr.appendChild(td);
5199
}
5200
{
5201
const td = document.createElement("td");
5202
td.innerHTML = `<code>${user.id}</code>`;
5203
tr.appendChild(td);
5204
}
5205
{
5206
const td = document.createElement("td");
5207
if (window.accountId != user.id) {
5208
const a = document.createElement("a");
5209
a.textContent = loc(`admin_possess`);
5210
a.href = "#";
5211
a.onclick = function () {
5212
localStorage.setItem("possessing", user.id);
5213
location.href = "/webui/inventory";
5214
};
5215
td.appendChild(a);
5216
}
5217
tr.appendChild(td);
5218
}
5219
document.getElementById("registered-losers").appendChild(tr);
5220
}
5221
})
5222
.fail(res => {
5223
if (res.responseText == "Log-in expired") {
5224
if (ws_is_open && !auth_pending) {
5225
console.warn("Credentials invalidated but the server didn't let us know");
5226
sendAuth();
5227
}
5228
revalidateAuthz().then(() => {
5229
if (single.getCurrentPath() == "/webui/admin") {
5230
single.loadRoute("/webui/admin");
5231
}
5232
});
5233
} else {
5234
//window.is_admin = false;
5235
$(".admin-hide").removeClass("d-none");
5236
$(".admin-show").addClass("d-none");
5237
}
5238
});
5239
}
5240
}, 10);
5241
});
@@ -29,6 +29,10 @@ body:not(.logged-in) .user-dropdown {
29
29
display: none;
30
30
}
31
31
32
.card-body .table.mb-0 tr:last-child {
33
border-bottom-color: transparent;
34
}
35
32
36
/* font awesome icons */
33
37
svg {
34
38
fill: currentColor;
@@ -16,6 +16,7 @@ dict = {
16
16
code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`,
17
17
code_changeNameRetry: `|NAME| ist bereits vergeben.`,
18
18
code_deleteAccountConfirm: `Bist du sicher, dass du deinen Account |DISPLAYNAME| (|EMAIL|) löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.`,
19
code_deletePosssedAccountConfirm: `[UNTRANSLATED] Are you sure you want to delete the account |DISPLAYNAME|? This action cannot be undone.`,
19
20
code_archgun: `Arch-Gewehr`,
20
21
code_melee: `Nahkampf`,
21
22
code_pistol: `Pistole`,
@@ -530,5 +531,9 @@ dict = {
530
531
guildView_promote: `Befördern`,
531
532
guildView_demote: `Degradieren`,
532
533
534
navbar_admin: `[UNTRANSLATED] Admin`,
535
admin_users: `[UNTRANSLATED] Users`,
536
admin_possess: `[UNTRANSLATED] Possess`,
537
533
538
prettier_sucks_ass: ``
534
539
};
@@ -15,6 +15,7 @@ dict = {
15
15
code_changeNameConfirm: `What would you like to change your account name to?`,
16
16
code_changeNameRetry: `|NAME| is already taken.`,
17
17
code_deleteAccountConfirm: `Are you sure you want to delete your account |DISPLAYNAME| (|EMAIL|)? This action cannot be undone.`,
18
code_deletePosssedAccountConfirm: `Are you sure you want to delete the account |DISPLAYNAME|? This action cannot be undone.`,
18
19
code_archgun: `Archgun`,
19
20
code_melee: `Melee`,
20
21
code_pistol: `Pistol`,
@@ -529,5 +530,9 @@ dict = {
529
530
guildView_promote: `Promote`,
530
531
guildView_demote: `Demote`,
531
532
533
navbar_admin: `Admin`,
534
admin_users: `Users`,
535
admin_possess: `Possess`,
536
532
537
prettier_sucks_ass: ``
533
538
};
@@ -16,6 +16,7 @@ dict = {
16
16
code_changeNameConfirm: `¿Qué nombre te gustaría ponerle a tu cuenta?`,
17
17
code_changeNameRetry: `|NAME| Ya está en uso.`,
18
18
code_deleteAccountConfirm: `¿Estás seguro de que deseas eliminar tu cuenta |DISPLAYNAME| (|EMAIL|)? Esta acción es permanente.`,
19
code_deletePosssedAccountConfirm: `[UNTRANSLATED] Are you sure you want to delete the account |DISPLAYNAME|? This action cannot be undone.`,
19
20
code_archgun: `Archcañón`,
20
21
code_melee: `Cuerpo a cuerpo`,
21
22
code_pistol: `Pistola`,
@@ -530,5 +531,9 @@ dict = {
530
531
guildView_promote: `Promover`,
531
532
guildView_demote: `Degradar`,
532
533
534
navbar_admin: `[UNTRANSLATED] Admin`,
535
admin_users: `[UNTRANSLATED] Users`,
536
admin_possess: `[UNTRANSLATED] Possess`,
537
533
538
prettier_sucks_ass: ``
534
539
};
@@ -16,6 +16,7 @@ dict = {
16
16
code_changeNameConfirm: `Nouveau nom du compte :`,
17
17
code_changeNameRetry: `|NAME| est déjà pris.`,
18
18
code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`,
19
code_deletePosssedAccountConfirm: `[UNTRANSLATED] Are you sure you want to delete the account |DISPLAYNAME|? This action cannot be undone.`,
19
20
code_archgun: `Archgun`,
20
21
code_melee: `Melee`,
21
22
code_pistol: `Pistolet`,
@@ -530,5 +531,9 @@ dict = {
530
531
guildView_promote: `Promouvoir`,
531
532
guildView_demote: `Rétrograder`,
532
533
534
navbar_admin: `[UNTRANSLATED] Admin`,
535
admin_users: `[UNTRANSLATED] Users`,
536
admin_possess: `[UNTRANSLATED] Possess`,
537
533
538
prettier_sucks_ass: ``
534
539
};