export const PORTAL_ACCESS_CLASS = Object.freeze({ PUBLIC: 'public', OPTIONAL_USER: 'optional-user', AUTHENTICATED: 'authenticated', INTERNAL: 'internal', ROUTE_AUTHORIZED: 'route-authorized', }); export const PORTAL_ACCESS_POLICY_MODE = Object.freeze({ OFF: 'off', SHADOW: 'shadow', }); export const PORTAL_ACCESS_ENFORCEMENT_GROUPS = Object.freeze([ 'legacy-page-data', 'page-data-public', 'plaza-optional-user', ]); const DEFAULT_SHADOW_LOG_INTERVAL_MS = 60_000; const MIN_SHADOW_LOG_INTERVAL_MS = 1_000; const MAX_SHADOW_LOG_INTERVAL_MS = 60 * 60_000; function freezeRule(rule) { return Object.freeze({ ...rule, paths: rule.paths ? Object.freeze([...rule.paths]) : undefined, methods: rule.methods ? Object.freeze([...rule.methods]) : undefined, }); } export const PORTAL_STATIC_ACCESS_RULES = Object.freeze([ freezeRule({ id: 'runtime-status', accessClass: PORTAL_ACCESS_CLASS.PUBLIC, legacyDirectBypass: true, paths: ['/status', '/runtime/status'], }), freezeRule({ id: 'blocked-words', accessClass: PORTAL_ACCESS_CLASS.PUBLIC, legacyDirectBypass: true, paths: ['/config/blocked-words'], }), freezeRule({ id: 'internal-agent', accessClass: PORTAL_ACCESS_CLASS.INTERNAL, legacyDirectBypass: true, pathPrefix: '/internal/agent/', }), freezeRule({ id: 'internal-deep-search-llm', accessClass: PORTAL_ACCESS_CLASS.INTERNAL, legacyDirectBypass: true, paths: ['/internal/deep-search/llm'], }), freezeRule({ id: 'mindspace-agent-callback', accessClass: PORTAL_ACCESS_CLASS.INTERNAL, legacyDirectBypass: true, paths: [ '/agent/mindspace_page_patch', '/agent/mindspace_asset_delete', '/agent/mindspace_asset_download', '/agent/mindspace_image_generate', ], }), freezeRule({ id: 'image-make-runtime-config', accessClass: PORTAL_ACCESS_CLASS.INTERNAL, legacyDirectBypass: true, paths: ['/internal/image-make/runtime-config'], }), freezeRule({ id: 'mindspace-asset-download', accessClass: PORTAL_ACCESS_CLASS.ROUTE_AUTHORIZED, legacyDirectBypass: true, methods: ['GET'], pathPattern: /^\/mindspace\/v1\/assets\/[^/]+\/download$/, }), freezeRule({ id: 'plaza-events', policyGroup: 'plaza-optional-user', accessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER, methods: ['POST'], paths: ['/plaza/v1/events'], }), freezeRule({ id: 'plaza-discovery', policyGroup: 'plaza-optional-user', accessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER, methods: ['GET'], paths: ['/plaza/v1/feed', '/plaza/v1/categories', '/plaza/v1/seo/sitemap'], }), freezeRule({ id: 'plaza-post-detail', policyGroup: 'plaza-optional-user', accessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER, methods: ['GET'], pathPattern: /^\/plaza\/v1\/posts\/[^/]+$/, }), freezeRule({ id: 'plaza-post-comments', policyGroup: 'plaza-optional-user', accessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER, methods: ['GET'], pathPattern: /^\/plaza\/v1\/posts\/[^/]+\/comments$/, }), freezeRule({ id: 'plaza-user-profile', policyGroup: 'plaza-optional-user', accessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER, methods: ['GET'], pathPattern: /^\/plaza\/v1\/users\/[^/]+$/, }), freezeRule({ id: 'plaza-user-posts', policyGroup: 'plaza-optional-user', accessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER, methods: ['GET'], pathPattern: /^\/plaza\/v1\/users\/[^/]+\/posts$/, }), ]); function normalizeRequest({ path, method } = {}) { return { path: String(path ?? ''), method: String(method ?? 'GET').trim().toUpperCase() || 'GET', }; } function ruleMatches(rule, request) { if (rule.methods && !rule.methods.includes(request.method)) return false; if (rule.paths?.includes(request.path)) return true; if (rule.pathPrefix && request.path.startsWith(rule.pathPrefix)) return true; return Boolean(rule.pathPattern?.test(request.path)); } export function findPortalStaticAccessRule(requestInput) { const request = normalizeRequest(requestInput); return PORTAL_STATIC_ACCESS_RULES.find((rule) => ruleMatches(rule, request)) ?? null; } export function resolvePortalAccessPolicyMode(env = process.env) { const raw = String(env.MEMIND_PORTAL_ACCESS_POLICY_MODE ?? '').trim().toLowerCase(); return raw === PORTAL_ACCESS_POLICY_MODE.SHADOW ? PORTAL_ACCESS_POLICY_MODE.SHADOW : PORTAL_ACCESS_POLICY_MODE.OFF; } export function resolvePortalAccessShadowLogIntervalMs(env = process.env) { const parsed = Number(env.MEMIND_PORTAL_ACCESS_SHADOW_LOG_INTERVAL_MS); if (!Number.isFinite(parsed)) return DEFAULT_SHADOW_LOG_INTERVAL_MS; return Math.min( MAX_SHADOW_LOG_INTERVAL_MS, Math.max(MIN_SHADOW_LOG_INTERVAL_MS, Math.trunc(parsed)), ); } function isExplicitlyEnabled(value) { return String(value ?? '').trim() === '1'; } export function resolvePortalAccessEnforcementConfig(env = process.env) { const masterEnabled = isExplicitlyEnabled( env.MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED, ); const killSwitch = isExplicitlyEnabled( env.MEMIND_PORTAL_ACCESS_POLICY_KILL_SWITCH, ); const requestedGroups = [ ...new Set( String(env.MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS ?? '') .split(',') .map((group) => group.trim()) .filter(Boolean), ), ]; const unknownGroups = requestedGroups.filter( (group) => !PORTAL_ACCESS_ENFORCEMENT_GROUPS.includes(group), ); if (masterEnabled && unknownGroups.length > 0) { throw new Error( `Unknown Portal access enforcement group(s): ${unknownGroups.join(', ')}`, ); } const activeGroups = masterEnabled && !killSwitch ? requestedGroups.filter((group) => PORTAL_ACCESS_ENFORCEMENT_GROUPS.includes(group)) : []; return Object.freeze({ masterEnabled, killSwitch, enabled: activeGroups.length > 0, requestedGroups: Object.freeze(requestedGroups), activeGroups: Object.freeze(activeGroups), unknownGroups: Object.freeze(unknownGroups), }); } export function isPortalLegacyDirectGlobalAuthBypass(path, method) { return Boolean( findPortalStaticAccessRule({ path, method })?.legacyDirectBypass, ); } export function isPortalPlazaOptionalUserPath(path, method) { return ( findPortalStaticAccessRule({ path, method })?.policyGroup === 'plaza-optional-user' ); } export function resolvePortalApiAccess( requestInput, { isPageDataPublicPath = () => false, isLegacyPageDataApiPath = () => false, } = {}, ) { const request = normalizeRequest(requestInput); const staticRule = findPortalStaticAccessRule(request); if (staticRule) { return { accessClass: staticRule.accessClass, ruleId: staticRule.id, policyGroup: staticRule.policyGroup ?? staticRule.id, }; } if (isPageDataPublicPath(request.path, request.method)) { return { accessClass: PORTAL_ACCESS_CLASS.ROUTE_AUTHORIZED, ruleId: 'page-data-public', policyGroup: 'page-data-public', }; } if (isLegacyPageDataApiPath(request.path)) { return { accessClass: PORTAL_ACCESS_CLASS.PUBLIC, ruleId: 'legacy-page-data', policyGroup: 'legacy-page-data', }; } return { accessClass: PORTAL_ACCESS_CLASS.AUTHENTICATED, ruleId: 'authenticated-default', policyGroup: 'authenticated-default', }; } export function classifyPortalApiAccess(requestInput, predicates = {}) { return resolvePortalApiAccess(requestInput, predicates).accessClass; } export function portalAccessBypassesGlobalAuth(accessClass) { return accessClass !== PORTAL_ACCESS_CLASS.AUTHENTICATED; } export function resolvePortalAccessEnforcementDecision( requestInput, predicates, config, ) { const proposed = resolvePortalApiAccess(requestInput, predicates); const activeGroups = config?.activeGroups ?? []; const selected = Boolean(config?.enabled) && activeGroups.includes(proposed.policyGroup); let reason = 'group-disabled'; if (config?.killSwitch) reason = 'kill-switch'; else if (!config?.masterEnabled) reason = 'master-disabled'; else if (selected) reason = 'group-enabled'; return { selected, bypassGlobalAuth: selected && portalAccessBypassesGlobalAuth(proposed.accessClass), reason, proposedAccessClass: proposed.accessClass, proposedRuleId: proposed.ruleId, policyGroup: proposed.policyGroup, }; } export function shouldOverridePortalLegacyGlobalAuth( decision, currentGlobalAuthBypass, ) { return ( Boolean(decision?.selected) && Boolean(decision?.bypassGlobalAuth) && !Boolean(currentGlobalAuthBypass) ); } export function comparePortalAccessPolicyShadow( requestInput, { multiUserEnabled = false, isPageDataPublicPath = () => false, isLegacyPageDataApiPath = () => false, } = {}, ) { const request = normalizeRequest(requestInput); const plazaOptionalUser = isPortalPlazaOptionalUserPath(request.path, request.method); const pageDataRouteAuthorized = isPageDataPublicPath(request.path, request.method); const legacyPageDataPublic = isLegacyPageDataApiPath(request.path); const currentGlobalAuthBypass = isPortalLegacyDirectGlobalAuthBypass(request.path, request.method) || ( Boolean(multiUserEnabled) && (plazaOptionalUser || pageDataRouteAuthorized || legacyPageDataPublic) ); const proposed = resolvePortalApiAccess(request, { isPageDataPublicPath, isLegacyPageDataApiPath, }); const proposedGlobalAuthBypass = portalAccessBypassesGlobalAuth(proposed.accessClass); return { path: request.path, method: request.method, proposedAccessClass: proposed.accessClass, proposedRuleId: proposed.ruleId, policyGroup: proposed.policyGroup, currentGlobalAuthBypass, proposedGlobalAuthBypass, mismatch: currentGlobalAuthBypass !== proposedGlobalAuthBypass, }; } export function assessPortalAccessMigrationReadiness( requests, { knownMismatchPolicyGroups = [], ...comparisonOptions } = {}, ) { const knownGroups = new Set(knownMismatchPolicyGroups); const comparisons = requests.map((request) => comparePortalAccessPolicyShadow(request, comparisonOptions)); const mismatches = comparisons.filter((comparison) => comparison.mismatch); const knownMismatches = mismatches.filter((comparison) => knownGroups.has(comparison.policyGroup)); const unexpectedMismatches = mismatches.filter((comparison) => !knownGroups.has(comparison.policyGroup)); return { contractReady: unexpectedMismatches.length === 0, safeForBehaviorSwitch: mismatches.length === 0, total: comparisons.length, aligned: comparisons.length - mismatches.length, knownMismatches, unexpectedMismatches, }; } export function createPortalAccessShadowReporter({ intervalMs = DEFAULT_SHADOW_LOG_INTERVAL_MS, now = Date.now, emit = () => {}, } = {}) { const effectiveIntervalMs = Math.min( MAX_SHADOW_LOG_INTERVAL_MS, Math.max(MIN_SHADOW_LOG_INTERVAL_MS, Math.trunc(Number(intervalMs) || 0)), ); const counters = new Map(); function record(event) { if (!event?.mismatch) return null; const key = [ event.authMode ?? 'unknown', event.method ?? 'GET', event.policyGroup ?? event.proposedRuleId ?? 'unknown', ].join(':'); const timestamp = Number(now()); const counter = counters.get(key) ?? { key, totalOccurrences: 0, suppressedSinceLast: 0, lastEmittedAt: null, }; counter.totalOccurrences += 1; const shouldEmit = counter.lastEmittedAt === null || timestamp - counter.lastEmittedAt >= effectiveIntervalMs; if (!shouldEmit) { counter.suppressedSinceLast += 1; counters.set(key, counter); return null; } const payload = { ...event, occurrences: counter.totalOccurrences, suppressedSinceLast: counter.suppressedSinceLast, shadowLogIntervalMs: effectiveIntervalMs, }; counter.lastEmittedAt = timestamp; counter.suppressedSinceLast = 0; counters.set(key, counter); emit(payload); return payload; } function snapshot() { return [...counters.values()].map((counter) => ({ ...counter })); } return Object.freeze({ record, snapshot }); }