444 lines
16 KiB
JavaScript
444 lines
16 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { isLegacyPageDataApiPath } from '../page-data-routes.mjs';
|
|
import { isPageDataPublicPath } from '../page-data-public-service.mjs';
|
|
import {
|
|
assessPortalAccessMigrationReadiness,
|
|
classifyPortalApiAccess,
|
|
comparePortalAccessPolicyShadow,
|
|
createPortalAccessShadowReporter,
|
|
findPortalStaticAccessRule,
|
|
isPortalLegacyDirectGlobalAuthBypass,
|
|
isPortalPlazaOptionalUserPath,
|
|
PORTAL_ACCESS_CLASS,
|
|
PORTAL_ACCESS_ENFORCEMENT_GROUPS,
|
|
PORTAL_STATIC_ACCESS_RULES,
|
|
resolvePortalAccessEnforcementConfig,
|
|
resolvePortalAccessEnforcementDecision,
|
|
resolvePortalAccessPolicyMode,
|
|
resolvePortalAccessShadowLogIntervalMs,
|
|
shouldOverridePortalLegacyGlobalAuth,
|
|
} from './portal-access-policy.mjs';
|
|
|
|
const pageDataPredicates = {
|
|
isPageDataPublicPath,
|
|
isLegacyPageDataApiPath,
|
|
};
|
|
|
|
const boundaryCases = [
|
|
['/status', 'GET', PORTAL_ACCESS_CLASS.PUBLIC, true],
|
|
['/runtime/status', 'POST', PORTAL_ACCESS_CLASS.PUBLIC, true],
|
|
['/config/blocked-words', 'GET', PORTAL_ACCESS_CLASS.PUBLIC, true],
|
|
['/internal/agent/jobs/job-1/claim', 'POST', PORTAL_ACCESS_CLASS.INTERNAL, true],
|
|
['/internal/deep-search/llm', 'POST', PORTAL_ACCESS_CLASS.INTERNAL, true],
|
|
['/agent/mindspace_page_patch', 'POST', PORTAL_ACCESS_CLASS.INTERNAL, true],
|
|
['/agent/mindspace_asset_delete', 'POST', PORTAL_ACCESS_CLASS.INTERNAL, true],
|
|
['/agent/mindspace_asset_download', 'POST', PORTAL_ACCESS_CLASS.INTERNAL, true],
|
|
['/agent/mindspace_image_generate', 'POST', PORTAL_ACCESS_CLASS.INTERNAL, true],
|
|
['/internal/image-make/runtime-config', 'GET', PORTAL_ACCESS_CLASS.INTERNAL, true],
|
|
['/mindspace/v1/assets/asset-1/download', 'GET', PORTAL_ACCESS_CLASS.ROUTE_AUTHORIZED, true],
|
|
['/mindspace/v1/assets/asset-1/download', 'POST', PORTAL_ACCESS_CLASS.AUTHENTICATED, false],
|
|
['/plaza/v1/categories', 'GET', PORTAL_ACCESS_CLASS.OPTIONAL_USER, false],
|
|
['/plaza/v1/feed', 'get', PORTAL_ACCESS_CLASS.OPTIONAL_USER, false],
|
|
['/plaza/v1/posts/post-1', 'GET', PORTAL_ACCESS_CLASS.OPTIONAL_USER, false],
|
|
['/plaza/v1/posts/post-1/comments', 'GET', PORTAL_ACCESS_CLASS.OPTIONAL_USER, false],
|
|
['/plaza/v1/users/alice', 'GET', PORTAL_ACCESS_CLASS.OPTIONAL_USER, false],
|
|
['/plaza/v1/users/alice/posts', 'GET', PORTAL_ACCESS_CLASS.OPTIONAL_USER, false],
|
|
['/plaza/v1/events', 'POST', PORTAL_ACCESS_CLASS.OPTIONAL_USER, false],
|
|
['/plaza/v1/posts/post-1/reports', 'POST', PORTAL_ACCESS_CLASS.AUTHENTICATED, false],
|
|
[
|
|
'/public/pages/page-1/data/signups/rows',
|
|
'POST',
|
|
PORTAL_ACCESS_CLASS.ROUTE_AUTHORIZED,
|
|
false,
|
|
],
|
|
['/public/pages/page-1/data/signups', 'GET', PORTAL_ACCESS_CLASS.ROUTE_AUTHORIZED, false],
|
|
['/page-data/signups', 'GET', PORTAL_ACCESS_CLASS.PUBLIC, false],
|
|
['/mindspace/v1/pages', 'GET', PORTAL_ACCESS_CLASS.AUTHENTICATED, false],
|
|
];
|
|
|
|
test('static access registry has stable unique rule ids', () => {
|
|
const ids = PORTAL_STATIC_ACCESS_RULES.map((rule) => rule.id);
|
|
assert.equal(new Set(ids).size, ids.length);
|
|
assert.ok(ids.length >= 10);
|
|
for (const rule of PORTAL_STATIC_ACCESS_RULES) {
|
|
assert.ok(Object.values(PORTAL_ACCESS_CLASS).includes(rule.accessClass), rule.id);
|
|
}
|
|
});
|
|
|
|
test('portal access policy classifies the existing route boundary', () => {
|
|
for (const [path, method, expected] of boundaryCases) {
|
|
assert.equal(
|
|
classifyPortalApiAccess({ path, method }, pageDataPredicates),
|
|
expected,
|
|
`${method} ${path}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('legacy direct bypass derives from the same access registry', () => {
|
|
for (const [path, method, _expectedClass, expectedDirectBypass] of boundaryCases) {
|
|
assert.equal(
|
|
isPortalLegacyDirectGlobalAuthBypass(path, method),
|
|
expectedDirectBypass,
|
|
`${method} ${path}`,
|
|
);
|
|
}
|
|
assert.equal(findPortalStaticAccessRule({ path: '/mindspace/v1/pages', method: 'GET' }), null);
|
|
});
|
|
|
|
test('Plaza optional-user matcher excludes authenticated write routes', () => {
|
|
assert.equal(isPortalPlazaOptionalUserPath('/plaza/v1/seo/sitemap', 'GET'), true);
|
|
assert.equal(isPortalPlazaOptionalUserPath('/plaza/v1/events', 'POST'), true);
|
|
assert.equal(isPortalPlazaOptionalUserPath('/plaza/v1/posts/post-1', 'PATCH'), false);
|
|
assert.equal(isPortalPlazaOptionalUserPath('/plaza/v1/posts/post-1/reports', 'POST'), false);
|
|
assert.equal(isPortalPlazaOptionalUserPath('/mindspace/v1/pages', 'GET'), false);
|
|
});
|
|
|
|
test('portal access policy flags are safely bounded', () => {
|
|
assert.equal(resolvePortalAccessPolicyMode({}), 'off');
|
|
assert.equal(
|
|
resolvePortalAccessPolicyMode({ MEMIND_PORTAL_ACCESS_POLICY_MODE: ' SHADOW ' }),
|
|
'shadow',
|
|
);
|
|
assert.equal(resolvePortalAccessPolicyMode({ MEMIND_PORTAL_ACCESS_POLICY_MODE: 'on' }), 'off');
|
|
assert.equal(resolvePortalAccessPolicyMode({ MEMIND_PORTAL_ACCESS_POLICY_MODE: 'invalid' }), 'off');
|
|
assert.equal(resolvePortalAccessShadowLogIntervalMs({}), 60_000);
|
|
assert.equal(
|
|
resolvePortalAccessShadowLogIntervalMs({
|
|
MEMIND_PORTAL_ACCESS_SHADOW_LOG_INTERVAL_MS: '1',
|
|
}),
|
|
1_000,
|
|
);
|
|
assert.equal(
|
|
resolvePortalAccessShadowLogIntervalMs({
|
|
MEMIND_PORTAL_ACCESS_SHADOW_LOG_INTERVAL_MS: '9999999',
|
|
}),
|
|
3_600_000,
|
|
);
|
|
});
|
|
|
|
test('access enforcement requires the master switch and an exact group', () => {
|
|
const defaultConfig = resolvePortalAccessEnforcementConfig({});
|
|
assert.equal(defaultConfig.enabled, false);
|
|
assert.deepEqual(defaultConfig.activeGroups, []);
|
|
|
|
const groupOnly = resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS: 'legacy-page-data',
|
|
});
|
|
assert.equal(groupOnly.enabled, false);
|
|
assert.deepEqual(groupOnly.requestedGroups, ['legacy-page-data']);
|
|
|
|
const enabled = resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED: '1',
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS:
|
|
' legacy-page-data,legacy-page-data ',
|
|
});
|
|
assert.equal(enabled.enabled, true);
|
|
assert.deepEqual(enabled.activeGroups, ['legacy-page-data']);
|
|
});
|
|
|
|
test('access enforcement rejects all and unknown groups when master is enabled', () => {
|
|
assert.deepEqual(PORTAL_ACCESS_ENFORCEMENT_GROUPS, [
|
|
'legacy-page-data',
|
|
'page-data-public',
|
|
'plaza-optional-user',
|
|
]);
|
|
assert.throws(
|
|
() =>
|
|
resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED: '1',
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS: 'all',
|
|
}),
|
|
/Unknown Portal access enforcement group\(s\): all/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED: '1',
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS:
|
|
'legacy-page-data,typo-group',
|
|
}),
|
|
/typo-group/,
|
|
);
|
|
});
|
|
|
|
test('kill switch overrides every requested enforcement group', () => {
|
|
const config = resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED: '1',
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS:
|
|
PORTAL_ACCESS_ENFORCEMENT_GROUPS.join(','),
|
|
MEMIND_PORTAL_ACCESS_POLICY_KILL_SWITCH: '1',
|
|
});
|
|
assert.equal(config.masterEnabled, true);
|
|
assert.equal(config.killSwitch, true);
|
|
assert.equal(config.enabled, false);
|
|
assert.deepEqual(config.activeGroups, []);
|
|
});
|
|
|
|
test('single-group enforcement bypasses only the selected policy group', () => {
|
|
const config = resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED: '1',
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS: 'legacy-page-data',
|
|
});
|
|
const legacyDecision = resolvePortalAccessEnforcementDecision(
|
|
{ path: '/page-data/signups', method: 'GET' },
|
|
pageDataPredicates,
|
|
config,
|
|
);
|
|
assert.deepEqual(legacyDecision, {
|
|
selected: true,
|
|
bypassGlobalAuth: true,
|
|
reason: 'group-enabled',
|
|
proposedAccessClass: PORTAL_ACCESS_CLASS.PUBLIC,
|
|
proposedRuleId: 'legacy-page-data',
|
|
policyGroup: 'legacy-page-data',
|
|
});
|
|
|
|
const publicPageDataDecision = resolvePortalAccessEnforcementDecision(
|
|
{ path: '/public/pages/page-1/data/signups', method: 'GET' },
|
|
pageDataPredicates,
|
|
config,
|
|
);
|
|
assert.equal(publicPageDataDecision.selected, false);
|
|
assert.equal(publicPageDataDecision.bypassGlobalAuth, false);
|
|
assert.equal(publicPageDataDecision.reason, 'group-disabled');
|
|
|
|
const privateDecision = resolvePortalAccessEnforcementDecision(
|
|
{ path: '/mindspace/v1/pages', method: 'GET' },
|
|
pageDataPredicates,
|
|
config,
|
|
);
|
|
assert.equal(privateDecision.selected, false);
|
|
assert.equal(privateDecision.bypassGlobalAuth, false);
|
|
assert.equal(privateDecision.policyGroup, 'authenticated-default');
|
|
});
|
|
|
|
test('enforcement preserves legacy optional-user hydration when already bypassed', () => {
|
|
const config = resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED: '1',
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS: 'page-data-public',
|
|
});
|
|
const decision = resolvePortalAccessEnforcementDecision(
|
|
{ path: '/public/pages/page-1/data/signups', method: 'GET' },
|
|
pageDataPredicates,
|
|
config,
|
|
);
|
|
|
|
assert.equal(decision.selected, true);
|
|
assert.equal(decision.bypassGlobalAuth, true);
|
|
assert.equal(
|
|
shouldOverridePortalLegacyGlobalAuth(decision, false),
|
|
true,
|
|
'no-auth and legacy modes need the new bypass',
|
|
);
|
|
assert.equal(
|
|
shouldOverridePortalLegacyGlobalAuth(decision, true),
|
|
false,
|
|
'multi-user mode must continue through legacy hydration',
|
|
);
|
|
});
|
|
|
|
test('enforcement decision reports kill switch and master-off reasons', () => {
|
|
const request = { path: '/page-data/signups', method: 'GET' };
|
|
const groupOnly = resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS: 'legacy-page-data',
|
|
});
|
|
assert.equal(
|
|
resolvePortalAccessEnforcementDecision(request, pageDataPredicates, groupOnly).reason,
|
|
'master-disabled',
|
|
);
|
|
|
|
const killed = resolvePortalAccessEnforcementConfig({
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCEMENT_ENABLED: '1',
|
|
MEMIND_PORTAL_ACCESS_POLICY_ENFORCE_GROUPS: 'legacy-page-data',
|
|
MEMIND_PORTAL_ACCESS_POLICY_KILL_SWITCH: '1',
|
|
});
|
|
assert.equal(
|
|
resolvePortalAccessEnforcementDecision(request, pageDataPredicates, killed).reason,
|
|
'kill-switch',
|
|
);
|
|
});
|
|
|
|
test('shadow comparison matches current direct bypass and authenticated routes', () => {
|
|
const cases = [
|
|
{ path: '/status', method: 'GET', multiUserEnabled: false },
|
|
{ path: '/internal/agent/jobs/job-1/claim', method: 'POST', multiUserEnabled: false },
|
|
{
|
|
path: '/mindspace/v1/assets/asset-1/download',
|
|
method: 'GET',
|
|
multiUserEnabled: false,
|
|
},
|
|
{ path: '/plaza/v1/categories', method: 'GET', multiUserEnabled: true },
|
|
{ path: '/mindspace/v1/pages', method: 'GET', multiUserEnabled: false },
|
|
{ path: '/mindspace/v1/pages', method: 'GET', multiUserEnabled: true },
|
|
];
|
|
|
|
for (const request of cases) {
|
|
const comparison = comparePortalAccessPolicyShadow(request, {
|
|
...pageDataPredicates,
|
|
multiUserEnabled: request.multiUserEnabled,
|
|
});
|
|
assert.equal(comparison.mismatch, false, `${request.method} ${request.path}`);
|
|
}
|
|
});
|
|
|
|
test('shadow comparison exposes public-route coupling to the multi-user auth mode', () => {
|
|
const cases = [
|
|
['/plaza/v1/categories', 'GET', PORTAL_ACCESS_CLASS.OPTIONAL_USER, 'plaza-optional-user'],
|
|
[
|
|
'/public/pages/page-1/data/signups',
|
|
'GET',
|
|
PORTAL_ACCESS_CLASS.ROUTE_AUTHORIZED,
|
|
'page-data-public',
|
|
],
|
|
['/page-data/signups', 'GET', PORTAL_ACCESS_CLASS.PUBLIC, 'legacy-page-data'],
|
|
];
|
|
|
|
for (const [path, method, expectedClass, expectedGroup] of cases) {
|
|
const comparison = comparePortalAccessPolicyShadow(
|
|
{ path, method },
|
|
{
|
|
...pageDataPredicates,
|
|
multiUserEnabled: false,
|
|
},
|
|
);
|
|
assert.equal(comparison.proposedAccessClass, expectedClass);
|
|
assert.equal(comparison.policyGroup, expectedGroup);
|
|
assert.equal(comparison.currentGlobalAuthBypass, false);
|
|
assert.equal(comparison.proposedGlobalAuthBypass, true);
|
|
assert.equal(comparison.mismatch, true);
|
|
}
|
|
});
|
|
|
|
test('shadow comparison stays aligned for public routes in multi-user mode', () => {
|
|
const comparison = comparePortalAccessPolicyShadow(
|
|
{ path: '/plaza/v1/categories', method: 'GET' },
|
|
{
|
|
...pageDataPredicates,
|
|
multiUserEnabled: true,
|
|
},
|
|
);
|
|
|
|
assert.deepEqual(comparison, {
|
|
path: '/plaza/v1/categories',
|
|
method: 'GET',
|
|
proposedAccessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER,
|
|
proposedRuleId: 'plaza-discovery',
|
|
policyGroup: 'plaza-optional-user',
|
|
currentGlobalAuthBypass: true,
|
|
proposedGlobalAuthBypass: true,
|
|
mismatch: false,
|
|
});
|
|
});
|
|
|
|
test('migration readiness separates known differences from unexpected drift', () => {
|
|
const requests = [
|
|
{ path: '/plaza/v1/categories', method: 'GET' },
|
|
{ path: '/public/pages/page-1/data/signups', method: 'GET' },
|
|
{ path: '/page-data/signups', method: 'GET' },
|
|
{ path: '/mindspace/v1/pages', method: 'GET' },
|
|
];
|
|
const knownMismatchPolicyGroups = [
|
|
'plaza-optional-user',
|
|
'page-data-public',
|
|
'legacy-page-data',
|
|
];
|
|
const report = assessPortalAccessMigrationReadiness(requests, {
|
|
...pageDataPredicates,
|
|
multiUserEnabled: false,
|
|
knownMismatchPolicyGroups,
|
|
});
|
|
|
|
assert.equal(report.contractReady, true);
|
|
assert.equal(report.safeForBehaviorSwitch, false);
|
|
assert.equal(report.total, 4);
|
|
assert.equal(report.aligned, 1);
|
|
assert.equal(report.knownMismatches.length, 3);
|
|
assert.equal(report.unexpectedMismatches.length, 0);
|
|
|
|
const strictReport = assessPortalAccessMigrationReadiness(requests, {
|
|
...pageDataPredicates,
|
|
multiUserEnabled: false,
|
|
knownMismatchPolicyGroups: knownMismatchPolicyGroups.slice(0, 2),
|
|
});
|
|
assert.equal(strictReport.contractReady, false);
|
|
assert.equal(strictReport.unexpectedMismatches[0].policyGroup, 'legacy-page-data');
|
|
});
|
|
|
|
test('multi-user route contract is ready and aligned', () => {
|
|
const report = assessPortalAccessMigrationReadiness(
|
|
boundaryCases.map(([path, method]) => ({ path, method })),
|
|
{
|
|
...pageDataPredicates,
|
|
multiUserEnabled: true,
|
|
},
|
|
);
|
|
assert.equal(report.contractReady, true);
|
|
assert.equal(report.safeForBehaviorSwitch, true);
|
|
assert.equal(report.aligned, boundaryCases.length);
|
|
});
|
|
|
|
test('shadow reporter groups dynamic routes, suppresses repeats, and preserves counts', () => {
|
|
let currentTime = 10_000;
|
|
const emitted = [];
|
|
const reporter = createPortalAccessShadowReporter({
|
|
intervalMs: 1_000,
|
|
now: () => currentTime,
|
|
emit: (event) => emitted.push(event),
|
|
});
|
|
const mismatch = {
|
|
authMode: 'none',
|
|
method: 'GET',
|
|
proposedAccessClass: PORTAL_ACCESS_CLASS.OPTIONAL_USER,
|
|
policyGroup: 'plaza-optional-user',
|
|
currentGlobalAuthBypass: false,
|
|
proposedGlobalAuthBypass: true,
|
|
mismatch: true,
|
|
};
|
|
|
|
reporter.record({
|
|
...mismatch,
|
|
requestId: 'req-1',
|
|
path: '/plaza/v1/posts/post-1',
|
|
proposedRuleId: 'plaza-post-detail',
|
|
});
|
|
currentTime += 500;
|
|
reporter.record({
|
|
...mismatch,
|
|
requestId: 'req-2',
|
|
path: '/plaza/v1/users/alice',
|
|
proposedRuleId: 'plaza-user-profile',
|
|
});
|
|
|
|
assert.equal(emitted.length, 1);
|
|
assert.deepEqual(reporter.snapshot(), [{
|
|
key: 'none:GET:plaza-optional-user',
|
|
totalOccurrences: 2,
|
|
suppressedSinceLast: 1,
|
|
lastEmittedAt: 10_000,
|
|
}]);
|
|
|
|
currentTime += 501;
|
|
const next = reporter.record({
|
|
...mismatch,
|
|
requestId: 'req-3',
|
|
path: '/plaza/v1/feed',
|
|
proposedRuleId: 'plaza-discovery',
|
|
});
|
|
assert.equal(emitted.length, 2);
|
|
assert.equal(next.occurrences, 3);
|
|
assert.equal(next.suppressedSinceLast, 1);
|
|
assert.equal(next.requestId, 'req-3');
|
|
});
|
|
|
|
test('shadow reporter ignores aligned requests', () => {
|
|
const emitted = [];
|
|
const reporter = createPortalAccessShadowReporter({
|
|
emit: (event) => emitted.push(event),
|
|
});
|
|
assert.equal(reporter.record({ mismatch: false }), null);
|
|
assert.deepEqual(reporter.snapshot(), []);
|
|
assert.deepEqual(emitted, []);
|
|
});
|