serverless-ircd 0.10.0 → 0.11.0

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 (192) hide show
  1. package/.github/workflows/ci.yml +28 -0
  2. package/.github/workflows/deploy-cf-tcp.yml +26 -2
  3. package/.github/workflows/deploy-cf.yml +26 -0
  4. package/CHANGELOG.md +289 -0
  5. package/README.md +153 -20
  6. package/apps/aws-stack/bin/aws.ts +36 -0
  7. package/apps/aws-stack/package.json +1 -1
  8. package/apps/aws-stack/src/aws-stack.ts +221 -15
  9. package/apps/aws-stack/tests/stack.test.ts +450 -16
  10. package/apps/cf-tcp-container/Dockerfile +37 -5
  11. package/apps/cf-tcp-container/package.json +7 -2
  12. package/apps/cf-tcp-container/src/config-loader.ts +113 -2
  13. package/apps/cf-tcp-container/src/container-server.ts +256 -79
  14. package/apps/cf-tcp-container/src/main.ts +22 -7
  15. package/apps/cf-tcp-container/src/proxy-protocol.ts +112 -0
  16. package/apps/cf-tcp-container/terraform/spectrum.tf +40 -11
  17. package/apps/cf-tcp-container/tests/config-loader.test.ts +170 -0
  18. package/apps/cf-tcp-container/tests/container-server-tls.test.ts +382 -0
  19. package/apps/cf-tcp-container/tests/container-server.test.ts +358 -31
  20. package/apps/cf-tcp-container/tests/dockerfile.test.ts +110 -0
  21. package/apps/cf-tcp-container/tests/proxy-protocol.test.ts +187 -0
  22. package/apps/cf-tcp-container/tests/spectrum-terraform.test.ts +135 -0
  23. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +5 -1
  24. package/apps/cf-tcp-container/wrangler.toml +17 -4
  25. package/apps/cf-worker/package.json +2 -2
  26. package/apps/cf-worker/src/worker.ts +77 -5
  27. package/apps/cf-worker/tests/raw-modules.d.ts +11 -0
  28. package/apps/cf-worker/tests/smoke.test.ts +4 -0
  29. package/apps/cf-worker/tests/wrangler-config.test.ts +47 -0
  30. package/apps/cf-worker/tests/ws-admission.test.ts +112 -0
  31. package/apps/cf-worker/tests/ws-rate-limit.test.ts +133 -0
  32. package/apps/cf-worker/wrangler.test.toml +15 -1
  33. package/apps/cf-worker/wrangler.toml +86 -9
  34. package/apps/local-cli/package.json +1 -1
  35. package/apps/local-cli/src/config-loader.ts +14 -2
  36. package/apps/local-cli/src/line-scanner.ts +26 -0
  37. package/apps/local-cli/src/server.ts +23 -2
  38. package/apps/local-cli/tests/line-scanner.test.ts +64 -0
  39. package/apps/local-cli/tests/tcp.test.ts +29 -0
  40. package/apps/web/package.json +1 -1
  41. package/docs/AWS-Deployment.md +123 -22
  42. package/docs/AWS-TCP-Deployment.md +37 -2
  43. package/docs/Chat-History.md +55 -0
  44. package/docs/Cloudflare-Deployment-Guide.md +9 -2
  45. package/docs/Cloudflare-TCP-Deployment.md +135 -52
  46. package/docs/SASL-EXTERNAL.md +175 -0
  47. package/package.json +3 -3
  48. package/packages/aws-adapter/package.json +1 -1
  49. package/packages/aws-adapter/src/admission.ts +28 -13
  50. package/packages/aws-adapter/src/aws-runtime.ts +30 -3
  51. package/packages/aws-adapter/src/cdk-table-defs.ts +34 -6
  52. package/packages/aws-adapter/src/config-loader.ts +134 -6
  53. package/packages/aws-adapter/src/dynamo-services-store.ts +12 -0
  54. package/packages/aws-adapter/src/handlers/connect.ts +47 -1
  55. package/packages/aws-adapter/src/handlers/default.ts +95 -6
  56. package/packages/aws-adapter/src/handlers/index.ts +31 -2
  57. package/packages/aws-adapter/src/handlers/nlb-stream.ts +132 -8
  58. package/packages/aws-adapter/src/ip-admission.ts +79 -0
  59. package/packages/aws-adapter/src/serialize.ts +8 -0
  60. package/packages/aws-adapter/src/tables.ts +9 -0
  61. package/packages/aws-adapter/tests/admission.test.ts +60 -2
  62. package/packages/aws-adapter/tests/aws-harness.ts +23 -1
  63. package/packages/aws-adapter/tests/aws-runtime.test.ts +64 -0
  64. package/packages/aws-adapter/tests/config-loader.test.ts +151 -0
  65. package/packages/aws-adapter/tests/connect.test.ts +199 -2
  66. package/packages/aws-adapter/tests/default-frame-limit.test.ts +231 -0
  67. package/packages/aws-adapter/tests/default-occ.test.ts +10 -3
  68. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +123 -1
  69. package/packages/aws-adapter/tests/handlers.test.ts +57 -1
  70. package/packages/aws-adapter/tests/nlb-secure.test.ts +362 -0
  71. package/packages/aws-adapter/tests/nlb-stream.test.ts +628 -9
  72. package/packages/cf-adapter/package.json +1 -1
  73. package/packages/cf-adapter/src/cf-runtime.ts +48 -9
  74. package/packages/cf-adapter/src/config-loader.ts +133 -8
  75. package/packages/cf-adapter/src/connection-do.ts +154 -21
  76. package/packages/cf-adapter/src/counter-do.ts +142 -0
  77. package/packages/cf-adapter/src/d1-services-store.ts +47 -5
  78. package/packages/cf-adapter/src/env.ts +88 -0
  79. package/packages/cf-adapter/src/index.ts +17 -1
  80. package/packages/cf-adapter/src/rate-limit-do.ts +87 -0
  81. package/packages/cf-adapter/tests/cf-runtime.test.ts +104 -15
  82. package/packages/cf-adapter/tests/config-loader.test.ts +159 -0
  83. package/packages/cf-adapter/tests/connection-do-counter.test.ts +165 -0
  84. package/packages/cf-adapter/tests/connection-do-frame-limit.test.ts +177 -0
  85. package/packages/cf-adapter/tests/connection-do-pure.test.ts +74 -5
  86. package/packages/cf-adapter/tests/connection-do-ws-spec-contract.test.ts +7 -4
  87. package/packages/cf-adapter/tests/counter-do.test.ts +181 -0
  88. package/packages/cf-adapter/tests/d1-services-store.test.ts +192 -1
  89. package/packages/cf-adapter/tests/rate-limit-do.test.ts +160 -0
  90. package/packages/cf-adapter/tests/worker/main.ts +4 -0
  91. package/packages/cf-adapter/wrangler.test.toml +18 -1
  92. package/packages/in-memory-runtime/package.json +1 -1
  93. package/packages/in-memory-runtime/src/in-memory-runtime.ts +25 -0
  94. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +74 -0
  95. package/packages/irc-core/package.json +1 -1
  96. package/packages/irc-core/src/caps/capabilities.ts +20 -10
  97. package/packages/irc-core/src/certfp.ts +178 -0
  98. package/packages/irc-core/src/commands/cap.ts +10 -2
  99. package/packages/irc-core/src/commands/chanserv.ts +117 -14
  100. package/packages/irc-core/src/commands/chathistory.ts +13 -5
  101. package/packages/irc-core/src/commands/hostserv.ts +84 -8
  102. package/packages/irc-core/src/commands/index.ts +2 -1
  103. package/packages/irc-core/src/commands/invite.ts +1 -7
  104. package/packages/irc-core/src/commands/join.ts +1 -16
  105. package/packages/irc-core/src/commands/kick.ts +1 -8
  106. package/packages/irc-core/src/commands/list.ts +1 -8
  107. package/packages/irc-core/src/commands/mode.ts +1 -8
  108. package/packages/irc-core/src/commands/multiline.ts +4 -10
  109. package/packages/irc-core/src/commands/names.ts +53 -13
  110. package/packages/irc-core/src/commands/nickserv.ts +40 -1
  111. package/packages/irc-core/src/commands/oper.ts +361 -8
  112. package/packages/irc-core/src/commands/part.ts +4 -10
  113. package/packages/irc-core/src/commands/privmsg.ts +8 -4
  114. package/packages/irc-core/src/commands/registration.ts +146 -2
  115. package/packages/irc-core/src/commands/sasl.ts +136 -19
  116. package/packages/irc-core/src/commands/topic.ts +10 -12
  117. package/packages/irc-core/src/commands/who.ts +1 -8
  118. package/packages/irc-core/src/config.ts +393 -20
  119. package/packages/irc-core/src/effects.ts +24 -0
  120. package/packages/irc-core/src/flood-control.ts +10 -10
  121. package/packages/irc-core/src/frame-rate-limit.ts +82 -0
  122. package/packages/irc-core/src/index.ts +8 -0
  123. package/packages/irc-core/src/oper-hashing.ts +43 -0
  124. package/packages/irc-core/src/oper-lockout.ts +87 -0
  125. package/packages/irc-core/src/ports.ts +395 -36
  126. package/packages/irc-core/src/protocol/bytes.ts +65 -0
  127. package/packages/irc-core/src/protocol/channel-name.ts +37 -0
  128. package/packages/irc-core/src/protocol/index.ts +12 -1
  129. package/packages/irc-core/src/protocol/outbound.ts +43 -10
  130. package/packages/irc-core/src/protocol/parser.ts +79 -10
  131. package/packages/irc-core/src/state/connection.ts +13 -0
  132. package/packages/irc-core/src/types.ts +228 -13
  133. package/packages/irc-core/src/ws-framing.ts +5 -4
  134. package/packages/irc-core/tests/bytes.test.ts +89 -0
  135. package/packages/irc-core/tests/certfp.test.ts +117 -0
  136. package/packages/irc-core/tests/commands/cap.test.ts +76 -2
  137. package/packages/irc-core/tests/commands/chanserv.test.ts +166 -0
  138. package/packages/irc-core/tests/commands/chathistory.test.ts +140 -0
  139. package/packages/irc-core/tests/commands/hostserv.test.ts +316 -0
  140. package/packages/irc-core/tests/commands/join.test.ts +78 -1
  141. package/packages/irc-core/tests/commands/names.test.ts +193 -0
  142. package/packages/irc-core/tests/commands/nickserv.test.ts +182 -2
  143. package/packages/irc-core/tests/commands/oper.test.ts +560 -2
  144. package/packages/irc-core/tests/commands/privmsg.test.ts +16 -0
  145. package/packages/irc-core/tests/commands/registration.test.ts +463 -1
  146. package/packages/irc-core/tests/commands/sasl.test.ts +596 -7
  147. package/packages/irc-core/tests/commands/topic.test.ts +137 -2
  148. package/packages/irc-core/tests/commands/unified-account.test.ts +2 -0
  149. package/packages/irc-core/tests/config.test.ts +534 -2
  150. package/packages/irc-core/tests/effects.test.ts +14 -0
  151. package/packages/irc-core/tests/flood-control.test.ts +29 -1
  152. package/packages/irc-core/tests/frame-rate-limit.test.ts +98 -0
  153. package/packages/irc-core/tests/oper-hashing.test.ts +60 -0
  154. package/packages/irc-core/tests/oper-lockout.test.ts +74 -0
  155. package/packages/irc-core/tests/outbound.test.ts +148 -0
  156. package/packages/irc-core/tests/parser.test.ts +287 -5
  157. package/packages/irc-core/tests/persistent-services-store.test.ts +141 -0
  158. package/packages/irc-core/tests/ports.test.ts +99 -7
  159. package/packages/irc-core/tests/services-store.test.ts +376 -14
  160. package/packages/irc-core/tests/ws-framing.test.ts +45 -0
  161. package/packages/irc-server/package.json +1 -1
  162. package/packages/irc-server/src/actor.ts +123 -8
  163. package/packages/irc-server/src/dispatch.ts +1 -0
  164. package/packages/irc-server/src/index.ts +7 -0
  165. package/packages/irc-server/src/redact.ts +159 -0
  166. package/packages/irc-server/src/runtime.ts +14 -0
  167. package/packages/irc-server/src/transport.ts +28 -1
  168. package/packages/irc-server/tests/actor.test.ts +544 -7
  169. package/packages/irc-server/tests/dispatch.test.ts +31 -0
  170. package/packages/irc-server/tests/redact.test.ts +198 -0
  171. package/packages/irc-server/tests/runtime.test.ts +2 -0
  172. package/packages/irc-server/tests/transport.test.ts +66 -0
  173. package/packages/irc-test-support/package.json +1 -1
  174. package/packages/irc-test-support/src/in-memory-harness.ts +4 -0
  175. package/scripts/package.json +1 -1
  176. package/tools/ci-hardening/package.json +2 -2
  177. package/tools/ci-hardening/src/cf-deploy-cli.ts +3 -0
  178. package/tools/ci-hardening/src/cf-deploy.ts +118 -0
  179. package/tools/ci-hardening/src/deploy-hostname.ts +118 -0
  180. package/tools/ci-hardening/src/env-var-drift.ts +192 -0
  181. package/tools/ci-hardening/src/hostname-guard.ts +11 -0
  182. package/tools/ci-hardening/src/index.ts +17 -0
  183. package/tools/ci-hardening/tests/__wrangler_missing__.toml +2 -0
  184. package/tools/ci-hardening/tests/__wrangler_placeholder__.toml +3 -0
  185. package/tools/ci-hardening/tests/__wrangler_real__.toml +3 -0
  186. package/tools/ci-hardening/tests/cf-deploy.test.ts +200 -0
  187. package/tools/ci-hardening/tests/deploy-hostname.test.ts +348 -0
  188. package/tools/ci-hardening/tests/env-var-drift.test.ts +284 -0
  189. package/tools/ci-hardening/vitest.config.ts +5 -1
  190. package/tools/hash-oper-cred.ts +85 -0
  191. package/tools/load-test/package.json +1 -1
  192. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -29,9 +29,10 @@ function makeCtx(
29
29
  conn: ConnectionState,
30
30
  clock = new FakeClock(1_000),
31
31
  services?: ServicesStore,
32
+ config: ServerConfig = serverConfig,
32
33
  ): Ctx {
33
34
  return buildCtx({
34
- serverConfig,
35
+ serverConfig: config,
35
36
  clock,
36
37
  ids: new SequentialIdFactory(),
37
38
  motd: EmptyMotdProvider,
@@ -182,6 +183,21 @@ describe('topicReducer — writes', () => {
182
183
  expect(out.state.topic?.text).toBe('a'.repeat(serverConfig.topicLen));
183
184
  });
184
185
 
186
+ it('keeps an ASCII topic of exactly topicLen bytes unchanged (no truncation)', () => {
187
+ const chan = makeChan('#foo');
188
+ addMember(chan, 'c1', 'alice');
189
+ const conn = makeConn();
190
+ const ctx = makeCtx(conn, new FakeClock(1_000), undefined, {
191
+ ...serverConfig,
192
+ topicLen: 64,
193
+ });
194
+
195
+ const topic = 'a'.repeat(64);
196
+ const out = topicReducer(chan, { command: 'TOPIC', params: ['#foo', topic], tags: {} }, ctx);
197
+
198
+ expect(out.state.topic?.text).toBe(topic);
199
+ });
200
+
185
201
  it('emits 482 ERR_CHANOPRIVSNEEDED when +t is set and the writer is a non-op member', () => {
186
202
  const chan = makeChan('#foo');
187
203
  chan.modes.topicLock = true;
@@ -238,9 +254,91 @@ describe('topicReducer — writes', () => {
238
254
  });
239
255
 
240
256
  // ============================================================================
241
- // topicReducer — rejections
257
+ // topicReducer — byte-budget truncation (TOPICLEN as a UTF-8 byte ceiling)
242
258
  // ============================================================================
243
259
 
260
+ describe('topicReducer — byte-budget truncation', () => {
261
+ const byteCtx = (conn: ConnectionState): Ctx =>
262
+ makeCtx(conn, new FakeClock(1_000), undefined, { ...serverConfig, topicLen: 64 });
263
+
264
+ it('truncates a multi-byte Latin-1 topic to the byte budget, not the char budget', () => {
265
+ const chan = makeChan('#foo');
266
+ addMember(chan, 'c1', 'alice');
267
+ const conn = makeConn();
268
+ const ctx = byteCtx(conn);
269
+
270
+ // 50 `é` chars = 50 UTF-16 units (under the 64 char budget) but 100
271
+ // UTF-8 bytes (over the 64 byte budget). Char-based slicing keeps all
272
+ // 50; byte-based truncation must keep exactly 32 (64 bytes).
273
+ const out = topicReducer(
274
+ chan,
275
+ { command: 'TOPIC', params: ['#foo', 'é'.repeat(50)], tags: {} },
276
+ ctx,
277
+ );
278
+
279
+ expect(out.state.topic?.text).toBe('é'.repeat(32));
280
+ });
281
+
282
+ it('backs off to a complete-char boundary when the budget lands mid-CJK-char', () => {
283
+ const chan = makeChan('#foo');
284
+ addMember(chan, 'c1', 'alice');
285
+ const conn = makeConn();
286
+ const ctx = byteCtx(conn);
287
+
288
+ // `好` is 3 UTF-8 bytes. 64 bytes fits 21 whole chars (63 bytes); the
289
+ // 22nd would need 66 bytes, so it is dropped whole and the result is a
290
+ // valid-UTF-8 string 1 byte under budget — never split mid-char.
291
+ const out = topicReducer(
292
+ chan,
293
+ { command: 'TOPIC', params: ['#foo', '好'.repeat(30)], tags: {} },
294
+ ctx,
295
+ );
296
+
297
+ expect(out.state.topic?.text).toBe('好'.repeat(21));
298
+ });
299
+
300
+ it('truncates an emoji topic without splitting surrogate pairs', () => {
301
+ const chan = makeChan('#foo');
302
+ addMember(chan, 'c1', 'alice');
303
+ const conn = makeConn();
304
+ const ctx = byteCtx(conn);
305
+
306
+ // `😀` is astral: 2 UTF-16 code units and 4 UTF-8 bytes each. 20 emoji
307
+ // = 40 UTF-16 units (under 64 chars) but 80 bytes (over budget). The
308
+ // byte budget fits exactly 16 emoji (64 bytes); a naive UTF-16 slice
309
+ // at 64 units would keep all 20, and a naive byte slice at 64 would
310
+ // split a surrogate pair.
311
+ const out = topicReducer(
312
+ chan,
313
+ { command: 'TOPIC', params: ['#foo', '😀'.repeat(20)], tags: {} },
314
+ ctx,
315
+ );
316
+
317
+ expect(out.state.topic?.text).toBe('😀'.repeat(16));
318
+ });
319
+
320
+ it('broadcasts and persists the byte-truncated text', () => {
321
+ const chan = makeChan('#foo');
322
+ addMember(chan, 'c1', 'alice');
323
+ const conn = makeConn();
324
+ const ctx = byteCtx(conn);
325
+
326
+ const out = topicReducer(
327
+ chan,
328
+ { command: 'TOPIC', params: ['#foo', 'é'.repeat(50)], tags: {} },
329
+ ctx,
330
+ );
331
+
332
+ const truncated = 'é'.repeat(32);
333
+ expect(out.effects).toEqual<EffectType[]>([
334
+ Effect.applyChannelDelta('#foo', {
335
+ topic: { text: truncated, setter: 'alice!alice@example.com', setAt: 1_000 },
336
+ }),
337
+ Effect.broadcast('#foo', [L(`:alice!alice@example.com TOPIC #foo :${truncated}`)]),
338
+ ]);
339
+ });
340
+ });
341
+
244
342
  describe('topicReducer — rejections', () => {
245
343
  it('emits 461 ERR_NEEDMOREPARAMS when no channel is supplied', () => {
246
344
  const chan = makeChan('#foo');
@@ -279,6 +377,43 @@ describe('topicReducer — rejections', () => {
279
377
  ]);
280
378
  });
281
379
 
380
+ it('emits 403 ERR_NOSUCHCHANNEL for a channel name containing NUL', () => {
381
+ const chan = makeChan('#foo');
382
+ const conn = makeConn();
383
+ const ctx = makeCtx(conn);
384
+
385
+ const out = topicReducer(chan, { command: 'TOPIC', params: ['#foo\0bar'], tags: {} }, ctx);
386
+
387
+ expect(out.effects).toEqual<EffectType[]>([
388
+ Effect.send('c1', [L(':irc.example.com 403 alice #foo\0bar :No such channel')]),
389
+ ]);
390
+ });
391
+
392
+ it('emits 403 ERR_NOSUCHCHANNEL for a channel name containing a control character', () => {
393
+ const chan = makeChan('#foo');
394
+ const conn = makeConn();
395
+ const ctx = makeCtx(conn);
396
+
397
+ const out = topicReducer(chan, { command: 'TOPIC', params: ['#foo\x1fbar'], tags: {} }, ctx);
398
+
399
+ expect(out.effects).toEqual<EffectType[]>([
400
+ Effect.send('c1', [L(':irc.example.com 403 alice #foo\x1fbar :No such channel')]),
401
+ ]);
402
+ });
403
+
404
+ it('emits 403 ERR_NOSUCHCHANNEL for a channel name whose UTF-8 byte size exceeds the cap', () => {
405
+ const long = `#${'é'.repeat(25)}`; // 51 UTF-8 bytes, only 26 UTF-16 units
406
+ const chan = makeChan(long);
407
+ const conn = makeConn();
408
+ const ctx = makeCtx(conn);
409
+
410
+ const out = topicReducer(chan, { command: 'TOPIC', params: [long], tags: {} }, ctx);
411
+
412
+ expect(out.effects).toEqual<EffectType[]>([
413
+ Effect.send('c1', [L(`:irc.example.com 403 alice ${long} :No such channel`)]),
414
+ ]);
415
+ });
416
+
282
417
  it('emits 403 ERR_NOSUCHCHANNEL for an empty channel name', () => {
283
418
  const chan = makeChan('#foo');
284
419
  const conn = makeConn();
@@ -409,6 +409,8 @@ describe('T156 unified account: scrypt-hashed credentials at rest', () => {
409
409
  createdAt: NOW,
410
410
  enforce: 'none',
411
411
  certSubjects: [],
412
+ identifyFailures: [],
413
+ pendingIdentifyFailureNotice: 0,
412
414
  // Malformed credential — empty hash, rejected by verifyHashedPassword.
413
415
  credential: { account: 'legacy', algorithm: 'scrypt', salt: 's', hash: '' },
414
416
  },