Release 1.0.8 with platform, security, and UI hardening.

Adds API filter registry, style theme registry, SW bitmask cache clear, KV namespacing, session expiry checks, accessibility improvements, and expanded test coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Amer Agovic
2026-06-10 21:08:21 -05:00
parent c6f7240912
commit 859db6ccb2
40 changed files with 2124 additions and 577 deletions
+120
View File
@@ -0,0 +1,120 @@
import { beforeEach, describe, test } from 'node:test';
import assert from 'node:assert';
import {
applyRequestAuthorization,
createAPIFilterRegistry,
normalizeRequestAuthorization
} from '../src/platform/api-filters.js';
import {
SECURITY_REQUEST_FILTER,
createSecurityRequestFilter
} from '../src/security/runtime/api-auth.js';
describe('api-filters', () => {
/** @type {ReturnType<typeof createAPIFilterRegistry>} */
let registry;
beforeEach(() => {
registry = createAPIFilterRegistry();
});
test('normalizeRequestAuthorization supports string, scheme/token, and custom headers', () => {
assert.deepStrictEqual(
normalizeRequestAuthorization('Bearer abc'),
{ name: 'Authorization', value: 'Bearer abc' }
);
assert.deepStrictEqual(
normalizeRequestAuthorization({ scheme: 'Basic', token: 'dXNlcjpwYXNz' }),
{ name: 'Authorization', value: 'Basic dXNlcjpwYXNz' }
);
assert.deepStrictEqual(
normalizeRequestAuthorization({ name: 'X-Api-Key', value: 'secret' }),
{ name: 'X-Api-Key', value: 'secret' }
);
});
test('applyRequestFilters runs in priority order and supports async filters', async () => {
const calls = [];
registry.registerRequestFilter('second', async (ctx) => {
calls.push('second');
return {
...ctx,
headers: applyRequestAuthorization(ctx.headers, { name: 'X-Second', value: '2' })
};
}, { priority: 20 });
registry.registerRequestFilter('first', async (ctx) => {
calls.push('first');
return {
...ctx,
headers: applyRequestAuthorization(ctx.headers, { name: 'X-First', value: '1' })
};
}, { priority: 10 });
const result = await registry.applyRequestFilters({
url: '/api/items',
endpoint: '/items',
headers: new Headers()
});
assert.deepStrictEqual(calls, ['first', 'second']);
assert.strictEqual(result.headers.get('X-First'), '1');
assert.strictEqual(result.headers.get('X-Second'), '2');
});
test('skipRequestFilters can skip named filters', async () => {
registry.registerRequestFilter('tenant', (ctx) => ({
...ctx,
headers: applyRequestAuthorization(ctx.headers, { name: 'X-Tenant', value: 'acme' })
}), { priority: 10 });
registry.registerRequestFilter('auth', (ctx) => ({
...ctx,
headers: applyRequestAuthorization(ctx.headers, { scheme: 'Bearer', token: 'token' })
}), { priority: 100 });
const result = await registry.applyRequestFilters({
url: '/api/login',
endpoint: '/login',
headers: new Headers(),
skipRequestFilters: ['auth']
});
assert.strictEqual(result.headers.get('X-Tenant'), 'acme');
assert.strictEqual(result.headers.get('Authorization'), null);
});
test('security request filter delegates authorization to the active policy', async () => {
const securityService = {
state: {
enabled: true,
provider: 'basic',
isAuthenticated: true,
session: { jwt_token: 'ignored-if-policy-returns' },
user: { id: 'user-1' },
profile: null,
realm: null,
config: {},
policy: {
async getRequestAuthorization() {
return { name: 'X-Api-Key', value: 'policy-key' };
}
}
}
};
const result = await createSecurityRequestFilter(securityService)({
url: '/api/items',
endpoint: '/items',
headers: new Headers()
});
assert.strictEqual(result.headers.get('X-Api-Key'), 'policy-key');
assert.strictEqual(result.headers.get('Authorization'), null);
});
test('installable security filter id is stable', () => {
assert.strictEqual(SECURITY_REQUEST_FILTER, 'security.auth');
});
});
+52
View File
@@ -0,0 +1,52 @@
import { describe, test } from 'node:test';
import assert from 'node:assert';
import { ApiSecurityPolicy } from '../src/security/policy/ApiSecurityPolicy.js';
import { Permit, Session, User } from '../src/security/model/index.js';
import { SECURITY_RIGHTS } from '../src/security/model/rights.js';
describe('ApiSecurityPolicy permit evaluation', () => {
test('evaluateSync matches permits by principal, not path alone', () => {
const policy = new ApiSecurityPolicy({});
policy.cache.user = new User({ id: 'user-1', role_ids: ['role-a'] });
policy.cache.session = new Session({ jwt_token: 'jwt-test' });
policy.cache.permits = [
new Permit({
principal_type: 'user',
principal_id: 'other-user',
resource_path: '/app',
effect: 'allow',
rights: SECURITY_RIGHTS.read
})
];
const denied = policy.evaluateSync('user-1', 'read', '/app');
assert.strictEqual(denied.allowed, false);
policy.cache.permits = [
new Permit({
principal_type: 'user',
principal_id: 'user-1',
resource_path: '/app',
effect: 'allow',
rights: SECURITY_RIGHTS.read
})
];
const allowed = policy.evaluateSync('user-1', 'read', '/app');
assert.strictEqual(allowed.allowed, true);
policy.cache.permits = [
new Permit({
principal_type: 'role',
principal_id: 'role-a',
resource_path: '/app',
effect: 'allow',
rights: SECURITY_RIGHTS.read
})
];
const roleAllowed = policy.evaluateSync('user-1', 'read', '/app');
assert.strictEqual(roleAllowed.allowed, true);
});
});
+51 -1
View File
@@ -7,13 +7,17 @@ import { test, describe, beforeEach } from 'node:test';
import assert from 'node:assert';
import {
initEnv,
getConfig,
getConfig,
getConfigSync,
setConfig,
getConfigDict,
isDevelopment,
isProduction,
isServiceWorkerEnabledSync,
resolveServiceWorkerEnabled,
CONFIG_KEYS
} from '../src/platform/env.js';
import { getProvider } from '../src/platform/storage.js';
describe('env.js', () => {
beforeEach(() => {
@@ -90,6 +94,34 @@ describe('env.js', () => {
// May return null or undefined depending on import.meta.env availability
assert.ok(value === null || value === undefined);
});
test('locked keys ignore persisted storage overrides', async () => {
initEnv({
name: 'TestApp',
api: { base_url: '/api/profile' },
modules: ['rt', 'game']
});
const configStorage = getProvider('kv', 'config');
await configStorage.set(CONFIG_KEYS.API_BASE_URL, '/api/stale');
await configStorage.set(CONFIG_KEYS.MODULES, ['stale']);
assert.strictEqual(await getConfig(CONFIG_KEYS.API_BASE_URL), '/api/profile');
assert.deepStrictEqual(await getConfig(CONFIG_KEYS.MODULES), ['rt', 'game']);
});
});
describe('getConfigSync', () => {
test('should return in-memory config without storage reads', () => {
initEnv({
name: 'SyncApp',
api: { base_url: '/api/sync' }
});
assert.strictEqual(getConfigSync(CONFIG_KEYS.APP_NAME), 'SyncApp');
assert.strictEqual(getConfigSync(CONFIG_KEYS.API_BASE_URL), '/api/sync');
assert.strictEqual(getConfigSync('MISSING', 'fallback'), 'fallback');
});
});
describe('setConfig', () => {
@@ -148,6 +180,24 @@ describe('env.js', () => {
assert.ok('STORAGE_BACKEND' in CONFIG_KEYS);
assert.ok('API_BASE_URL' in CONFIG_KEYS);
assert.ok('MODULES' in CONFIG_KEYS);
assert.ok('SERVICE_WORKER_ENABLED' in CONFIG_KEYS);
});
});
describe('service worker profile flag', () => {
test('resolveServiceWorkerEnabled defaults to true', () => {
assert.strictEqual(resolveServiceWorkerEnabled({}), true);
});
test('resolveServiceWorkerEnabled reads service_worker.enabled', () => {
assert.strictEqual(resolveServiceWorkerEnabled({ service_worker: { enabled: false } }), false);
assert.strictEqual(resolveServiceWorkerEnabled({ pwa: { service_worker: { enabled: false } } }), false);
});
test('initEnv seeds SERVICE_WORKER_ENABLED into config', () => {
initEnv({ service_worker: { enabled: false } });
assert.strictEqual(isServiceWorkerEnabledSync(), false);
assert.strictEqual(getConfigDict()[CONFIG_KEYS.SERVICE_WORKER_ENABLED], false);
});
});
+19
View File
@@ -0,0 +1,19 @@
import { describe, test } from 'node:test';
import assert from 'node:assert';
import { SecurityPolicy } from '../src/security/policy/SecurityPolicy.js';
describe('SecurityPolicy defaults', () => {
test('evaluate fails closed when not implemented by a concrete policy', async () => {
const policy = new SecurityPolicy();
const result = await policy.evaluate('user-1', 'read', '/app');
assert.strictEqual(result.allowed, false);
assert.strictEqual(result.requires_login, true);
});
test('evaluateSync fails closed when not implemented by a concrete policy', () => {
const policy = new SecurityPolicy();
const result = policy.evaluateSync('user-1', 'read', '/app');
assert.strictEqual(result.allowed, false);
assert.strictEqual(result.requires_login, true);
});
});
+22
View File
@@ -0,0 +1,22 @@
import { describe, test } from 'node:test';
import assert from 'node:assert';
import { isSessionActive } from '../src/security/runtime/session-utils.js';
describe('session-utils', () => {
test('isSessionActive returns false for missing or expired sessions', () => {
assert.strictEqual(isSessionActive(null), false);
assert.strictEqual(isSessionActive({ status: 'expired' }), false);
assert.strictEqual(isSessionActive({
status: 'active',
expires_on: new Date(Date.now() - 60_000).toISOString()
}), false);
});
test('isSessionActive returns true for active unexpired sessions', () => {
assert.strictEqual(isSessionActive({ status: 'active' }), true);
assert.strictEqual(isSessionActive({
status: 'active',
expires_on: new Date(Date.now() + 60_000).toISOString()
}), true);
});
});
+50
View File
@@ -0,0 +1,50 @@
import { beforeEach, describe, test } from 'node:test';
import assert from 'node:assert';
import { KeyValueStore, KV_KEY_PREFIX } from '../src/platform/storage.js';
describe('KeyValueStore namespacing', () => {
beforeEach(() => {
localStorage.clear();
});
test('isolates values by provider name', async () => {
const configStore = new KeyValueStore('config', 'localStorage');
const securityStore = new KeyValueStore('security.basic', 'localStorage');
await configStore.set('theme.mode', 'dark');
await securityStore.set('security.basic.session', { user_id: 'u1' });
assert.strictEqual(await configStore.get('theme.mode'), 'dark');
assert.deepStrictEqual(await securityStore.get('security.basic.session'), { user_id: 'u1' });
assert.strictEqual(await configStore.get('security.basic.session', null), null);
assert.strictEqual(
localStorage.getItem(`${KV_KEY_PREFIX}config:theme.mode`),
JSON.stringify('dark')
);
assert.strictEqual(
localStorage.getItem(`${KV_KEY_PREFIX}security.basic:security.basic.session`),
JSON.stringify({ user_id: 'u1' })
);
});
test('clear removes only keys owned by the provider', async () => {
const configStore = new KeyValueStore('config', 'localStorage');
const securityStore = new KeyValueStore('security.basic', 'localStorage');
await configStore.set('theme.mode', 'dark');
await securityStore.set('security.basic.session', { user_id: 'u1' });
await configStore.clear();
assert.strictEqual(await configStore.get('theme.mode', null), null);
assert.deepStrictEqual(await securityStore.get('security.basic.session'), { user_id: 'u1' });
});
test('config provider reads legacy flat keys as fallback', async () => {
localStorage.setItem('theme.mode', JSON.stringify('light'));
const configStore = new KeyValueStore('config', 'localStorage');
assert.strictEqual(await configStore.get('theme.mode'), 'light');
});
});
+16
View File
@@ -0,0 +1,16 @@
import { describe, test } from 'node:test';
import assert from 'node:assert';
import { PWA_CACHE_SCOPE } from '../src/platform/worker.js';
describe('worker PWA cache scope', () => {
test('PWA_CACHE_SCOPE exposes composable bit flags', () => {
assert.strictEqual(PWA_CACHE_SCOPE.SERVICE_WORKERS, 1);
assert.strictEqual(PWA_CACHE_SCOPE.CACHES, 2);
assert.strictEqual(PWA_CACHE_SCOPE.STORAGE, 4);
assert.strictEqual(PWA_CACHE_SCOPE.ALL, 7);
assert.strictEqual(
PWA_CACHE_SCOPE.CACHES | PWA_CACHE_SCOPE.SERVICE_WORKERS,
3
);
});
});