返回提交历史
Added
src/controllers/custom/changeEmailController.ts
+38
-0
Added
src/controllers/custom/changePasswordController.ts
+31
-0
Modified
src/routes/custom.ts
+5
-0
Modified
src/routes/webui.ts
+0
-1
Modified
static/webui/index.html
+3
-1
Modified
static/webui/script.js
+97
-0
Modified
static/webui/translations/de.js
+13
-0
Modified
static/webui/translations/en.js
+13
-0
Modified
static/webui/translations/es.js
+13
-0
Modified
static/webui/translations/fr.js
+13
-0
Modified
static/webui/translations/ru.js
+13
-0
Modified
static/webui/translations/uk.js
+13
-0
Modified
static/webui/translations/zh.js
+13
-0
XFEstudio/XFESpaceNinjaServer
Feat: Change Password & Email (#3670)
Add POST /custom/changePassword and /custom/changeEmail, authenticated with the same accountId/nonce/wsid query params as other custom routes. Passwords are verified and stored using the existing Whirlpool-hashed format used by WebSocket login. Has handling for duplicate Email. Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3670 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: Drake <kuzuriyager@gmail.com> Co-committed-by: Drake <kuzuriyager@gmail.com>
695d90ad
代码差异
13 个文件
+265
-2
@@ -0,0 +1,38 @@
1
import type { RequestHandler } from "express";
2
import { Account } from "../../models/loginModel.ts";
3
import { getAccountForRequest, isCorrectPassword } from "../../services/loginService.ts";
4
5
interface IChangeEmailBody {
6
currentPassword?: string;
7
newEmail?: string;
8
}
9
10
export const changeEmailController: RequestHandler = async (req, res) => {
11
const account = await getAccountForRequest(req);
12
const body = req.body as IChangeEmailBody;
13
if (typeof body.currentPassword != "string" || typeof body.newEmail != "string") {
14
res.status(400).send("Missing fields").end();
15
return;
16
}
17
const newEmail = body.newEmail.trim().toLowerCase();
18
if (!newEmail.includes("@")) {
19
res.status(400).send("Invalid email").end();
20
return;
21
}
22
if (!isCorrectPassword(body.currentPassword, account.password)) {
23
res.status(403).send("Wrong password").end();
24
return;
25
}
26
if (newEmail === account.email) {
27
res.status(400).send("That is already your email").end();
28
return;
29
}
30
const taken = await Account.findOne({ email: newEmail });
31
if (taken) {
32
res.status(409).send("Email already in use").end();
33
return;
34
}
35
account.email = newEmail;
36
await account.save();
37
res.end();
38
};
@@ -0,0 +1,31 @@
1
import type { RequestHandler } from "express";
2
import { getAccountForRequest, isCorrectPassword } from "../../services/loginService.ts";
3
4
interface IChangePasswordBody {
5
currentPassword?: string;
6
newPassword?: string;
7
}
8
9
export const changePasswordController: RequestHandler = async (req, res) => {
10
const account = await getAccountForRequest(req);
11
const body = req.body as IChangePasswordBody;
12
if (typeof body.currentPassword != "string" || typeof body.newPassword != "string") {
13
res.status(400).send("Missing password fields").end();
14
return;
15
}
16
if (!body.newPassword.length) {
17
res.status(400).send("New password is empty").end();
18
return;
19
}
20
if (!isCorrectPassword(body.currentPassword, account.password)) {
21
res.status(403).send("Wrong password").end();
22
return;
23
}
24
if (body.currentPassword === body.newPassword) {
25
res.status(400).send("New password must differ from the current one").end();
26
return;
27
}
28
account.password = body.newPassword;
29
await account.save();
30
res.end();
31
};
@@ -11,6 +11,8 @@ import { getAccountInfoController } from "../controllers/custom/getAccountInfoCo
11
11
import { getGuildController } from "../controllers/custom/getGuildController.ts";
12
12
import { getAllianceController } from "../controllers/custom/getAllianceController.ts";
13
13
import { renameAccountController } from "../controllers/custom/renameAccountController.ts";
14
import { changePasswordController } from "../controllers/custom/changePasswordController.ts";
15
import { changeEmailController } from "../controllers/custom/changeEmailController.ts";
14
16
import { ircDroppedController } from "../controllers/custom/ircDroppedController.ts";
15
17
import { hubDroppedController } from "../controllers/custom/hubDroppedController.ts";
16
18
import { unlockAllIntrinsicsController } from "../controllers/custom/unlockAllIntrinsicsController.ts";
@@ -108,6 +110,9 @@ customRouter.post("/setUmbraEchoes", setUmbraEchoesController);
108
110
customRouter.post("/setAccountCheat", setAccountCheatController);
109
111
customRouter.post("/setGuildCheat", setGuildCheatController);
110
112
113
customRouter.post("/changePassword", changePasswordController);
114
customRouter.post("/changeEmail", changeEmailController);
115
111
116
customRouter.post("/getConfig", getConfigController);
112
117
customRouter.post("/setConfig", setConfigController);
113
118
@@ -38,7 +38,6 @@ const virtualRouteController: RequestHandler = async (_req, res) => {
38
38
webuiRouter.get("/webui/inventory", virtualRouteController);
39
39
webuiRouter.get("/webui/detailedView", virtualRouteController);
40
40
webuiRouter.get("/webui/mods", virtualRouteController);
41
webuiRouter.get("/webui/settings", virtualRouteController);
42
41
webuiRouter.get("/webui/quests", virtualRouteController);
43
42
webuiRouter.get("/webui/cheats", virtualRouteController);
44
43
webuiRouter.get("/webui/import", virtualRouteController);
@@ -41,6 +41,8 @@
41
41
<ul class="dropdown-menu dropdown-menu-end">
42
42
<li><a class="dropdown-item" href="/webui/" onclick="doLogout();" data-loc="navbar_logout"></a></li>
43
43
<li><hr class="dropdown-divider"></li>
44
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();changeAccountPassword();" data-loc="navbar_changePassword"></a></li>
45
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();changeAccountEmail();" data-loc="navbar_changeEmail"></a></li>
44
46
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();renameAccount();" data-loc="navbar_renameAccount"></a></li>
45
47
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();deleteAccount();" data-loc="navbar_deleteAccount"></a></li>
46
48
</ul>
@@ -917,7 +919,7 @@
917
919
</div>
918
920
</div>
919
921
</div>
920
<div data-route="/webui/cheats, /webui/settings" data-title-tag="navbar_cheats">
922
<div data-route="/webui/cheats" data-title-tag="navbar_cheats">
921
923
<div class="row g-3">
922
924
<div class="col-md-6">
923
925
<div class="card">
@@ -206,6 +206,103 @@ function deleteAccount() {
206
206
}
207
207
}
208
208
209
function changeAccountPassword() {
210
const cur = window.prompt(loc("code_promptCurrentPassword"));
211
if (cur === null) {
212
return;
213
}
214
if (!cur.length) {
215
toast(loc("settings_changeFailed"));
216
return;
217
}
218
const nw = window.prompt(loc("code_promptNewPassword"));
219
if (nw === null) {
220
return;
221
}
222
if (!nw.length) {
223
toast(loc("settings_changeFailed"));
224
return;
225
}
226
const cf = window.prompt(loc("code_promptConfirmNewPassword"));
227
if (cf === null) {
228
return;
229
}
230
if (nw !== cf) {
231
toast(loc("settings_passwordMismatch"));
232
return;
233
}
234
revalidateAuthz().then(() => {
235
fetch("/custom/changePassword?" + window.authz, {
236
method: "POST",
237
headers: { "Content-Type": "application/json" },
238
body: JSON.stringify({
239
currentPassword: wp.encSync(cur),
240
newPassword: wp.encSync(nw)
241
})
242
}).then(res => {
243
if (res.status == 200) {
244
localStorage.setItem("password", nw);
245
toast(loc("settings_passwordSuccess"));
246
} else if (res.status == 403) {
247
toast(loc("settings_wrongPassword"));
248
} else {
249
res.text().then(t => toast(t || loc("settings_changeFailed")));
250
}
251
});
252
});
253
}
254
255
function changeAccountEmail() {
256
const curPw = window.prompt(loc("code_promptEmailCurrentPassword"));
257
if (curPw === null) {
258
return;
259
}
260
if (!curPw.length) {
261
toast(loc("settings_changeFailed"));
262
return;
263
}
264
const currentHash = wp.encSync(curPw);
265
function promptAndSend(conflictEmail) {
266
const newEmailRaw = window.prompt(
267
`${conflictEmail ? loc("code_changeEmailRetry").replace("|EMAIL|", conflictEmail) + " " : ""}${loc("code_promptNewEmail")}`
268
);
269
if (newEmailRaw === null) {
270
return;
271
}
272
const newEmail = newEmailRaw.trim().toLowerCase();
273
if (!newEmail.length) {
274
toast(loc("settings_changeFailed"));
275
return;
276
}
277
if (!newEmail.includes("@")) {
278
toast(loc("settings_changeFailed"));
279
return;
280
}
281
revalidateAuthz().then(() => {
282
fetch("/custom/changeEmail?" + window.authz, {
283
method: "POST",
284
headers: { "Content-Type": "application/json" },
285
body: JSON.stringify({
286
currentPassword: currentHash,
287
newEmail
288
})
289
}).then(res => {
290
if (res.status == 200) {
291
localStorage.setItem("email", newEmail);
292
toast(loc("settings_emailSuccess"));
293
} else if (res.status == 403) {
294
toast(loc("settings_wrongPassword"));
295
} else if (res.status == 409) {
296
promptAndSend(newEmail);
297
} else {
298
res.text().then(t => toast(t || loc("settings_changeFailed")));
299
}
300
});
301
});
302
}
303
promptAndSend();
304
}
305
209
306
function updateTitle() {
210
307
const tag = single.getCurrentRoute().elm.getAttribute("data-title-tag");
211
308
if (tag) {
@@ -117,6 +117,14 @@ dict = {
117
117
login_loginButton: `Anmelden`,
118
118
login_registerButton: `Registrieren`,
119
119
navbar_logout: `Abmelden`,
120
navbar_changePassword: `[UNTRANSLATED] Change Password`,
121
navbar_changeEmail: `[UNTRANSLATED] Change Email`,
122
code_promptCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
123
code_promptNewPassword: `[UNTRANSLATED] Enter your new password:`,
124
code_promptConfirmNewPassword: `[UNTRANSLATED] Confirm your new password:`,
125
code_promptEmailCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
126
code_promptNewEmail: `[UNTRANSLATED] Enter your new email address:`,
127
code_changeEmailRetry: `[UNTRANSLATED] |EMAIL| is already registered.`,
120
128
navbar_renameAccount: `Account umbenennen`,
121
129
navbar_deleteAccount: `Account löschen`,
122
130
navbar_inventory: `Inventar`,
@@ -125,6 +133,11 @@ dict = {
125
133
navbar_quests: `Quests`,
126
134
navbar_cheats: `Cheats`,
127
135
navbar_import: `Importieren`,
136
settings_passwordMismatch: `[UNTRANSLATED] The new passwords do not match.`,
137
settings_passwordSuccess: `[UNTRANSLATED] Password updated.`,
138
settings_emailSuccess: `[UNTRANSLATED] Email updated.`,
139
settings_wrongPassword: `[UNTRANSLATED] Current password is incorrect.`,
140
settings_changeFailed: `[UNTRANSLATED] Could not apply the change.`,
128
141
inventory_addItems: `Inhalte hinzufügen`,
129
142
inventory_addItemByItemType: `Roh`,
130
143
inventory_addItemByItemType_warning: `Verwende diese Funktion auf eigene Gefahr. Sie kann dein Inventar beschädigen und du musst Inhalte manuell entfernen, falls etwas schiefgeht.`,
@@ -116,6 +116,14 @@ dict = {
116
116
login_loginButton: `Login`,
117
117
login_registerButton: `Register`,
118
118
navbar_logout: `Logout`,
119
navbar_changePassword: `Change Password`,
120
navbar_changeEmail: `Change Email`,
121
code_promptCurrentPassword: `Enter your current password:`,
122
code_promptNewPassword: `Enter your new password:`,
123
code_promptConfirmNewPassword: `Confirm your new password:`,
124
code_promptEmailCurrentPassword: `Enter your current password:`,
125
code_promptNewEmail: `Enter your new email address:`,
126
code_changeEmailRetry: `|EMAIL| is already registered.`,
119
127
navbar_renameAccount: `Rename Account`,
120
128
navbar_deleteAccount: `Delete Account`,
121
129
navbar_inventory: `Inventory`,
@@ -124,6 +132,11 @@ dict = {
124
132
navbar_quests: `Quests`,
125
133
navbar_cheats: `Cheats`,
126
134
navbar_import: `Import`,
135
settings_passwordMismatch: `The new passwords do not match.`,
136
settings_passwordSuccess: `Password updated.`,
137
settings_emailSuccess: `Email updated.`,
138
settings_wrongPassword: `Current password is incorrect.`,
139
settings_changeFailed: `Could not apply the change.`,
127
140
inventory_addItems: `Add Items`,
128
141
inventory_addItemByItemType: `Raw`,
129
142
inventory_addItemByItemType_warning: `Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
@@ -117,6 +117,14 @@ dict = {
117
117
login_loginButton: `Iniciar sesión`,
118
118
login_registerButton: `Registrarse`,
119
119
navbar_logout: `Cerrar sesión`,
120
navbar_changePassword: `[UNTRANSLATED] Change Password`,
121
navbar_changeEmail: `[UNTRANSLATED] Change Email`,
122
code_promptCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
123
code_promptNewPassword: `[UNTRANSLATED] Enter your new password:`,
124
code_promptConfirmNewPassword: `[UNTRANSLATED] Confirm your new password:`,
125
code_promptEmailCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
126
code_promptNewEmail: `[UNTRANSLATED] Enter your new email address:`,
127
code_changeEmailRetry: `[UNTRANSLATED] |EMAIL| is already registered.`,
120
128
navbar_renameAccount: `Renombrar cuenta`,
121
129
navbar_deleteAccount: `Eliminar cuenta`,
122
130
navbar_inventory: `Inventario`,
@@ -125,6 +133,11 @@ dict = {
125
133
navbar_quests: `Misiones`,
126
134
navbar_cheats: `Trucos`,
127
135
navbar_import: `Importar`,
136
settings_passwordMismatch: `[UNTRANSLATED] The new passwords do not match.`,
137
settings_passwordSuccess: `[UNTRANSLATED] Password updated.`,
138
settings_emailSuccess: `[UNTRANSLATED] Email updated.`,
139
settings_wrongPassword: `[UNTRANSLATED] Current password is incorrect.`,
140
settings_changeFailed: `[UNTRANSLATED] Could not apply the change.`,
128
141
inventory_addItems: `Agregar objetos`,
129
142
inventory_addItemByItemType: `Sin refinar`,
130
143
inventory_addItemByItemType_warning: `Usa esta función bajo tu propio riesgo. Podría dañar tu inventario, y tendrías que eliminar los objetos manualmente si algo sale mal.`,
@@ -117,6 +117,14 @@ dict = {
117
117
login_loginButton: `Connexion`,
118
118
login_registerButton: `S'enregistrer`,
119
119
navbar_logout: `Déconnexion`,
120
navbar_changePassword: `[UNTRANSLATED] Change Password`,
121
navbar_changeEmail: `[UNTRANSLATED] Change Email`,
122
code_promptCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
123
code_promptNewPassword: `[UNTRANSLATED] Enter your new password:`,
124
code_promptConfirmNewPassword: `[UNTRANSLATED] Confirm your new password:`,
125
code_promptEmailCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
126
code_promptNewEmail: `[UNTRANSLATED] Enter your new email address:`,
127
code_changeEmailRetry: `[UNTRANSLATED] |EMAIL| is already registered.`,
120
128
navbar_renameAccount: `Renommer le compte`,
121
129
navbar_deleteAccount: `Supprimer le compte`,
122
130
navbar_inventory: `Inventaire`,
@@ -125,6 +133,11 @@ dict = {
125
133
navbar_quests: `Quêtes`,
126
134
navbar_cheats: `Cheats`,
127
135
navbar_import: `Importer`,
136
settings_passwordMismatch: `[UNTRANSLATED] The new passwords do not match.`,
137
settings_passwordSuccess: `[UNTRANSLATED] Password updated.`,
138
settings_emailSuccess: `[UNTRANSLATED] Email updated.`,
139
settings_wrongPassword: `[UNTRANSLATED] Current password is incorrect.`,
140
settings_changeFailed: `[UNTRANSLATED] Could not apply the change.`,
128
141
inventory_addItems: `Ajouter des items`,
129
142
inventory_addItemByItemType: `Brut`,
130
143
inventory_addItemByItemType_warning: `Cette fonctionnalité comporte des risques. Il faudra rajouter les items manuellement si l'inventaire est compris.`,
@@ -117,6 +117,14 @@ dict = {
117
117
login_loginButton: `Войти`,
118
118
login_registerButton: `Зарегистрироваться`,
119
119
navbar_logout: `Выйти`,
120
navbar_changePassword: `[UNTRANSLATED] Change Password`,
121
navbar_changeEmail: `[UNTRANSLATED] Change Email`,
122
code_promptCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
123
code_promptNewPassword: `[UNTRANSLATED] Enter your new password:`,
124
code_promptConfirmNewPassword: `[UNTRANSLATED] Confirm your new password:`,
125
code_promptEmailCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
126
code_promptNewEmail: `[UNTRANSLATED] Enter your new email address:`,
127
code_changeEmailRetry: `[UNTRANSLATED] |EMAIL| is already registered.`,
120
128
navbar_renameAccount: `Переименовать аккаунт`,
121
129
navbar_deleteAccount: `Удалить аккаунт`,
122
130
navbar_inventory: `Инвентарь`,
@@ -125,6 +133,11 @@ dict = {
125
133
navbar_quests: `Квесты`,
126
134
navbar_cheats: `Читы`,
127
135
navbar_import: `Импорт`,
136
settings_passwordMismatch: `[UNTRANSLATED] The new passwords do not match.`,
137
settings_passwordSuccess: `[UNTRANSLATED] Password updated.`,
138
settings_emailSuccess: `[UNTRANSLATED] Email updated.`,
139
settings_wrongPassword: `[UNTRANSLATED] Current password is incorrect.`,
140
settings_changeFailed: `[UNTRANSLATED] Could not apply the change.`,
128
141
inventory_addItems: `Добавить предметы`,
129
142
inventory_addItemByItemType: `Необработанные данные`,
130
143
inventory_addItemByItemType_warning: `Используйте эту функцию на свой страх и риск. Она может повредить ваш инвентарь, и в случае проблем вам придётся удалять предметы вручную.`,
@@ -117,6 +117,14 @@ dict = {
117
117
login_loginButton: `Увійти`,
118
118
login_registerButton: `Зареєструватися`,
119
119
navbar_logout: `Вийти`,
120
navbar_changePassword: `[UNTRANSLATED] Change Password`,
121
navbar_changeEmail: `[UNTRANSLATED] Change Email`,
122
code_promptCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
123
code_promptNewPassword: `[UNTRANSLATED] Enter your new password:`,
124
code_promptConfirmNewPassword: `[UNTRANSLATED] Confirm your new password:`,
125
code_promptEmailCurrentPassword: `[UNTRANSLATED] Enter your current password:`,
126
code_promptNewEmail: `[UNTRANSLATED] Enter your new email address:`,
127
code_changeEmailRetry: `[UNTRANSLATED] |EMAIL| is already registered.`,
120
128
navbar_renameAccount: `Перейменувати обліковий запис`,
121
129
navbar_deleteAccount: `Видалити обліковий запис`,
122
130
navbar_inventory: `Спорядження`,
@@ -125,6 +133,11 @@ dict = {
125
133
navbar_quests: `Пригоди`,
126
134
navbar_cheats: `Чити`,
127
135
navbar_import: `Імпорт`,
136
settings_passwordMismatch: `[UNTRANSLATED] The new passwords do not match.`,
137
settings_passwordSuccess: `[UNTRANSLATED] Password updated.`,
138
settings_emailSuccess: `[UNTRANSLATED] Email updated.`,
139
settings_wrongPassword: `[UNTRANSLATED] Current password is incorrect.`,
140
settings_changeFailed: `[UNTRANSLATED] Could not apply the change.`,
128
141
inventory_addItems: `Додати предмети`,
129
142
inventory_addItemByItemType: `Необроблені дані`,
130
143
inventory_addItemByItemType_warning: `Використовуйте цю функцію на власний ризик. Вона може пошкодити ваше спорядження, і вам доведеться видаляти предмети вручну, якщо щось піде не так.`,