update
This commit is contained in:
136
readme.hints.md
136
readme.hints.md
@ -30,10 +30,72 @@
|
||||
- Test: `pnpm test` (runs `tstest test/`).
|
||||
- Format: `pnpm format` (runs `gitzone format`).
|
||||
|
||||
## Testing Framework
|
||||
- Uses `@push.rocks/tapbundle` (`tap`, `expect`, `expactAsync`).
|
||||
- Test files: must start with `test.` and use `.ts` extension.
|
||||
- Run specific tests via `tsx`, e.g., `tsx test/test.router.ts`.
|
||||
## How to Test
|
||||
|
||||
### Test Structure
|
||||
Tests use tapbundle from `@git.zone/tstest`. The correct pattern is:
|
||||
|
||||
```typescript
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
tap.test('test description', async () => {
|
||||
// Test logic here
|
||||
expect(someValue).toEqual(expectedValue);
|
||||
});
|
||||
|
||||
// IMPORTANT: Must end with tap.start()
|
||||
tap.start();
|
||||
```
|
||||
|
||||
### Expect Syntax (from @push.rocks/smartexpect)
|
||||
```typescript
|
||||
// Type assertions
|
||||
expect('hello').toBeTypeofString();
|
||||
expect(42).toBeTypeofNumber();
|
||||
|
||||
// Equality
|
||||
expect('hithere').toEqual('hithere');
|
||||
|
||||
// Negated assertions
|
||||
expect(1).not.toBeTypeofString();
|
||||
|
||||
// Regular expressions
|
||||
expect('hithere').toMatch(/hi/);
|
||||
|
||||
// Numeric comparisons
|
||||
expect(5).toBeGreaterThan(3);
|
||||
expect(0.1 + 0.2).toBeCloseTo(0.3, 10);
|
||||
|
||||
// Arrays
|
||||
expect([1, 2, 3]).toContain(2);
|
||||
expect([1, 2, 3]).toHaveLength(3);
|
||||
|
||||
// Async assertions
|
||||
await expect(asyncFunction()).resolves.toEqual('expected');
|
||||
await expect(asyncFunction()).resolves.withTimeout(5000).toBeTypeofString();
|
||||
|
||||
// Complex object navigation
|
||||
expect(complexObject)
|
||||
.property('users')
|
||||
.arrayItem(0)
|
||||
.property('name')
|
||||
.toEqual('Alice');
|
||||
```
|
||||
|
||||
### Test Modifiers
|
||||
- `tap.only.test()` - Run only this test
|
||||
- `tap.skip.test()` - Skip a test
|
||||
- `tap.timeout()` - Set test-specific timeout
|
||||
|
||||
### Running Tests
|
||||
- All tests: `pnpm test`
|
||||
- Specific test: `tsx test/test.router.ts`
|
||||
- With options: `tstest test/**/*.ts --verbose --timeout 60`
|
||||
|
||||
### Test File Requirements
|
||||
- Must start with `test.` prefix
|
||||
- Must use `.ts` extension
|
||||
- Must call `tap.start()` at the end
|
||||
|
||||
## Coding Conventions
|
||||
- Import modules via `plugins.ts`:
|
||||
@ -192,4 +254,68 @@ if (result instanceof Promise) {
|
||||
- Verifies that initial data is received even when handler sets up listeners after async work
|
||||
|
||||
### Usage Note
|
||||
Socket handlers require initial data from the client to trigger routing (not just a TLS handshake). Clients must send at least one byte of data for the handler to be invoked.
|
||||
Socket handlers require initial data from the client to trigger routing (not just a TLS handshake). Clients must send at least one byte of data for the handler to be invoked.
|
||||
|
||||
## Route-Specific Security Implementation (v19.5.3)
|
||||
|
||||
### Issue
|
||||
Route-specific security configurations (ipAllowList, ipBlockList, authentication) were defined in the route types but not enforced at runtime.
|
||||
|
||||
### Root Cause
|
||||
The RouteConnectionHandler only checked global IP validation but didn't enforce route-specific security rules after matching a route.
|
||||
|
||||
### Solution
|
||||
Added security checks after route matching:
|
||||
```typescript
|
||||
// Apply route-specific security checks
|
||||
const routeSecurity = route.action.security || route.security;
|
||||
if (routeSecurity) {
|
||||
// Check IP allow/block lists
|
||||
if (routeSecurity.ipAllowList || routeSecurity.ipBlockList) {
|
||||
const isIPAllowed = this.securityManager.isIPAuthorized(
|
||||
remoteIP,
|
||||
routeSecurity.ipAllowList || [],
|
||||
routeSecurity.ipBlockList || []
|
||||
);
|
||||
|
||||
if (!isIPAllowed) {
|
||||
socket.end();
|
||||
this.connectionManager.cleanupConnection(record, 'route_ip_blocked');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
- `test/test.route-security-unit.ts` - Unit tests verifying SecurityManager.isIPAuthorized logic
|
||||
- Tests confirm IP allow/block lists work correctly with glob patterns
|
||||
|
||||
### Configuration Example
|
||||
```typescript
|
||||
const routes: IRouteConfig[] = [{
|
||||
name: 'secure-api',
|
||||
match: { ports: 8443, domains: 'api.example.com' },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 3000 },
|
||||
security: {
|
||||
ipAllowList: ['192.168.1.*', '10.0.0.0/8'], // Allow internal IPs
|
||||
ipBlockList: ['192.168.1.100'], // But block specific IP
|
||||
maxConnections: 100, // Per-route limit (TODO)
|
||||
authentication: { // HTTP-only, requires TLS termination
|
||||
type: 'basic',
|
||||
credentials: [{ username: 'api', password: 'secret' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
### Notes
|
||||
- IP lists support glob patterns (via minimatch): `192.168.*`, `10.?.?.1`
|
||||
- Block lists take precedence over allow lists
|
||||
- Authentication requires TLS termination (cannot be enforced on passthrough/direct connections)
|
||||
- Per-route connection limits are not yet implemented
|
||||
- Security is defined at the route level (route.security), not in the action
|
||||
- Route matching is based solely on match criteria; security is enforced after matching
|
Reference in New Issue
Block a user