XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

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
Drake <kuzuriyager@gmail.com>
提交于

代码差异

13 个文件 +265 -2
Added src/controllers/custom/changeEmailController.ts +38 -0
@@ -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 };
Added src/controllers/custom/changePasswordController.ts +31 -0
@@ -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 };
Modified src/routes/custom.ts +5 -0
@@ -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
Modified src/routes/webui.ts +0 -1
@@ -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);
Modified static/webui/index.html +3 -1
@@ -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">
Modified static/webui/script.js +97 -0
@@ -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) {
Modified static/webui/translations/de.js +13 -0
@@ -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.`,
Modified static/webui/translations/en.js +13 -0
@@ -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.`,
Modified static/webui/translations/es.js +13 -0
@@ -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.`,
Modified static/webui/translations/fr.js +13 -0
@@ -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.`,
Modified static/webui/translations/ru.js +13 -0
@@ -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: `Используйте эту функцию на свой страх и риск. Она может повредить ваш инвентарь, и в случае проблем вам придётся удалять предметы вручную.`,
Modified static/webui/translations/uk.js +13 -0
@@ -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: `Використовуйте цю функцію на власний ризик. Вона може пошкодити ваше спорядження, і вам доведеться видаляти предмети вручну, якщо щось піде не так.`,
Modified static/webui/translations/zh.js +13 -0