ldrouter 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cli.js +20 -0
  3. package/dist/server/app.js +138 -0
  4. package/dist/server/auth/api-key.js +75 -0
  5. package/dist/server/auth/crypto.js +94 -0
  6. package/dist/server/auth/ids.js +40 -0
  7. package/dist/server/auth/middleware.js +36 -0
  8. package/dist/server/auth/recovery.js +11 -0
  9. package/dist/server/caching/store.js +119 -0
  10. package/dist/server/cli/tui/ansi.js +58 -0
  11. package/dist/server/cli/tui/noise.js +397 -0
  12. package/dist/server/config/index.js +97 -0
  13. package/dist/server/db/index.js +64 -0
  14. package/dist/server/db/migrate.js +408 -0
  15. package/dist/server/db/repositories/audit.js +75 -0
  16. package/dist/server/db/repositories/settings.js +63 -0
  17. package/dist/server/db/schema.js +396 -0
  18. package/dist/server/errors.js +65 -0
  19. package/dist/server/gateway/runner.js +745 -0
  20. package/dist/server/logging/logger.js +35 -0
  21. package/dist/server/maintenance/retention.js +48 -0
  22. package/dist/server/metrics/registry.js +169 -0
  23. package/dist/server/protocols/anthropic.js +154 -0
  24. package/dist/server/protocols/canonical.js +201 -0
  25. package/dist/server/providers/index.js +89 -0
  26. package/dist/server/routes/admin/aliases.js +98 -0
  27. package/dist/server/routes/admin/api-keys.js +194 -0
  28. package/dist/server/routes/admin/audit.js +19 -0
  29. package/dist/server/routes/admin/auth.js +124 -0
  30. package/dist/server/routes/admin/backup.js +113 -0
  31. package/dist/server/routes/admin/combos.js +198 -0
  32. package/dist/server/routes/admin/dashboard.js +55 -0
  33. package/dist/server/routes/admin/models.js +178 -0
  34. package/dist/server/routes/admin/providers.js +212 -0
  35. package/dist/server/routes/admin/requests.js +156 -0
  36. package/dist/server/routes/admin/settings.js +197 -0
  37. package/dist/server/routes/admin/setup.js +80 -0
  38. package/dist/server/routes/admin/stats.js +180 -0
  39. package/dist/server/routes/admin.js +39 -0
  40. package/dist/server/routes/gateway/anthropic.js +112 -0
  41. package/dist/server/routes/gateway/openai.js +257 -0
  42. package/dist/server/routes/gateway.js +7 -0
  43. package/dist/server/routes/health.js +27 -0
  44. package/dist/server/routing/capabilities.js +52 -0
  45. package/dist/server/routing/circuit.js +37 -0
  46. package/dist/server/routing/combo.js +100 -0
  47. package/dist/server/routing/quota.js +51 -0
  48. package/dist/server/routing/ratelimit.js +58 -0
  49. package/dist/server/routing/resolver.js +43 -0
  50. package/dist/server/security/redact.js +111 -0
  51. package/dist/server/selfupdate/index.js +208 -0
  52. package/dist/server/upstream/client.js +179 -0
  53. package/dist/server/util/cidr.js +91 -0
  54. package/dist/server/util/client-ip.js +15 -0
  55. package/dist/server/util/stable-json.js +19 -0
  56. package/dist/server/version.js +35 -0
  57. package/dist/shared/types.js +2 -0
  58. package/dist/web/assets/index-B3mCvc2W.js +251 -0
  59. package/dist/web/assets/index-DSddXVaT.css +1 -0
  60. package/dist/web/favicon.png +0 -0
  61. package/dist/web/index.html +15 -0
  62. package/dist/web/logo.png +0 -0
  63. package/package.json +2 -2
@@ -0,0 +1,91 @@
1
+ // CIDR matching for IPv4 + IPv6.
2
+ import ipaddr from 'ipaddr.js';
3
+ export function parseCidr(input) {
4
+ const cidr = input.trim();
5
+ if (!cidr)
6
+ throw new Error('empty cidr');
7
+ let base;
8
+ let prefix;
9
+ if (cidr.includes('/')) {
10
+ const parts = cidr.split('/');
11
+ base = parts[0];
12
+ prefix = Number(parts[1]);
13
+ }
14
+ else {
15
+ base = cidr;
16
+ prefix = -1; // -1 = single host, normalized
17
+ }
18
+ const parsed = ipaddr.parse(base);
19
+ const family = parsed.kind() === 'ipv4' ? 'ipv4' : 'ipv6';
20
+ if (prefix === -1) {
21
+ prefix = family === 'ipv4' ? 32 : 128;
22
+ }
23
+ if (family === 'ipv4' && (prefix < 0 || prefix > 32))
24
+ throw new Error('invalid ipv4 prefix');
25
+ if (family === 'ipv6' && (prefix < 0 || prefix > 128))
26
+ throw new Error('invalid ipv6 prefix');
27
+ const normalized = `${parsed.toString()}/${prefix}`;
28
+ return {
29
+ cidr: normalized,
30
+ family,
31
+ prefix,
32
+ matches(ip) {
33
+ try {
34
+ const addr = ipaddr.parse(ip);
35
+ const addrKind = addr.kind();
36
+ if (addrKind === family) {
37
+ return samePrefix(parsed, addr, prefix);
38
+ }
39
+ // IPv4-mapped IPv6 (::ffff:a.b.c.d) satisfies IPv4 CIDR rules.
40
+ if (family === 'ipv4' && addrKind === 'ipv6') {
41
+ const v4 = addr.toIPv4Address();
42
+ if (!v4)
43
+ return false;
44
+ return samePrefix(parsed, v4, prefix);
45
+ }
46
+ return false;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ },
52
+ };
53
+ }
54
+ function samePrefix(a, b, prefix) {
55
+ const aBytes = octets(a);
56
+ const bBytes = octets(b);
57
+ const full = aBytes.length * 8;
58
+ if (prefix === 0)
59
+ return true;
60
+ if (prefix >= full)
61
+ return a.toString() === b.toString();
62
+ const fullBytes = Math.floor(prefix / 8);
63
+ const remBits = prefix % 8;
64
+ for (let i = 0; i < fullBytes; i++) {
65
+ if (aBytes[i] !== bBytes[i])
66
+ return false;
67
+ }
68
+ if (remBits === 0)
69
+ return true;
70
+ const mask = (0xff << (8 - remBits)) & 0xff;
71
+ return (aBytes[fullBytes] & mask) === (bBytes[fullBytes] & mask);
72
+ }
73
+ function octets(a) {
74
+ if (a.kind() === 'ipv4') {
75
+ return a.toByteArray();
76
+ }
77
+ return a.toByteArray();
78
+ }
79
+ export function ipMatchesAny(ip, cidrs) {
80
+ for (const c of cidrs) {
81
+ try {
82
+ const parsed = parseCidr(c);
83
+ if (parsed.matches(ip))
84
+ return true;
85
+ }
86
+ catch {
87
+ // ignore invalid cidr
88
+ }
89
+ }
90
+ return false;
91
+ }
@@ -0,0 +1,15 @@
1
+ // Client-IP resolution: trust X-Forwarded-For only when trustProxyHops > 0.
2
+ import { loadConfig } from '../config/index.js';
3
+ export function resolveClientIp(req) {
4
+ const cfg = loadConfig();
5
+ if (cfg.trustProxyHops > 0) {
6
+ const xff = req.headers['x-forwarded-for'];
7
+ if (typeof xff === 'string') {
8
+ const chain = xff.split(',').map((s) => s.trim()).filter(Boolean);
9
+ const idx = Math.max(0, chain.length - cfg.trustProxyHops);
10
+ if (chain[idx])
11
+ return chain[idx];
12
+ }
13
+ }
14
+ return req.ip || (req.socket?.remoteAddress ?? '0.0.0.0');
15
+ }
@@ -0,0 +1,19 @@
1
+ // Stable deterministic JSON serialization for cache key derivation.
2
+ export function stableStringify(value) {
3
+ return JSON.stringify(sortKeys(value));
4
+ }
5
+ function sortKeys(value) {
6
+ if (value === null || value === undefined)
7
+ return value;
8
+ if (Array.isArray(value))
9
+ return value.map(sortKeys);
10
+ if (typeof value === 'object') {
11
+ const obj = value;
12
+ const sorted = {};
13
+ for (const k of Object.keys(obj).sort()) {
14
+ sorted[k] = sortKeys(obj[k]);
15
+ }
16
+ return sorted;
17
+ }
18
+ return value;
19
+ }
@@ -0,0 +1,35 @@
1
+ // Single source of truth for the running app version.
2
+ //
3
+ // Precedence:
4
+ // 1. LATEDEV_APP_VERSION env (set explicitly, or baked into Docker images via
5
+ // `ENV LATEDEV_APP_VERSION=${APP_VERSION}`).
6
+ // 2. The package.json found by walking up from this module — works from
7
+ // dist/server (npm install + Docker) AND the source tree (dev/test), where
8
+ // npm_package_version is unset.
9
+ // 3. npm_package_version (npm-run scripts).
10
+ // 4. '0.0.0'.
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import process from 'node:process';
14
+ import { fileURLToPath } from 'node:url';
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ export function getAppVersion() {
17
+ // '0.0.0' is the Dockerfile ARG default (local builds pass no arg) — treat
18
+ // it as unset so the package.json walk yields the real version.
19
+ if (process.env.LATEDEV_APP_VERSION && process.env.LATEDEV_APP_VERSION !== '0.0.0')
20
+ return process.env.LATEDEV_APP_VERSION;
21
+ let dir = __dirname;
22
+ for (let i = 0; i < 6; i++) {
23
+ try {
24
+ const v = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version;
25
+ if (v)
26
+ return v;
27
+ }
28
+ catch { /* keep climbing */ }
29
+ const parent = path.dirname(dir);
30
+ if (parent === dir)
31
+ break;
32
+ dir = parent;
33
+ }
34
+ return process.env.npm_package_version ?? '0.0.0';
35
+ }
@@ -0,0 +1,2 @@
1
+ // Shared public types between server and web admin UI.
2
+ export {};