fix: add try/catch around websocket message event handler #2529

Merged
Sainan merged 2 commits from ws-try-catch into main 2025-07-21 07:44:55 -07:00
3 changed files with 68 additions and 58 deletions

View File

@ -1,16 +1,11 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { logger } from "@/src/utils/logger"; import { logError } from "@/src/utils/logger";
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => { export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
if (err.message == "Invalid accountId-nonce pair") { if (err.message == "Invalid accountId-nonce pair") {
res.status(400).send("Log-in expired"); res.status(400).send("Log-in expired");
} else if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while processing ${req.path} request`;
logger.error(stackArr.join("\n"));
res.status(500).end();
} else { } else {
logger.error(`uncaught error while processing ${req.path} request: ${err.message}`); logError(err, `processing ${req.path} request`);
res.status(500).end(); res.status(500).end();
} }
}; };

View File

@ -5,6 +5,7 @@ import { Account } from "@/src/models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
import { IDatabaseAccountJson } from "@/src/types/loginTypes"; import { IDatabaseAccountJson } from "@/src/types/loginTypes";
import { HydratedDocument } from "mongoose"; import { HydratedDocument } from "mongoose";
import { logError } from "@/src/utils/logger";
let wsServer: ws.Server | undefined; let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined; let wssServer: ws.Server | undefined;
@ -88,63 +89,67 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => { ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient; try {
if (data.auth) { const data = JSON.parse(String(msg)) as IWsMsgFromClient;
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email }); if (data.auth) {
if (account) { let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (isCorrectPassword(data.auth.password, account.password)) { if (account) {
if (!account.Nonce) { if (isCorrectPassword(data.auth.password, account.password)) {
account.ClientType = "webui"; if (!account.Nonce) {
account.Nonce = createNonce(); account.ClientType = "webui";
await (account as HydratedDocument<IDatabaseAccountJson>).save(); account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else {
account = null;
} }
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else { } else {
account = null; ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
} }
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
} }
if (account) { if (data.logout) {
(ws as IWsCustomData).accountId = account.id; const accountId = (ws as IWsCustomData).accountId;
ws.send( (ws as IWsCustomData).accountId = undefined;
JSON.stringify({ await Account.updateOne(
auth_succ: { {
id: account.id, _id: accountId,
DisplayName: account.DisplayName, ClientType: "webui"
Nonce: account.Nonce },
} {
} satisfies IWsMsgToClient) Nonce: 0
); }
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
); );
} }
} } catch (e) {
if (data.logout) { logError(e as Error, `processing websocket message`);
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
} }
}); });
}; };

View File

@ -108,3 +108,13 @@ errorLog.on("new", filename => logger.info(`Using error log file: ${filename}`))
combinedLog.on("new", filename => logger.info(`Using combined log file: ${filename}`)); combinedLog.on("new", filename => logger.info(`Using combined log file: ${filename}`));
errorLog.on("rotate", filename => logger.info(`Rotated error log file: ${filename}`)); errorLog.on("rotate", filename => logger.info(`Rotated error log file: ${filename}`));
combinedLog.on("rotate", filename => logger.info(`Rotated combined log file: ${filename}`)); combinedLog.on("rotate", filename => logger.info(`Rotated combined log file: ${filename}`));
export const logError = (err: Error, context: string): void => {
if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while ${context}`;
logger.error(stackArr.join("\n"));
} else {
logger.error(`uncaught error while ${context}: ${err.message}`);
}
};