mirror of
https://github.com/docmost/docmost.git
synced 2026-05-16 14:14:06 +08:00
restriction info
This commit is contained in:
@@ -91,7 +91,7 @@ export class PagePermissionController {
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('list')
|
||||
@Post('members')
|
||||
async getPagePermissions(
|
||||
@Body() dto: PageIdDto,
|
||||
@Body() pagination: PaginationOptions,
|
||||
@@ -103,6 +103,15 @@ export class PagePermissionController {
|
||||
pagination,
|
||||
);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('info')
|
||||
async getPageRestrictionInfo(
|
||||
@Body() dto: PageIdDto,
|
||||
@AuthUser() user: User,
|
||||
) {
|
||||
return this.pagePermissionService.getPageRestrictionInfo(dto.pageId, user);
|
||||
}
|
||||
}
|
||||
|
||||
function validateMemberIds(dto: { userIds?: string[]; groupIds?: string[] }) {
|
||||
|
||||
@@ -26,6 +26,18 @@ import {
|
||||
SpaceCaslSubject,
|
||||
} from '../../casl/interfaces/space-ability.type';
|
||||
|
||||
export type PageRestrictionInfo = {
|
||||
id: string;
|
||||
title: string;
|
||||
hasDirectRestriction: boolean;
|
||||
hasInheritedRestriction: boolean;
|
||||
userAccess: {
|
||||
canView: boolean;
|
||||
canEdit: boolean;
|
||||
canManage: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PagePermissionService {
|
||||
constructor(
|
||||
@@ -342,6 +354,86 @@ export class PagePermissionService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page restriction info for the current user.
|
||||
*
|
||||
* Security: User must be a space member. Returns 404 for pages the user cannot view
|
||||
* to avoid leaking existence of restricted pages.
|
||||
*/
|
||||
async getPageRestrictionInfo(
|
||||
pageId: string,
|
||||
authUser: User,
|
||||
): Promise<PageRestrictionInfo> {
|
||||
const page = await this.pageRepo.findById(pageId);
|
||||
if (!page) {
|
||||
throw new NotFoundException('Page not found');
|
||||
}
|
||||
|
||||
const ability = await this.spaceAbility.createForUser(
|
||||
authUser,
|
||||
page.spaceId,
|
||||
);
|
||||
|
||||
if (ability.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
const [hasDirectRestriction, hasAnyRestriction, canView, canEdit] =
|
||||
await Promise.all([
|
||||
this.pagePermissionRepo.findPageAccessByPageId(pageId).then((r) => !!r),
|
||||
this.pagePermissionRepo.hasRestrictedAncestor(pageId),
|
||||
this.canViewPage(authUser.id, pageId),
|
||||
this.canEditPage(authUser.id, pageId),
|
||||
]);
|
||||
|
||||
// Security: return 404 to avoid leaking existence of restricted pages
|
||||
if (!canView) {
|
||||
throw new NotFoundException('Page not found');
|
||||
}
|
||||
|
||||
const hasInheritedRestriction = hasAnyRestriction && !hasDirectRestriction;
|
||||
|
||||
// Determine if user can manage permissions
|
||||
const canManage = this.computeCanManage(ability, canEdit, canView);
|
||||
|
||||
return {
|
||||
id: page.id,
|
||||
title: page.title,
|
||||
hasDirectRestriction,
|
||||
hasInheritedRestriction,
|
||||
userAccess: {
|
||||
canView,
|
||||
canEdit,
|
||||
canManage,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute if user can manage page permissions based on precomputed access values.
|
||||
* Mirrors validateWriteAccess logic without throwing.
|
||||
*/
|
||||
private computeCanManage(
|
||||
ability: Awaited<ReturnType<SpaceAbilityFactory['createForUser']>>,
|
||||
canEdit: boolean,
|
||||
canView: boolean,
|
||||
): boolean {
|
||||
if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (canEdit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isSpaceAdmin = ability.can(
|
||||
SpaceCaslAction.Manage,
|
||||
SpaceCaslSubject.Page,
|
||||
);
|
||||
|
||||
return isSpaceAdmin && canView;
|
||||
}
|
||||
|
||||
async validateLastWriter(pageAccessId: string): Promise<void> {
|
||||
const writerCount =
|
||||
await this.pagePermissionRepo.countWritersByPageAccessId(pageAccessId);
|
||||
|
||||
Reference in New Issue
Block a user