From 97559c102cf035ce6817d3b78e965b1cf385957d Mon Sep 17 00:00:00 2001 From: Spagles <106791090+Spagles@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:38:42 +0000 Subject: [PATCH 1/7] fix: ensure password verification only occurs if password exists in result --- server/routes/session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/session.js b/server/routes/session.js index ae95f02ee..a350eea80 100644 --- a/server/routes/session.js +++ b/server/routes/session.js @@ -84,7 +84,7 @@ async function handlePasswordLogin (ctx, { limiterSlowBruteByIP, limiterConsecut .first() let successful = false - if (result) { + if (result && result.password) { const match = await verify(result.password, request.body.password) if (match) successful = true From 9988389a50360e6989164a0427b57feffd246b67 Mon Sep 17 00:00:00 2001 From: Spagles <106791090+Spagles@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:39:58 +0000 Subject: [PATCH 2/7] fix: enhance rate limit bypass logic to include server permissions --- server/routes/upload.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/routes/upload.js b/server/routes/upload.js index d45c496f5..c44702f2c 100644 --- a/server/routes/upload.js +++ b/server/routes/upload.js @@ -56,8 +56,18 @@ module.exports = function uploadRoute (dbPool) { } // Check rate limit (skip for users with bypass permission) - const canBypassRateLimit = acl.hasPermission('player.appeals', 'attachment.ratelimit.bypass') || - acl.hasPermission('player.reports', 'attachment.ratelimit.bypass') + let canBypassRateLimit = acl.hasPermission('player.appeals', 'attachment.ratelimit.bypass') || + acl.hasPermission('player.reports', 'attachment.ratelimit.bypass') + + if (!canBypassRateLimit) { + for (const server of ctx.state.serversPool.keys()) { + if (acl.hasServerPermission(server, 'player.appeals', 'attachment.ratelimit.bypass') || + acl.hasServerPermission(server, 'player.reports', 'attachment.ratelimit.bypass')) { + canBypassRateLimit = true + break + } + } + } if (!canBypassRateLimit) { const ipAddr = requestIp.getClientIp(ctx.request) From d7391c1f6ad43289b4e618bd83f44834a19a57cc Mon Sep 17 00:00:00 2001 From: Spagles <106791090+Spagles@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:40:47 +0000 Subject: [PATCH 3/7] fix: enhance permission checks for attachment creation to include server-level permissions --- server/routes/upload.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/routes/upload.js b/server/routes/upload.js index c44702f2c..94ca83962 100644 --- a/server/routes/upload.js +++ b/server/routes/upload.js @@ -85,8 +85,15 @@ module.exports = function uploadRoute (dbPool) { } // Check if user has attachment.create permission for appeals OR reports - const hasAppealsPermission = acl.hasPermission('player.appeals', 'attachment.create') - const hasReportsPermission = acl.hasPermission('player.reports', 'attachment.create') + let hasAppealsPermission = acl.hasPermission('player.appeals', 'attachment.create') + let hasReportsPermission = acl.hasPermission('player.reports', 'attachment.create') + + if (!hasAppealsPermission && !hasReportsPermission) { + for (const server of ctx.state.serversPool.keys()) { + if (acl.hasServerPermission(server, 'player.appeals', 'attachment.create')) hasAppealsPermission = true + if (acl.hasServerPermission(server, 'player.reports', 'attachment.create')) hasReportsPermission = true + } + } if (!hasAppealsPermission && !hasReportsPermission) { ctx.status = 403 From b1d6af55432964639cfbe3bc134ff57af4e30cf1 Mon Sep 17 00:00:00 2001 From: Spagles <106791090+Spagles@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:41:52 +0000 Subject: [PATCH 4/7] fix: ensure count is converted to a number before comparison in cleanup process --- server/data/cleanup-documents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/data/cleanup-documents.js b/server/data/cleanup-documents.js index 8a6d523f7..0bd6c8cfb 100644 --- a/server/data/cleanup-documents.js +++ b/server/data/cleanup-documents.js @@ -36,7 +36,7 @@ async function cleanupOrphanDocuments (dbPool, logger) { .where({ content_hash: contentHash }) .count('* as count') - if (count === 0) { + if (Number(count) === 0) { // Content has no more references - delete file and content record const [content] = await dbPool('bm_web_document_contents') .where({ content_hash: contentHash }) From b096487fb3f3c1ad1bbdf310623c818d5576cfa8 Mon Sep 17 00:00:00 2001 From: Spagles <106791090+Spagles@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:42:43 +0000 Subject: [PATCH 5/7] fix: ensure count is converted to a number before comparison in document deletion logic --- server/graphql/resolvers/mutations/delete-document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/graphql/resolvers/mutations/delete-document.js b/server/graphql/resolvers/mutations/delete-document.js index 7e468203e..8efa798fa 100644 --- a/server/graphql/resolvers/mutations/delete-document.js +++ b/server/graphql/resolvers/mutations/delete-document.js @@ -83,7 +83,7 @@ module.exports = async function deleteDocument (obj, { id }, { log, session, sta .where({ content_hash: contentHash }) .count('* as count') - if (count === 0) { + if (Number(count) === 0) { // No more references - delete content record and file const relativePath = document.path.replace('uploads/documents/', '').split('/').join(path.sep) const fullPath = path.join(UPLOAD_PATH, relativePath) From 823962a1f2d5e1a7e7dd5d78ccb4fe8c5408c252 Mon Sep 17 00:00:00 2001 From: Spagles <106791090+Spagles@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:43:34 +0000 Subject: [PATCH 6/7] fix: ensure comment_id is converted to a number before comparison in permission check --- server/routes/documents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/documents.js b/server/routes/documents.js index de17fdfb8..539c664c5 100644 --- a/server/routes/documents.js +++ b/server/routes/documents.js @@ -57,7 +57,7 @@ module.exports = function documentsRoute (dbPool) { } // If attached to a comment, also check view.comments permission - if (appealDoc.comment_id !== 0) { + if (Number(appealDoc.comment_id) !== 0) { const canViewComments = acl.hasServerPermission(appeal.server_id, 'player.appeals', 'view.comments') if (!canViewComments) { ctx.status = 403 From 63dd324878ea1f1a1eef7be6a979bffaa98d849a Mon Sep 17 00:00:00 2001 From: Spagles <106791090+Spagles@users.noreply.github.com> Date: Thu, 30 Jul 2026 02:44:28 +0000 Subject: [PATCH 7/7] fix: ensure comment_id is converted to a number before comparison in appeal links --- server/graphql/resolvers/scalars/document.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/graphql/resolvers/scalars/document.js b/server/graphql/resolvers/scalars/document.js index 83f7e1b92..e91169661 100644 --- a/server/graphql/resolvers/scalars/document.js +++ b/server/graphql/resolvers/scalars/document.js @@ -51,7 +51,7 @@ module.exports = { .select('a.id as appeal_id', 'a.server_id', 'ad.comment_id') for (const link of appealLinks) { - if (link.comment_id === 0) { + if (Number(link.comment_id) === 0) { usages.push({ type: 'appeal', id: String(link.appeal_id),