Files
cinny/src/app/utils/serverAcl.test.ts
T
Lotus CIandClaude Opus 5.5 adf94d7bc1
CI / Build & Quality Checks (pull_request) Successful in 1m54s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 10s
CI / Playwright smoke (e2e) (pull_request) Successful in 11m45s
fix(acl): a missing allow_ip_literals means allowed (#261)
The Server ACL editor treated an absent allow_ip_literals as false, so the
first save in a room with no ACL (or one omitting the field) silently banned
IP-literal servers. The spec default is true. normalizeServerAcl() fills the
spec defaults once and feeds the initial state, the dirty check and the save.
It also stops showing an existing ACL with no allow list as allow-all: per
spec that allows no servers, and the /acl command already keeps it as-is.

The checkbox is driven by onClick (folds has no onChange), now readOnly so
React stops warning about a checked input without onChange.

Fixes #261

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 14:07:03 -04:00

82 lines
2.7 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
isValidServerPattern,
globToRegExp,
matchesAnyGlob,
normalizeServerAcl,
} from './serverAcl';
test('isValidServerPattern: accepts hostnames, IPs, and globs', () => {
for (const v of [
'*',
'*.example.com',
'matrix.org',
'1.2.3.*',
'10.0.0.?',
'*.evil.*',
'*bad*',
]) {
assert.equal(isValidServerPattern(v), true, v);
}
});
test('isValidServerPattern: rejects empty, malformed, and non-glob junk', () => {
for (const v of ['', ' ', '.foo', 'foo.', 'a..b', '-', ':', 'under_score', 'a,b', 'a b']) {
assert.equal(isValidServerPattern(v), false, v);
}
});
test('globToRegExp: * = any run, ? = single char, other metachars escaped', () => {
assert.equal(globToRegExp('*').test('anything.org'), true);
assert.equal(globToRegExp('*.evil.com').test('a.evil.com'), true);
assert.equal(globToRegExp('*.evil.com').test('evil.com'), false); // needs the leading label
assert.equal(globToRegExp('1.2.3.?').test('1.2.3.4'), true);
assert.equal(globToRegExp('1.2.3.?').test('1.2.3.45'), false);
// The dot is a literal, not a regex wildcard.
assert.equal(globToRegExp('a.b').test('axb'), false);
});
test('globToRegExp: case-insensitive (hostnames + Synapse IGNORECASE)', () => {
assert.equal(globToRegExp('MATRIX.foo.org').test('matrix.foo.org'), true);
});
test('matchesAnyGlob: true if any glob matches (self-ban detection)', () => {
assert.equal(matchesAnyGlob('lotusguild.org', ['*']), true);
assert.equal(matchesAnyGlob('lotusguild.org', ['*.evil.com', 'lotus*.org']), true);
assert.equal(matchesAnyGlob('lotusguild.org', ['*.evil.com']), false);
assert.equal(matchesAnyGlob('lotusguild.org', []), false);
});
test('no ACL event: every server allowed, IP literals allowed (#261)', () => {
assert.deepEqual(normalizeServerAcl(undefined), {
allow: ['*'],
deny: [],
allow_ip_literals: true,
});
});
test('a missing allow_ip_literals defaults to true, per the spec (#261)', () => {
assert.equal(normalizeServerAcl({ allow: ['*'], deny: ['bad.example'] }).allow_ip_literals, true);
});
test('an explicit allow_ip_literals is kept', () => {
assert.equal(
normalizeServerAcl({ allow: ['*'], allow_ip_literals: false }).allow_ip_literals,
false,
);
assert.equal(
normalizeServerAcl({ allow: ['*'], allow_ip_literals: true }).allow_ip_literals,
true,
);
});
test('missing or malformed lists become empty, non-strings are dropped', () => {
assert.deepEqual(normalizeServerAcl({ allow: ['a.example', 3, null] }), {
allow: ['a.example'],
deny: [],
allow_ip_literals: true,
});
assert.deepEqual(normalizeServerAcl({ allow: 'a.example', deny: {} }).allow, []);
});