harden(page-templates): throttle lookup/toggle; workspace-scope ref writes
Release-cycle review: POST /pages/template/lookup had only JwtAuthGuard and the embed depth cap was client-only, so a scripted client could drive heavy full-content fan-out (access control holds per-id, but a cost/DoS gap). And page_template_references rows were written for any sourcePageId with no workspace check at sync time (no leak today since lookup re-checks access, but the graph could accumulate cross-space rows). - Apply the standard per-user throttler (PAGE_TEMPLATE_THROTTLER, 30/min) to /pages/template/lookup and /pages/toggle-template (mirrors ai-chat); auth + the toggle's validateCanEdit CASL are unchanged. - syncPageTemplateReferences / insertTemplateReferencesForPages now restrict inserts to in-workspace source ids (filterInWorkspaceSourceIds, workspace + not-deleted scoped, trx-aware) and still delete stale out-of-workspace rows (self-heal). SECURITY comment: the ref table is NOT access-filtered; every consumer must permission-filter at read time (as lookupTemplate does). - Tests: lookup access exercises the REAL filterViewerAccessiblePageIds (no_access / cross-workspace excluded / accessible+comment-stripped / <=50); toggle controller CASL (cannot-edit -> Forbidden, flag not flipped); ref-sync excludes cross-workspace and keeps in-workspace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,11 @@ import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'
|
||||
import { EnvironmentService } from '../environment/environment.service';
|
||||
import { EnvironmentModule } from '../environment/environment.module';
|
||||
import { parseRedisUrl } from '../../common/helpers';
|
||||
import { AUTH_THROTTLER, AI_CHAT_THROTTLER } from './throttler-names';
|
||||
import {
|
||||
AUTH_THROTTLER,
|
||||
AI_CHAT_THROTTLER,
|
||||
PAGE_TEMPLATE_THROTTLER,
|
||||
} from './throttler-names';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
@Module({
|
||||
@@ -18,6 +22,11 @@ import Redis from 'ioredis';
|
||||
throttlers: [
|
||||
{ name: AUTH_THROTTLER, ttl: 60_000, limit: 10 },
|
||||
{ name: AI_CHAT_THROTTLER, ttl: 60_000, limit: 25 },
|
||||
// Whole-page template lookup returns full ProseMirror docs for up
|
||||
// to 50 ids per call and the embed depth cap is client-side only, so
|
||||
// a scripted client could drive heavy content fan-out. 30 req/min
|
||||
// per user is plenty for legitimate render-time batched lookups.
|
||||
{ name: PAGE_TEMPLATE_THROTTLER, ttl: 60_000, limit: 30 },
|
||||
],
|
||||
errorMessage: 'Too many requests',
|
||||
storage: new ThrottlerStorageRedisService(
|
||||
|
||||
Reference in New Issue
Block a user