From 27e858d760a6f9fc82538f41cb54b453468d6360 Mon Sep 17 00:00:00 2001 From: Juan Castro Date: Tue, 15 Sep 2026 18:57:50 -0400 Subject: [PATCH] fix: gate the group-share index delete on authority, qualify the live filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-ups on #3872: deleteActiveGroup ran even when every canManagePermission check was denied, deleting the index row while the team's grant survived — unlisted live access, the class this PR exists to remove. And memberIdsAmong's join left deleted_at unqualified, which only works while jct_user_group has no such column; #live now takes an alias. --- src/backend/services/share/ShareService.ts | 4 ++++ src/backend/stores/team/TeamStore.ts | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/backend/services/share/ShareService.ts b/src/backend/services/share/ShareService.ts index f96324935..414dc8931 100644 --- a/src/backend/services/share/ShareService.ts +++ b/src/backend/services/share/ShareService.ts @@ -1456,6 +1456,7 @@ export class ShareService extends PuterService { Number(row.holder_group_id), ); if (!node || !team) continue; + let authorized = false; for (const permission of entryPermissions(node.uuid)) { if ( !(await this.services.permission.canManagePermission( @@ -1465,6 +1466,7 @@ export class ShareService extends PuterService { ) { continue; } + authorized = true; if ( await this.services.permission.revokeUserGroupPermission( actor, @@ -1477,6 +1479,8 @@ export class ShareService extends PuterService { revoked++; } } + // Gated as the user path is: a surviving grant keeps its index row. + if (!authorized) continue; await this.stores.share.deleteActiveGroup({ holderGroupId: Number(row.holder_group_id), fsentryId: node.id, diff --git a/src/backend/stores/team/TeamStore.ts b/src/backend/stores/team/TeamStore.ts index f30f52026..a7c7f45ef 100644 --- a/src/backend/stores/team/TeamStore.ts +++ b/src/backend/stores/team/TeamStore.ts @@ -178,8 +178,9 @@ export class TeamStore extends PuterStore { } /** Makes the seeded `kind IS NULL` groups unreachable, not merely absent. */ - #live(): string { - return '`kind` = ? AND `deleted_at` IS NULL'; + #live(alias = ''): string { + const prefix = alias ? `${alias}.` : ''; + return `${prefix}\`kind\` = ? AND ${prefix}\`deleted_at\` IS NULL`; } // -- Reads -------------------------------------------------------- @@ -380,7 +381,7 @@ export class TeamStore extends PuterStore { const rows = (await this.clients.db.read( 'SELECT ug.`user_id` FROM `jct_user_group` ug ' + 'JOIN `group` g ON g.`id` = ug.`group_id` ' + - `WHERE g.\`uid\` = ? AND g.${this.#live()} ` + + `WHERE g.\`uid\` = ? AND ${this.#live('g')} ` + `AND ug.\`user_id\` IN (${ids.map(() => '?').join(', ')})`, [teamUid, TEAM_KIND, ...ids], )) as { user_id: number }[];