add hacky database error handling to prevent the server from crashing due to trivial errors

This commit is contained in:
hippoz 2022-08-05 05:18:55 +03:00
parent 6fe398c82a
commit 9540bc6178
No known key found for this signature in database
GPG key ID: 7C52899193467641
7 changed files with 47 additions and 22 deletions

View file

@ -83,7 +83,7 @@ export function decodeToken(encoded: string): Promise<User> {
} }
const user = await query("SELECT * FROM users WHERE id = $1", [decoded.id]); const user = await query("SELECT * FROM users WHERE id = $1", [decoded.id]);
if (user.rowCount < 1) { if (!user || user.rowCount < 1) {
reject("user does not exist (could not find in database by id)"); reject("user does not exist (could not find in database by id)");
return; return;
} }

View file

@ -1,5 +1,25 @@
import { Pool } from "pg"; import { Pool, QueryResult } from "pg";
const pool = new Pool(); const pool = new Pool();
export const query = pool.query.bind(pool); // hacky wrapper function that returns null on database errors.
// this is done because express doesn't automatically call next()
// when an async function throws, so this prevents the server
// from crashing due to trivial database errors that can be handled.
// we could use a try catch block for each query, but that will
// quickly get cumbersome.
export const query = function(text: string, params: any[] = [], rejectOnError = false): Promise<QueryResult | null> {
return new Promise((resolve, reject) => {
pool.query(text, params)
.then((data) => {
resolve(data);
})
.catch((error) => {
if (rejectOnError) {
reject(error);
} else {
resolve(null);
}
});
});
};

View file

@ -20,4 +20,5 @@ export const gatewayErrors = {
PAYLOAD_TOO_LARGE: { code: 4007, message: "Payload too large" }, PAYLOAD_TOO_LARGE: { code: 4007, message: "Payload too large" },
TOO_MANY_SESSIONS: { code: 4008, message: "Too many sessions" }, TOO_MANY_SESSIONS: { code: 4008, message: "Too many sessions" },
NOT_AUTHENTICATED: { code: 4009, message: "Not authenticated" }, NOT_AUTHENTICATED: { code: 4009, message: "Not authenticated" },
GOT_NO_DATABASE_DATA: { code: 4010, message: "Unexpectedly got no data from database" },
}; };

View file

@ -223,6 +223,10 @@ export default function(server: Server) {
// TODO: each user should have their own list of channels that they join // TODO: each user should have their own list of channels that they join
const channels = await query("SELECT id, name, owner_id FROM channels ORDER BY id ASC"); const channels = await query("SELECT id, name, owner_id FROM channels ORDER BY id ASC");
if (!channels) {
return closeWithError(ws, gatewayErrors.GOT_NO_DATABASE_DATA);
}
clientSubscribe(ws, "*"); clientSubscribe(ws, "*");
channels.rows.forEach(c => { channels.rows.forEach(c => {
clientSubscribe(ws, `channel:${c.id}`); clientSubscribe(ws, `channel:${c.id}`);

View file

@ -26,7 +26,7 @@ router.post(
const { name } = req.body; const { name } = req.body;
const result = await query("INSERT INTO channels(name, owner_id) VALUES ($1, $2) RETURNING id, name, owner_id", [name, req.user.id]); const result = await query("INSERT INTO channels(name, owner_id) VALUES ($1, $2) RETURNING id, name, owner_id", [name, req.user.id]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });
@ -60,7 +60,7 @@ router.put(
const id = parseInt(req.params.id); // TODO: ?? const id = parseInt(req.params.id); // TODO: ??
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]); const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
if (permissionCheckResult.rowCount < 1) { if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
return res.status(404).json({ return res.status(404).json({
...errors.NOT_FOUND ...errors.NOT_FOUND
}); });
@ -72,7 +72,7 @@ router.put(
} }
const result = await query("UPDATE channels SET name = $1 WHERE id = $2", [name, id]); const result = await query("UPDATE channels SET name = $1 WHERE id = $2", [name, id]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });
@ -106,7 +106,7 @@ router.delete(
const id = parseInt(req.params.id); // TODO: ?? const id = parseInt(req.params.id); // TODO: ??
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]); const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
if (permissionCheckResult.rowCount < 1) { if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
return res.status(404).json({ return res.status(404).json({
...errors.NOT_FOUND ...errors.NOT_FOUND
}); });
@ -118,7 +118,7 @@ router.delete(
} }
const result = await query("DELETE FROM channels WHERE id = $1", [id]); const result = await query("DELETE FROM channels WHERE id = $1", [id]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });
@ -148,7 +148,7 @@ router.get(
const { id } = req.params; const { id } = req.params;
const result = await query("SELECT id, name, owner_id FROM channels WHERE id = $1", [id]); const result = await query("SELECT id, name, owner_id FROM channels WHERE id = $1", [id]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(404).json({ return res.status(404).json({
...errors.NOT_FOUND ...errors.NOT_FOUND
}); });
@ -164,7 +164,7 @@ router.get(
async (req, res) => { async (req, res) => {
const result = await query("SELECT id, name, owner_id FROM channels"); const result = await query("SELECT id, name, owner_id FROM channels");
return res.status(200).send(result.rows); return res.status(200).send(result ? result.rows : []);
} }
); );
@ -185,7 +185,7 @@ router.post(
const createdAt = Date.now().toString(); const createdAt = Date.now().toString();
const result = await query("INSERT INTO messages(content, channel_id, author_id, created_at) VALUES ($1, $2, $3, $4) RETURNING id", [content, channelId, authorId, createdAt]); const result = await query("INSERT INTO messages(content, channel_id, author_id, created_at) VALUES ($1, $2, $3, $4) RETURNING id", [content, channelId, authorId, createdAt]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });
@ -226,10 +226,10 @@ router.get(
if (before) { if (before) {
const result = await query(getMessagesByChannelPage, [before, channelId]); const result = await query(getMessagesByChannelPage, [before, channelId]);
finalRows = result.rows; finalRows = result ? result.rows : [];
} else { } else {
const result = await query(getMessagesByChannelFirstPage, [channelId]); const result = await query(getMessagesByChannelFirstPage, [channelId]);
finalRows = result.rows; finalRows = result ? result.rows : [];
} }
return res.status(200).send(finalRows); return res.status(200).send(finalRows);

View file

@ -22,7 +22,7 @@ router.delete(
const id = parseInt(req.params.id); // TODO: ?? const id = parseInt(req.params.id); // TODO: ??
const permissionCheckResult = await query("SELECT author_id, channel_id FROM messages WHERE id = $1", [id]); const permissionCheckResult = await query("SELECT author_id, channel_id FROM messages WHERE id = $1", [id]);
if (permissionCheckResult.rowCount < 1) { if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
return res.status(404).json({ return res.status(404).json({
...errors.NOT_FOUND ...errors.NOT_FOUND
}); });
@ -34,7 +34,7 @@ router.delete(
} }
const result = await query("DELETE FROM messages WHERE id = $1", [id]); const result = await query("DELETE FROM messages WHERE id = $1", [id]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });
@ -67,7 +67,7 @@ router.put(
const id = parseInt(req.params.id); // TODO: ?? const id = parseInt(req.params.id); // TODO: ??
const permissionCheckResult = await query(getMessageById, [id]); const permissionCheckResult = await query(getMessageById, [id]);
if (permissionCheckResult.rowCount < 1) { if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
return res.status(404).json({ return res.status(404).json({
...errors.NOT_FOUND ...errors.NOT_FOUND
}); });
@ -79,7 +79,7 @@ router.put(
} }
const result = await query("UPDATE messages SET content = $1 WHERE id = $2", [content, id]); const result = await query("UPDATE messages SET content = $1 WHERE id = $2", [content, id]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });
@ -111,7 +111,7 @@ router.get(
const { id } = req.params; const { id } = req.params;
const result = await query(getMessageById, [id]); const result = await query(getMessageById, [id]);
if (result.rowCount < 1) { if (!result || result.rowCount < 1) {
return res.status(404).json({ return res.status(404).json({
...errors.NOT_FOUND ...errors.NOT_FOUND
}); });

View file

@ -26,7 +26,7 @@ router.post(
const { username, password } = req.body; const { username, password } = req.body;
const existingUser = await query("SELECT * FROM users WHERE username = $1", [username]); const existingUser = await query("SELECT * FROM users WHERE username = $1", [username]);
if (existingUser.rowCount > 0) { if (existingUser && existingUser.rowCount > 0) {
return res.status(400).json({ return res.status(400).json({
...errors.INVALID_DATA, ...errors.INVALID_DATA,
errors: [ { location: "body", msg: "Username already exists", param: "username" } ] errors: [ { location: "body", msg: "Username already exists", param: "username" } ]
@ -35,7 +35,7 @@ router.post(
const hashedPassword = await hash(password, 10); const hashedPassword = await hash(password, 10);
const insertedUser = await query("INSERT INTO users(username, password, is_superuser) VALUES ($1, $2, $3) RETURNING id, username, is_superuser", [username, hashedPassword, false]); const insertedUser = await query("INSERT INTO users(username, password, is_superuser) VALUES ($1, $2, $3) RETURNING id, username, is_superuser", [username, hashedPassword, false]);
if (insertedUser.rowCount < 1) { if (!insertedUser || insertedUser.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });
@ -58,7 +58,7 @@ router.post(
const { username, password } = req.body; const { username, password } = req.body;
const existingUser = await query("SELECT * FROM users WHERE username = $1", [username]); const existingUser = await query("SELECT * FROM users WHERE username = $1", [username]);
if (existingUser.rowCount < 1) { if (!existingUser || existingUser.rowCount < 1) {
return res.status(400).json({ ...errors.BAD_LOGIN_CREDENTIALS }); return res.status(400).json({ ...errors.BAD_LOGIN_CREDENTIALS });
} }
@ -95,7 +95,7 @@ router.post(
if (superuserKey && superuserKey.length >= 1 && key === superuserKey && req.user) { if (superuserKey && superuserKey.length >= 1 && key === superuserKey && req.user) {
const updateUserResult = await query("UPDATE users SET is_superuser = true WHERE id = $1", [req.user.id]); const updateUserResult = await query("UPDATE users SET is_superuser = true WHERE id = $1", [req.user.id]);
if (updateUserResult.rowCount < 1) { if (!updateUserResult || updateUserResult.rowCount < 1) {
return res.status(500).json({ return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA ...errors.GOT_NO_DATABASE_DATA
}); });