Share permissions

This commit is contained in:
Philipinho
2026-01-02 03:45:39 +00:00
parent 8eb698648e
commit d17efaf26e
3 changed files with 54 additions and 1 deletions
@@ -26,6 +26,7 @@ import {
UpdateShareDto,
} from './dto/share.dto';
import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo';
import { PageAccessService } from '../page-access/page-access.service';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { Public } from '../../common/decorators/public.decorator';
@@ -42,6 +43,7 @@ export class ShareController {
private readonly spaceAbility: SpaceAbilityFactory,
private readonly shareRepo: ShareRepo,
private readonly pageRepo: PageRepo,
private readonly pagePermissionRepo: PagePermissionRepo,
private readonly pageAccessService: PageAccessService,
private readonly environmentService: EnvironmentService,
) {}
@@ -126,8 +128,17 @@ export class ShareController {
}
// User must be able to edit the page to create a share
//TODO: i dont think this is neccessary if we prevent restricted pages from getting shared
// rather, use space level permission and workspace/space level sharing restriction
await this.pageAccessService.validateCanEdit(page, user);
// Prevent sharing restricted pages
const isRestricted =
await this.pagePermissionRepo.hasRestrictedAncestor(page.id);
if (isRestricted) {
throw new BadRequestException('Cannot share a restricted page');
}
return this.shareService.createShare({
page,
authUserId: user.id,
+16 -1
View File
@@ -19,6 +19,7 @@ import {
} from '../../common/helpers/prosemirror/utils';
import { Node } from '@tiptap/pm/model';
import { ShareRepo } from '@docmost/db/repos/share/share.repo';
import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo';
import { updateAttachmentAttr } from './share.util';
import { Page } from '@docmost/db/types/entity.types';
import { validate as isValidUUID } from 'uuid';
@@ -31,6 +32,7 @@ export class ShareService {
constructor(
private readonly shareRepo: ShareRepo,
private readonly pageRepo: PageRepo,
private readonly pagePermissionRepo: PagePermissionRepo,
@InjectKysely() private readonly db: KyselyDB,
private readonly tokenService: TokenService,
) {}
@@ -46,7 +48,13 @@ export class ShareService {
includeContent: false,
});
return { share, pageTree: pageList };
// Filter out restricted pages and their descendants
const restrictedIds =
await this.pagePermissionRepo.getRestrictedSubtreeIds(share.pageId);
const restrictedSet = new Set(restrictedIds);
const filteredPages = pageList.filter((page) => !restrictedSet.has(page.id));
return { share, pageTree: filteredPages };
} else {
return { share, pageTree: [] };
}
@@ -112,6 +120,13 @@ export class ShareService {
throw new NotFoundException('Shared page not found');
}
// Block access to restricted pages
const isRestricted =
await this.pagePermissionRepo.hasRestrictedAncestor(page.id);
if (isRestricted) {
throw new NotFoundException('Shared page not found');
}
page.content = await this.updatePublicAttachments(page);
return { page, share };
@@ -624,4 +624,31 @@ export class PagePermissionRepo {
return results.map((r) => r.parentPageId);
}
/**
* Get all page IDs within a subtree that are restricted OR are descendants of restricted pages.
* Used to filter pages from public shares - if a page is restricted, it and all its
* children should be hidden.
*/
async getRestrictedSubtreeIds(rootPageId: string): Promise<string[]> {
const results = await this.db
.selectFrom('pageHierarchy as subtree')
.where('subtree.ancestorId', '=', rootPageId)
.innerJoin(
(eb) =>
eb
.selectFrom('pageHierarchy as inner')
.innerJoin('pageAccess', 'pageAccess.pageId', 'inner.ancestorId')
.select('inner.descendantId as restrictedDescendant')
.distinct()
.as('restricted'),
(join) =>
join.onRef('restricted.restrictedDescendant', '=', 'subtree.descendantId'),
)
.select('subtree.descendantId')
.distinct()
.execute();
return results.map((r) => r.descendantId);
}
}