fix(html-embed): strip embeds at serve time on authenticated read paths (#28)
Completes the workspace htmlEmbed kill-switch. The public-share path already strips at serve time when the toggle is OFF, but the authenticated read paths (/info and /history/info) returned page/history content with embeds intact, so a disabled feature kept executing for in-workspace view-only viewers until the page was next saved. Now both paths resolve the workspace toggle and run stripHtmlEmbedNodes when it's OFF (fail-closed on a missing workspace), before any markdown/html format conversion. Admin-authored content only — completeness, not privilege escalation. Injects WorkspaceRepo into PageController. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,11 @@ import {
|
||||
} from '../casl/interfaces/space-ability.type';
|
||||
import SpaceAbilityFactory from '../casl/abilities/space-ability.factory';
|
||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
|
||||
import {
|
||||
isHtmlEmbedFeatureEnabled,
|
||||
stripHtmlEmbedNodes,
|
||||
} from '../../common/helpers/prosemirror/html-embed.util';
|
||||
import { RecentPageDto } from './dto/recent-page.dto';
|
||||
import { CreatedByUserDto } from './dto/created-by-user.dto';
|
||||
import { DuplicatePageDto } from './dto/duplicate-page.dto';
|
||||
@@ -63,6 +68,7 @@ export class PageController {
|
||||
constructor(
|
||||
private readonly pageService: PageService,
|
||||
private readonly pageRepo: PageRepo,
|
||||
private readonly workspaceRepo: WorkspaceRepo,
|
||||
private readonly pageHistoryService: PageHistoryService,
|
||||
private readonly spaceAbility: SpaceAbilityFactory,
|
||||
private readonly pageAccessService: PageAccessService,
|
||||
@@ -92,6 +98,18 @@ export class PageController {
|
||||
|
||||
const permissions = { canEdit, hasRestriction };
|
||||
|
||||
if (page.content) {
|
||||
const workspace = await this.workspaceRepo.findById(page.workspaceId);
|
||||
if (!isHtmlEmbedFeatureEnabled(workspace?.settings)) {
|
||||
// Kill-switch: when the workspace feature is OFF, never serve raw
|
||||
// htmlEmbed nodes on the read path (mirrors the public-share strip),
|
||||
// so disabling the feature is an immediate, total kill-switch and not
|
||||
// dependent on the page being re-saved. Admin-authored content only.
|
||||
// Fail-closed: a missing workspace resolves to OFF and is stripped.
|
||||
page.content = stripHtmlEmbedNodes(page.content) as any;
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.format && dto.format !== 'json' && page.content) {
|
||||
const contentOutput =
|
||||
dto.format === 'markdown'
|
||||
@@ -536,6 +554,16 @@ export class PageController {
|
||||
|
||||
await this.pageAccessService.validateCanView(page, user);
|
||||
|
||||
if (history.content) {
|
||||
const workspace = await this.workspaceRepo.findById(page.workspaceId);
|
||||
if (!isHtmlEmbedFeatureEnabled(workspace?.settings)) {
|
||||
// Kill-switch: history snapshots are an authenticated read path too, so
|
||||
// strip htmlEmbed when the workspace feature is OFF (same as /info and
|
||||
// the public-share path). Fail-closed on a missing workspace.
|
||||
history.content = stripHtmlEmbedNodes(history.content) as any;
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user