Every per-network limit counted by the connection's address, which behind Caddy is Caddy's for everyone: new anonymous accounts, codes by mail and password sign-ins were each limited for the whole instance. `networkOf` (now `common/network.dart`) takes the last address of X-Forwarded-For, the one the proxy saw itself, for a connection from a proxy the operator trusts (`TRUSTED_PROXIES`, which deploy/compose.yaml sets to Caddy's fixed address); otherwise, or where that is no address, the connection's. Never the Forwarded header, which Caddy passes on as sent, so never Serverpod's `remoteInfo`. A value that isn't addresses stops the server as it starts. Serverpod's own limit on anonymous accounts counts by the connection's address too and can't be told otherwise, so it is off; the anonymous endpoint counts them itself, 30 an hour per network as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
136 lines
3.6 KiB
Dart
136 lines
3.6 KiB
Dart
import 'package:halkyon_base_server/base.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('The proxies the operator trusts (TRUSTED_PROXIES)', () {
|
|
test('are none where unset or empty', () {
|
|
expect(trustedProxiesFrom(null), isEmpty);
|
|
expect(trustedProxiesFrom(''), isEmpty);
|
|
expect(trustedProxiesFrom(' '), isEmpty);
|
|
});
|
|
|
|
test('are IP addresses, separated by commas or spaces', () {
|
|
expect(trustedProxiesFrom('172.30.80.2'), {'172.30.80.2'});
|
|
expect(trustedProxiesFrom(' 172.30.80.2, 2001:db8::2 10.0.0.1 '), {
|
|
'172.30.80.2',
|
|
'2001:db8::2',
|
|
'10.0.0.1',
|
|
});
|
|
});
|
|
|
|
test('take an IPv4 address written as IPv6 as the IPv4 address', () {
|
|
expect(trustedProxiesFrom('::ffff:172.30.80.2'), {'172.30.80.2'});
|
|
});
|
|
|
|
test('stop the server for anything but addresses', () {
|
|
expect(() => trustedProxiesFrom('caddy'), throwsStateError);
|
|
expect(() => trustedProxiesFrom('172.30.80.0/24'), throwsStateError);
|
|
expect(
|
|
() => trustedProxiesFrom('172.30.80.2, 300.1.1.1'),
|
|
throwsStateError,
|
|
);
|
|
});
|
|
});
|
|
|
|
group("A request's network", () {
|
|
const caddy = {'172.30.80.2'};
|
|
|
|
test("is the connection's address where no proxy is trusted, whatever "
|
|
'X-Forwarded-For says', () {
|
|
expect(
|
|
visitorAddress(
|
|
socket: '203.0.113.9',
|
|
forwardedFor: ['198.51.100.7'],
|
|
trustedProxies: const {},
|
|
),
|
|
'203.0.113.9',
|
|
);
|
|
});
|
|
|
|
test('is the last address of X-Forwarded-For, the one the trusted proxy '
|
|
'saw itself, for a connection from that proxy', () {
|
|
expect(
|
|
visitorAddress(
|
|
socket: '172.30.80.2',
|
|
forwardedFor: ['198.51.100.7'],
|
|
trustedProxies: caddy,
|
|
),
|
|
'198.51.100.7',
|
|
);
|
|
// What a visitor sent along is in front of it, never believed.
|
|
expect(
|
|
visitorAddress(
|
|
socket: '172.30.80.2',
|
|
forwardedFor: ['10.9.9.9, 192.0.2.1 , 198.51.100.7'],
|
|
trustedProxies: caddy,
|
|
),
|
|
'198.51.100.7',
|
|
);
|
|
expect(
|
|
visitorAddress(
|
|
socket: '172.30.80.2',
|
|
forwardedFor: ['10.9.9.9', '2001:db8::7'],
|
|
trustedProxies: caddy,
|
|
),
|
|
'2001:db8::7',
|
|
);
|
|
});
|
|
|
|
test('is never X-Forwarded-For from a connection the operator trusts '
|
|
'not', () {
|
|
expect(
|
|
visitorAddress(
|
|
socket: '203.0.113.9',
|
|
forwardedFor: ['198.51.100.7'],
|
|
trustedProxies: caddy,
|
|
),
|
|
'203.0.113.9',
|
|
);
|
|
});
|
|
|
|
test("knows the proxy by its IPv4 address on the server's IPv6 socket", () {
|
|
expect(
|
|
visitorAddress(
|
|
socket: '::ffff:172.30.80.2',
|
|
forwardedFor: ['::ffff:198.51.100.7'],
|
|
trustedProxies: caddy,
|
|
),
|
|
'198.51.100.7',
|
|
);
|
|
});
|
|
|
|
test("is the proxy's own address where its header is missing or no "
|
|
'address', () {
|
|
for (final header in [
|
|
null,
|
|
<String>[],
|
|
[''],
|
|
['198.51.100.7, '],
|
|
['unknown'],
|
|
['198.51.100.7:4711'],
|
|
['[2001:db8::7]'],
|
|
]) {
|
|
expect(
|
|
visitorAddress(
|
|
socket: '172.30.80.2',
|
|
forwardedFor: header,
|
|
trustedProxies: caddy,
|
|
),
|
|
'172.30.80.2',
|
|
reason: '$header',
|
|
);
|
|
}
|
|
});
|
|
|
|
test('is none without a connection (tests, future calls)', () {
|
|
expect(
|
|
visitorAddress(
|
|
socket: null,
|
|
forwardedFor: ['198.51.100.7'],
|
|
trustedProxies: caddy,
|
|
),
|
|
isNull,
|
|
);
|
|
});
|
|
});
|
|
}
|