fix(ws): shrink restriction-cache TTL to bound the leak window (#53)
invalidateSpaceRestrictionCache has no callers because no restriction-mutation path exists yet (PagePermissionRepo mutators are uncalled; there is no restrict/grant/revoke endpoint), so the 30s spaceHasRestrictions cache could serve a stale 'no restrictions' verdict. Until a mutation endpoint exists to wire the direct invalidation, lower the TTL (30s -> 3s) to bound the worst-case window; the invalidation primitive is kept for that future endpoint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,24 +23,29 @@ export class WsService {
|
||||
}
|
||||
|
||||
// Drop the cached spaceHasRestrictions verdict for a space. spaceHasRestrictions
|
||||
// caches "does this space have ANY restricted page" for WS_CACHE_TTL_MS (30s),
|
||||
// and emitTreeEvent / emitCommentEvent take a room-wide fast path when it is
|
||||
// false. The FIRST time a space gains a restriction (or loses its last one)
|
||||
// this cached verdict goes stale for up to the TTL, during which a title/icon-
|
||||
// bearing tree payload could fan out to the whole room. This MUST be called by
|
||||
// whatever code creates or removes a page's restriction (the page-access /
|
||||
// page-permission grant/revoke/restrict path), passing the affected page's
|
||||
// spaceId, so the next emit re-reads hasRestrictedPagesInSpace.
|
||||
// caches "does this space have ANY restricted page" for WS_CACHE_TTL_MS, and
|
||||
// emitTreeEvent / emitCommentEvent take a room-wide fast path when it is false.
|
||||
// The FIRST time a space gains a restriction (or loses its last one) this cached
|
||||
// verdict goes stale for up to the TTL, during which a title/icon-bearing tree
|
||||
// payload could fan out to the whole room. This MUST be called by whatever code
|
||||
// creates or removes a page's restriction (the page-access / page-permission
|
||||
// grant/revoke/restrict path), passing the affected page's spaceId, so the next
|
||||
// emit re-reads hasRestrictedPagesInSpace immediately instead of serving a
|
||||
// stale cached value.
|
||||
//
|
||||
// NOTE: on this branch there is no permission-mutation site to call this from —
|
||||
// the page-access/page-permission repo mutators (insertPageAccess /
|
||||
// insertPagePermissions / deletePagePermission* / updatePagePermissionRole)
|
||||
// have ZERO callers in apps/server/src; PageAccessService only validates access.
|
||||
// This primitive is kept (and tested) so that flow, when it lands, has the
|
||||
// correct hook to invalidate the cache.
|
||||
// Because there is nothing to wire the invalidation to yet, the documented
|
||||
// fallback was applied instead: WS_CACHE_TTL_MS was dropped from 30s to 3s (see
|
||||
// ws.utils.ts) to bound the worst-case stale-leak window. This primitive is kept
|
||||
// (and tested) so the restriction-mutation flow, when it lands, has the correct
|
||||
// hook to invalidate the cache.
|
||||
//
|
||||
// TODO: the future restriction-mutation endpoint (restrict/grant/revoke page
|
||||
// access) MUST call this with the affected page's spaceId.
|
||||
// access) MUST call this with the affected page's spaceId; once wired, the TTL
|
||||
// can be raised back to a higher value if desired.
|
||||
async invalidateSpaceRestrictionCache(spaceId: string): Promise<void> {
|
||||
await this.cacheManager.del(
|
||||
`${WS_SPACE_RESTRICTION_CACHE_PREFIX}${spaceId}`,
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
export const WS_CACHE_TTL_MS = 30_000;
|
||||
// TTL for the cached spaceHasRestrictions verdict (see WsService). This cache is
|
||||
// a read-side fast path: while it is `false`, emitTreeEvent/emitCommentEvent
|
||||
// broadcast page-bearing payloads to the WHOLE space room. If a space gains its
|
||||
// first restriction (or loses its last one), the verdict goes stale for up to
|
||||
// this TTL, during which a title/icon-bearing payload could fan out to
|
||||
// now-unauthorized sockets. The proper fix is to call
|
||||
// WsService.invalidateSpaceRestrictionCache(spaceId) from the restriction
|
||||
// mutation path — but on this branch no such mutation path exists yet (the
|
||||
// page-permission repo mutators have zero callers), so there is nothing to wire
|
||||
// the invalidation to. As the documented fallback, the TTL is kept short (3s)
|
||||
// to bound the worst-case leak window until that endpoint lands and the
|
||||
// invalidation can be wired directly.
|
||||
export const WS_CACHE_TTL_MS = 3_000;
|
||||
export const WS_SPACE_RESTRICTION_CACHE_PREFIX = 'ws:space-restrictions:';
|
||||
|
||||
export function getSpaceRoomName(spaceId: string): string {
|
||||
|
||||
Reference in New Issue
Block a user