From f24b009472c371d59ed2502e701514e5aa6033bc Mon Sep 17 00:00:00 2001 From: Alexey Zinoviev Date: Sun, 15 Feb 2026 01:17:40 +0400 Subject: [PATCH] Qfix: allow notifications for guests with collab security Signed-off-by: Alexey Zinoviev --- foundations/core/packages/core/src/classes.ts | 1 + .../packages/middleware/src/spaceSecurity.ts | 28 +++++++++++++++---- .../server/packages/postgres/src/storage.ts | 13 ++++++--- models/activity/src/notification.ts | 6 ++-- models/core/src/core.ts | 1 + 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/foundations/core/packages/core/src/classes.ts b/foundations/core/packages/core/src/classes.ts index 579bfaaba93..4307dbf5025 100644 --- a/foundations/core/packages/core/src/classes.ts +++ b/foundations/core/packages/core/src/classes.ts @@ -947,6 +947,7 @@ export interface ClassCollaborators extends Doc { allFields?: boolean // for all (PersonId | Ref | PersonId[] | Ref[]) attributes fields: (keyof T)[] // PersonId | Ref | PersonId[] | Ref[] provideSecurity?: boolean // If true, will provide security for collaborators + provideAttachedSecurity?: boolean // If true, will provide security for collaborators of attached doc } export interface Collaborator extends AttachedDoc { diff --git a/foundations/server/packages/middleware/src/spaceSecurity.ts b/foundations/server/packages/middleware/src/spaceSecurity.ts index 09279c19a17..28f3098e9b3 100644 --- a/foundations/server/packages/middleware/src/spaceSecurity.ts +++ b/foundations/server/packages/middleware/src/spaceSecurity.ts @@ -484,10 +484,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar const space = this.spacesMap.get(tx.objectSpace) if (space === undefined) return undefined - // For all other spaces broadcast to space members + guests that are collaborators for objects with collab security enabled - let collabTargets: AccountUuid[] = [] - const collabSec = getClassCollaborators(this.context.modelDb, this.context.hierarchy, cud.objectClass) - if (collabSec?.provideSecurity === true) { + const getCollabTargets = async (_id: Ref): Promise => { const guests = new Set() for (const val of ctx.contextData.socialStringsToUsers.values()) { if ([AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(val.role)) { @@ -495,11 +492,30 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar } } const collaboratorObjs = (await this.next?.findAll(ctx, core.class.Collaborator, { - attachedTo: cud.objectId + attachedTo: _id })) as Collaborator[] - collabTargets = collaboratorObjs.map((it) => it.collaborator).filter((it) => guests.has(it)) + return collaboratorObjs.map((it) => it.collaborator).filter((it) => guests.has(it)) + } + + // For all other spaces broadcast to space members + // + guests that are collaborators for objects with collab security enabled + // + guests that are collaborators for attached objects with collab security enabled + let collabTargets: AccountUuid[] = [] + const collabSec = getClassCollaborators(this.context.modelDb, this.context.hierarchy, cud.objectClass) + if (collabSec?.provideSecurity === true) { + collabTargets = await getCollabTargets(cud.objectId) + } else if (cud.attachedTo != null && cud.attachedToClass != null) { + const attachedCollabSec = getClassCollaborators( + this.context.modelDb, + this.context.hierarchy, + cud.attachedToClass + ) + if (attachedCollabSec?.provideSecurity === true) { + collabTargets = await getCollabTargets(cud.attachedTo) + } } + const spaceTargets = space.members.length === 0 ? [] : this.getTargets(space?.members) const target = [...collabTargets, ...spaceTargets] diff --git a/foundations/server/packages/postgres/src/storage.ts b/foundations/server/packages/postgres/src/storage.ts index b89cf5a838e..c2fec296227 100644 --- a/foundations/server/packages/postgres/src/storage.ts +++ b/foundations/server/packages/postgres/src/storage.ts @@ -638,11 +638,16 @@ abstract class PostgresAdapterBase implements DbAdapter { const res = `EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_SPACE)} sec WHERE sec._id = ${domain}.${key} AND sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND ${q})` const collabSec = getClassCollaborators(this.modelDb, this.hierarchy, _class) - if (collabSec?.provideSecurity === true && [AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(acc.role)) { - const collab = `OR EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_COLLABORATOR)} collab_sec WHERE collab_sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND collab_sec."attachedTo" = ${domain}._id AND collab_sec.collaborator = '${acc.uuid}')` - return `AND (${res} ${collab})` + let collabRes = '' + if ([AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(acc.role)) { + if (collabSec?.provideSecurity === true) { + collabRes += ` OR EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_COLLABORATOR)} collab_sec WHERE collab_sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND collab_sec."attachedTo" = ${domain}._id AND collab_sec.collaborator = '${acc.uuid}')` + } + if (collabSec?.provideAttachedSecurity === true) { + collabRes += ` OR EXISTS (SELECT 1 FROM ${translateDomain(DOMAIN_COLLABORATOR)} collab_sec WHERE collab_sec."workspaceId" = ${vars.add(this.workspaceId, '::uuid')} AND collab_sec."attachedTo" = ${domain}."attachedTo" AND collab_sec.collaborator = '${acc.uuid}')` + } } - return `AND (${res})` + return `AND (${res}${collabRes})` } } } diff --git a/models/activity/src/notification.ts b/models/activity/src/notification.ts index c7c05922ce5..0370c131b8a 100644 --- a/models/activity/src/notification.ts +++ b/models/activity/src/notification.ts @@ -44,12 +44,14 @@ export function buildNotifications (builder: Builder): void { builder.createDoc>(core.class.ClassCollaborators, core.space.Model, { attachedTo: activity.class.ActivityMessage, - fields: ['createdBy', 'repliedPersons'] + fields: ['createdBy', 'repliedPersons'], + provideAttachedSecurity: true }) builder.createDoc>(core.class.ClassCollaborators, core.space.Model, { attachedTo: activity.class.DocUpdateMessage, - fields: ['createdBy', 'repliedPersons'] + fields: ['createdBy', 'repliedPersons'], + provideAttachedSecurity: true }) builder.mixin(activity.class.ActivityMessage, core.class.Class, notification.mixin.NotificationContextPresenter, { diff --git a/models/core/src/core.ts b/models/core/src/core.ts index 29fa90e9c77..a2ffa2eef82 100644 --- a/models/core/src/core.ts +++ b/models/core/src/core.ts @@ -416,6 +416,7 @@ export class TClassCollaborators extends TDoc implements ClassCollaborators allFields?: boolean fields!: (keyof Doc)[] provideSecurity?: boolean + provideAttachedSecurity?: boolean } @Model(core.class.Collaborator, core.class.Doc, DOMAIN_COLLABORATOR)