From f83e74eb97efced07731c3c335309bfeb89dd7a7 Mon Sep 17 00:00:00 2001 From: Mohammad Elwan Date: Mon, 3 Aug 2026 12:16:28 +0200 Subject: [PATCH] fix : restrict public templates list and view to importable types --- backend/webserver/Socket.js | 30 +++++++++---------- backend/webserver/sockets/template.js | 5 ++++ .../templates/PublicTemplatesModal.vue | 3 ++ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/backend/webserver/Socket.js b/backend/webserver/Socket.js index 2b1eab34e..60c806bb7 100644 --- a/backend/webserver/Socket.js +++ b/backend/webserver/Socket.js @@ -510,28 +510,28 @@ module.exports = class Socket { let fullRowAccess = isPublicOrAdmin; if (!fullRowAccess) { - // --- Ownership: user always sees their own rows when table has userId --- - if (hasUserIdAttribute) { - rowVisibilityConditions.push({userId}); - } - - // --- Public rows: always visible regardless of ownership or access rights --- - if ('public' in model.getAttributes()) { - rowVisibilityConditions.push({public: true}); - } - - // --- User-level row filter --- + // --- User-level row filter (authoritative when present, e.g. template type rules) --- if (hasModelUserFilter) { - const userFilter = await model.getUserFilter(userId); + const userFilter = await model.getUserFilter(userId, isAdmin); if (Reflect.ownKeys(userFilter).length > 0) { rowVisibilityConditions.push(userFilter); } else { // getUserFilter returns {} → grants full row access (e.g. for admins) fullRowAccess = true; } - } else if (!hasUserIdAttribute && accessRights.length === 0) { - this.logger.warn("User with id " + userId + " requested table " + tableName + " without access rights"); - return {filter: allFilter, attributes: allAttributes, accessAllowed: false}; + } else { + // --- Ownership: user always sees their own rows when table has userId --- + if (hasUserIdAttribute) { + rowVisibilityConditions.push({userId}); + } + + // --- Public rows: always visible regardless of ownership or access rights --- + if ('public' in model.getAttributes()) { + rowVisibilityConditions.push({public: true}); + } else if (!hasUserIdAttribute && accessRights.length === 0) { + this.logger.warn("User with id " + userId + " requested table " + tableName + " without access rights"); + return {filter: allFilter, attributes: allAttributes, accessAllowed: false}; + } } // --- Access-map limitations (ORed with user filter conditions) --- diff --git a/backend/webserver/sockets/template.js b/backend/webserver/sockets/template.js index 5b8135a06..85742dd3b 100644 --- a/backend/webserver/sockets/template.js +++ b/backend/webserver/sockets/template.js @@ -92,10 +92,15 @@ class TemplateSocket extends Socket { const isOwner = template.userId === this.userId; const isPublicFromOthers = template.public === true && !isOwner; + const isAdmin = await this.isAdmin(); + const isEmailType = [1, 2, 3, 6, 7].includes(template.type); if (!isOwner && !isPublicFromOthers) { throw new Error("You can only view templates that you own or public templates from others"); } + if (!isAdmin && !isOwner && isEmailType) { + throw new Error("Access denied: Only administrators can view email templates"); + } const langRow = await this.models["template_content"].findOne({ where: { templateId: data.templateId, language: data.language, deleted: false }, diff --git a/frontend/src/components/dashboard/templates/PublicTemplatesModal.vue b/frontend/src/components/dashboard/templates/PublicTemplatesModal.vue index 455af5677..d2d3f2213 100644 --- a/frontend/src/components/dashboard/templates/PublicTemplatesModal.vue +++ b/frontend/src/components/dashboard/templates/PublicTemplatesModal.vue @@ -70,8 +70,11 @@ export default { .filter(t => t.userId === this.userId && !t.deleted); }, publicTemplates() { + const importableTypes = [4, 5]; + const isAdmin = this.$store.getters["auth/isAdmin"]; return this.$store.getters["table/template/getAll"] .filter(t => t.public && t.userId !== this.userId && !t.deleted) + .filter(t => isAdmin || importableTypes.includes(t.type)) .map(t => { const alreadyCopied = this.ownTemplates.some( own => own.sourceId === t.id