serverless-ircd 0.7.0 → 0.8.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 (135) hide show
  1. package/.github/workflows/ci.yml +3 -3
  2. package/.gitmodules +1 -1
  3. package/CHANGELOG.md +273 -29
  4. package/README.md +155 -55
  5. package/apps/aws-stack/package.json +1 -1
  6. package/apps/aws-stack/src/aws-stack.ts +186 -18
  7. package/apps/aws-stack/tests/stack.test.ts +400 -56
  8. package/apps/cf-tcp-container/package.json +1 -1
  9. package/apps/cf-worker/package.json +1 -1
  10. package/apps/cf-worker/src/worker.ts +4 -4
  11. package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
  12. package/apps/cf-worker/tests/smoke.test.ts +5 -5
  13. package/apps/cf-worker/wrangler.test.toml +6 -6
  14. package/apps/cf-worker/wrangler.toml +7 -5
  15. package/apps/local-cli/package.json +1 -1
  16. package/apps/local-cli/src/config-loader.ts +8 -0
  17. package/apps/local-cli/src/main.ts +16 -0
  18. package/apps/local-cli/src/server.ts +1 -0
  19. package/apps/local-cli/tests/config-loader.test.ts +14 -0
  20. package/apps/web/landing/index.html +14 -16
  21. package/apps/web/package.json +2 -2
  22. package/apps/web/scripts/build.mjs +23 -16
  23. package/apps/web/src/build-env.ts +1 -1
  24. package/apps/web/src/config-schema.ts +6 -6
  25. package/apps/web/tests/build-smoke.test.ts +14 -14
  26. package/apps/web/tests/config-schema.test.ts +1 -1
  27. package/docs/AWS-Deployment.md +21 -8
  28. package/docs/Cloudflare-Deployment-Guide.md +22 -6
  29. package/docs/PlanExtensions.md +113 -3
  30. package/docs/Release-Process.md +23 -13
  31. package/docs/Services.md +546 -0
  32. package/docs/WebClientGuide.md +32 -31
  33. package/package.json +2 -2
  34. package/packages/aws-adapter/package.json +1 -1
  35. package/packages/aws-adapter/src/aws-runtime.ts +5 -0
  36. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
  37. package/packages/aws-adapter/src/config-loader.ts +11 -0
  38. package/packages/aws-adapter/src/connection-counter.ts +89 -0
  39. package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
  40. package/packages/aws-adapter/src/handlers/connect.ts +55 -51
  41. package/packages/aws-adapter/src/handlers/default.ts +36 -4
  42. package/packages/aws-adapter/src/handlers/index.ts +15 -0
  43. package/packages/aws-adapter/src/handlers/nlb-stream.ts +15 -0
  44. package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
  45. package/packages/aws-adapter/src/index.ts +4 -0
  46. package/packages/aws-adapter/src/stats.ts +6 -1
  47. package/packages/aws-adapter/src/tables.ts +34 -4
  48. package/packages/aws-adapter/tests/aws-harness.ts +3 -0
  49. package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
  50. package/packages/aws-adapter/tests/connect.test.ts +158 -32
  51. package/packages/aws-adapter/tests/connection-counter.test.ts +127 -0
  52. package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
  53. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
  54. package/packages/aws-adapter/tests/handlers.test.ts +105 -3
  55. package/packages/aws-adapter/tests/tables.test.ts +6 -1
  56. package/packages/cf-adapter/package.json +1 -1
  57. package/packages/cf-adapter/src/config-loader.ts +11 -0
  58. package/packages/cf-adapter/src/connection-do.ts +112 -2
  59. package/packages/cf-adapter/src/d1-services-store.ts +703 -0
  60. package/packages/cf-adapter/src/env.ts +8 -0
  61. package/packages/cf-adapter/src/index.ts +5 -0
  62. package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
  63. package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
  64. package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
  65. package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
  66. package/packages/cf-adapter/tests/serialize.test.ts +1 -0
  67. package/packages/in-memory-runtime/package.json +1 -1
  68. package/packages/irc-core/package.json +1 -1
  69. package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
  70. package/packages/irc-core/src/commands/account-auth.ts +172 -0
  71. package/packages/irc-core/src/commands/chanserv.ts +882 -0
  72. package/packages/irc-core/src/commands/hostserv.ts +487 -0
  73. package/packages/irc-core/src/commands/index.ts +12 -0
  74. package/packages/irc-core/src/commands/join.ts +164 -8
  75. package/packages/irc-core/src/commands/markread.ts +202 -0
  76. package/packages/irc-core/src/commands/memoserv.ts +319 -0
  77. package/packages/irc-core/src/commands/mode.ts +96 -4
  78. package/packages/irc-core/src/commands/nickserv.ts +390 -0
  79. package/packages/irc-core/src/commands/oper.ts +18 -1
  80. package/packages/irc-core/src/commands/operserv.ts +346 -0
  81. package/packages/irc-core/src/commands/pre-away.ts +3 -1
  82. package/packages/irc-core/src/commands/privmsg.ts +42 -0
  83. package/packages/irc-core/src/commands/read-marker.ts +8 -8
  84. package/packages/irc-core/src/commands/registration.ts +61 -6
  85. package/packages/irc-core/src/commands/sasl.ts +18 -49
  86. package/packages/irc-core/src/commands/tagmsg.ts +41 -6
  87. package/packages/irc-core/src/commands/topic.ts +37 -0
  88. package/packages/irc-core/src/config.ts +36 -5
  89. package/packages/irc-core/src/effects.ts +56 -1
  90. package/packages/irc-core/src/ports.ts +1653 -84
  91. package/packages/irc-core/src/protocol/numerics.ts +8 -0
  92. package/packages/irc-core/src/state/channel.ts +21 -1
  93. package/packages/irc-core/src/state/connection.ts +25 -1
  94. package/packages/irc-core/src/types.ts +48 -12
  95. package/packages/irc-core/tests/commands/chanserv.test.ts +1668 -0
  96. package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
  97. package/packages/irc-core/tests/commands/hostserv.test.ts +935 -0
  98. package/packages/irc-core/tests/commands/join.test.ts +393 -1
  99. package/packages/irc-core/tests/commands/markread.test.ts +361 -0
  100. package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
  101. package/packages/irc-core/tests/commands/mode.test.ts +381 -2
  102. package/packages/irc-core/tests/commands/nickserv.test.ts +807 -0
  103. package/packages/irc-core/tests/commands/oper.test.ts +13 -0
  104. package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
  105. package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
  106. package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
  107. package/packages/irc-core/tests/commands/registration.test.ts +788 -14
  108. package/packages/irc-core/tests/commands/sasl.test.ts +185 -12
  109. package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
  110. package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
  111. package/packages/irc-core/tests/commands/topic.test.ts +94 -2
  112. package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
  113. package/packages/irc-core/tests/config.test.ts +49 -5
  114. package/packages/irc-core/tests/effects.test.ts +19 -0
  115. package/packages/irc-core/tests/message-store.test.ts +63 -0
  116. package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
  117. package/packages/irc-core/tests/services-store.test.ts +1289 -0
  118. package/packages/irc-core/tests/state/channel.test.ts +3 -0
  119. package/packages/irc-server/package.json +1 -1
  120. package/packages/irc-server/src/actor.ts +71 -16
  121. package/packages/irc-server/src/dispatch.ts +94 -7
  122. package/packages/irc-server/src/routing.ts +19 -0
  123. package/packages/irc-server/tests/actor.test.ts +623 -12
  124. package/packages/irc-server/tests/dispatch.test.ts +270 -2
  125. package/packages/irc-server/tests/routing.test.ts +6 -0
  126. package/packages/irc-test-support/package.json +1 -1
  127. package/packages/irc-test-support/src/in-memory-harness.ts +29 -3
  128. package/packages/irc-test-support/src/index.ts +1 -0
  129. package/packages/irc-test-support/tests/in-memory-harness.test.ts +32 -0
  130. package/tools/ci-hardening/package.json +1 -1
  131. package/tools/load-test/package.json +1 -1
  132. package/tools/tcp-ws-forwarder/package.json +1 -1
  133. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +2 -2
  134. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +0 -18
  135. package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
@@ -190,6 +190,14 @@ export const Numerics = {
190
190
  ERR_NOOPERHOST: 491,
191
191
  ERR_UMODEUNKNOWNFLAG: 501,
192
192
  ERR_USERSDONTMATCH: 502,
193
+ /**
194
+ * `519 ERR_ALLMUSTREGISTER` — ChanServ `+R` channel mode rejection.
195
+ * Emitted to unidentified clients attempting to JOIN a `+R` channel; the
196
+ * connection must have identified to NickServ (SASL or `IDENTIFY`) to
197
+ * bypass the gate. Surfaced by ChanServ-registered channels that have
198
+ * enabled `SET RESTRICTED` or had `+R` applied directly.
199
+ */
200
+ ERR_ALLMUSTREGISTER: 519,
193
201
  } as const;
194
202
 
195
203
  export type NumericCode = keyof typeof Numerics;
@@ -35,6 +35,13 @@ export interface ChannelTopic {
35
35
  * parameterized modes — `k` (key) and `l` (limit) — are optional and use
36
36
  * `undefined` to signal "unset", exactly matching how `MODE` deltas encode
37
37
  * clearing them.
38
+ *
39
+ * Services-derived modes (settable only via ChanServ, not via `MODE`):
40
+ * - `registered` — channel is registered with ChanServ (`+r`).
41
+ * - `blockUnidentified` — `+R`, blocks join/PRIVMSG from unidentified
42
+ * connections.
43
+ * - `moderatedIdent` — `+M`, moderated speak requires identification
44
+ * (ops/voiced/identified pass).
38
45
  */
39
46
  export interface ChannelModes {
40
47
  // boolean modes
@@ -44,6 +51,10 @@ export interface ChannelModes {
44
51
  moderated: boolean; // m
45
52
  secret: boolean; // s
46
53
  private: boolean; // p
54
+ // services-derived boolean modes (settable only via ChanServ)
55
+ registered: boolean; // r
56
+ blockUnidentified: boolean; // R
57
+ moderatedIdent: boolean; // M
47
58
  // parameterized modes
48
59
  key?: string; // k
49
60
  limit?: number; // l
@@ -56,7 +67,10 @@ export type BooleanChannelMode =
56
67
  | 'noExternal'
57
68
  | 'moderated'
58
69
  | 'secret'
59
- | 'private';
70
+ | 'private'
71
+ | 'registered'
72
+ | 'blockUnidentified'
73
+ | 'moderatedIdent';
60
74
 
61
75
  /** Parameterized mode letters we know how to set/clear. */
62
76
  export type ParameterChannelMode = 'key' | 'limit';
@@ -143,6 +157,9 @@ export function emptyModes(): ChannelModes {
143
157
  moderated: false,
144
158
  secret: false,
145
159
  private: false,
160
+ registered: false,
161
+ blockUnidentified: false,
162
+ moderatedIdent: false,
146
163
  };
147
164
  }
148
165
 
@@ -271,6 +288,9 @@ function applyModeChange(modes: ChannelModes, change: ModeChange): void {
271
288
  case 'moderated':
272
289
  case 'secret':
273
290
  case 'private':
291
+ case 'registered':
292
+ case 'blockUnidentified':
293
+ case 'moderatedIdent':
274
294
  modes[change.mode] = change.set;
275
295
  break;
276
296
  case 'key':
@@ -21,7 +21,8 @@ export type RegistrationState = 'pre-registration' | 'registering' | 'registered
21
21
  /**
22
22
  * User modes applied to a connection. The IRC user-mode letters are
23
23
  * `i` (invisible), `o` (oper), `w` (wallops), `s` (server notices),
24
- * `S` (TLS connected — read-only, set by the transport at registration).
24
+ * `S` (TLS connected — read-only, set by the transport at registration),
25
+ * `r` (registered — read-only, set by NickServ IDENTIFY / SASL login).
25
26
  */
26
27
  export interface UserModes {
27
28
  invisible: boolean;
@@ -35,6 +36,13 @@ export interface UserModes {
35
36
  * settable via `MODE` (the parser rejects `+S`/`-S` with `501`).
36
37
  */
37
38
  tls: boolean;
39
+ /**
40
+ * User mode `r` — the connection is identified to a NickServ account.
41
+ * Read-only: set by the NickServ IDENTIFY reducer (and cleared on
42
+ * account drop / logout). Not settable via `MODE` (the parser rejects
43
+ * `+r`/`-r` with `501`).
44
+ */
45
+ registered: boolean;
38
46
  }
39
47
 
40
48
  /**
@@ -138,6 +146,21 @@ export interface ConnectionState {
138
146
  * ERR_MONLISTFULL` for rejected nicks.
139
147
  */
140
148
  monitorList?: Set<string>;
149
+ /**
150
+ * HostServ vhost state. When true, {@link host} currently carries the
151
+ * per-account vhost assigned via HostServ (rather than the connection's
152
+ * real — cloaked or otherwise — host). Toggled by `HostServ ON` /
153
+ * `HostServ OFF`; absent / false means the connection presents its real
154
+ * host. Persisted so a reconnect retains the toggle until the user (or
155
+ * the actor on identify) reapplies the vhost.
156
+ */
157
+ vhostActive?: boolean;
158
+ /**
159
+ * The real (cloaked) host that was visible before HostServ applied the
160
+ * vhost. `HostServ OFF` restores {@link host} from this field and
161
+ * clears it. Only meaningful while {@link vhostActive} is true.
162
+ */
163
+ preVhostHost?: string;
141
164
  }
142
165
 
143
166
  /**
@@ -185,6 +208,7 @@ export function createConnection(opts: CreateConnectionOptions): ConnectionState
185
208
  wallops: false,
186
209
  serverNotices: false,
187
210
  tls: false,
211
+ registered: false,
188
212
  },
189
213
  lastSeen: opts.lastSeen ?? opts.connectedSince,
190
214
  connectedSince: opts.connectedSince,
@@ -17,7 +17,7 @@ import type {
17
17
  MotdProvider,
18
18
  MtlsIdentityProvider,
19
19
  NickHistoryStore,
20
- ReadMarkerStore,
20
+ ServicesStore,
21
21
  } from './ports.js';
22
22
  import type { IrcMessage } from './protocol/messages.js';
23
23
  import type { ConnectionState } from './state/connection.js';
@@ -195,6 +195,33 @@ export interface ServerConfig {
195
195
  * `exactOptionalPropertyTypes: true`.
196
196
  */
197
197
  readonly servicesEnabled?: boolean | undefined;
198
+ /**
199
+ * Grace period (ms) NickServ applies before force-disconnecting a client
200
+ * that uses a `ghost`-enforced registered nick without identifying. The
201
+ * reducer stamps this value onto the emitted `EnforceNick` effect;
202
+ * `dispatch` schedules the disconnect via its injectable scheduler.
203
+ *
204
+ * Omitted → falls back to {@link DEFAULT_NICK_ENFORCE_GRACE_MS} (30s,
205
+ * matching the Atheme/Anope convention). `0` disables the grace so
206
+ * `ghost` behaves like `kill` (immediate disconnect).
207
+ *
208
+ * Typed `number | undefined` to mirror Zod's optional output shape under
209
+ * `exactOptionalPropertyTypes: true`.
210
+ */
211
+ readonly nickEnforceGraceMs?: number | undefined;
212
+ /**
213
+ * HostServ approval mode for `REQUEST <vhost>`. `true` (the parsed
214
+ * default) preserves the legacy auto-approve behaviour — the vhost is
215
+ * recorded against the account immediately, no oper review. `false`
216
+ * switches HostServ into oper-queue mode: `REQUEST` queues a pending
217
+ * request that an oper must `APPROVE` (or `REJECT`); `LIST` enumerates
218
+ * the pending queue. See `docs/Services.md` §6.
219
+ *
220
+ * Typed `boolean | undefined` so inline `ServerConfig` literals in tests
221
+ * may omit it (the reducer treats `undefined` as "auto-approve on",
222
+ * matching the parsed default).
223
+ */
224
+ readonly hostservAutoApproveVhosts?: boolean | undefined;
198
225
  }
199
226
 
200
227
  /** Per-invocation context handed to every reducer. */
@@ -230,15 +257,6 @@ export interface Ctx {
230
257
  * entries and the WHOWAS reducer replays them.
231
258
  */
232
259
  readonly history?: NickHistoryStore;
233
- /**
234
- * Read-marker persistence source for IRCv3 `draft/read-marker` (absent
235
- * when the deployment does not persist read markers). When present, a
236
- * `+draft/read-marker=<msgid>` TAGMSG from an identified, cap-enabled
237
- * client updates the per-`(account, channel)` marker, and SASL identify
238
- * seeds the connection's `lastReadMarkers` from the stored values so a
239
- * reconnect restores the user's read position.
240
- */
241
- readonly readMarkers?: ReadMarkerStore;
242
260
  /**
243
261
  * Away-status persistence source for IRCv3 `draft/pre-away` (absent
244
262
  * when the deployment does not persist away reasons). When present, a
@@ -251,6 +269,24 @@ export interface Ctx {
251
269
  * connections or restored on reconnect.
252
270
  */
253
271
  readonly away?: AwayStore;
272
+ /**
273
+ * IRC services persistence source for NickServ / ChanServ / HostServ /
274
+ * MemoServ, plus the per-account read-marker persistence for IRCv3
275
+ * `draft/read-marker`. Absent when services are not wired for this
276
+ * deployment. When present, the NickServ reducer answers
277
+ * `PRIVMSG NickServ :…` subcommands (REGISTER / IDENTIFY / DROP / INFO)
278
+ * against this store, a `+draft/read-marker=<msgid>` TAGMSG from an
279
+ * identified, cap-enabled client updates the per-`(account, channel)`
280
+ * marker via {@link ServicesStore.setLastReadMarker}, SASL identify seeds
281
+ * the connection's `lastReadMarkers` from
282
+ * {@link ServicesStore.listReadMarkers} so a reconnect restores the user's
283
+ * read position, and SASL PLAIN falls back to this store for nicks
284
+ * registered via NickServ. Omitted (→ `ctx.services` undefined) preserves
285
+ * the no-services behaviour: `PRIVMSG NickServ :…` is not intercepted,
286
+ * read-marker persistence stays session-only, and SASL PLAIN consults
287
+ * only {@link Ctx.accounts}.
288
+ */
289
+ readonly services?: ServicesStore;
254
290
  /** The connection invoking the command. Reducers mutate this freely. */
255
291
  readonly connection: ConnectionState;
256
292
  /** Convenience: `connection.id`. */
@@ -291,8 +327,8 @@ export interface BuildCtxOptions {
291
327
  mtlsIdentity?: MtlsIdentityProvider;
292
328
  messages?: MessageStore;
293
329
  history?: NickHistoryStore;
294
- readMarkers?: ReadMarkerStore;
295
330
  away?: AwayStore;
331
+ services?: ServicesStore;
296
332
  connection: ConnectionState;
297
333
  /**
298
334
  * IRCv3 `labeled-response` label extracted from the inbound `+label` tag.
@@ -317,8 +353,8 @@ export function buildCtx(opts: BuildCtxOptions): Ctx {
317
353
  ...(opts.mtlsIdentity !== undefined ? { mtlsIdentity: opts.mtlsIdentity } : {}),
318
354
  ...(opts.messages !== undefined ? { messages: opts.messages } : {}),
319
355
  ...(opts.history !== undefined ? { history: opts.history } : {}),
320
- ...(opts.readMarkers !== undefined ? { readMarkers: opts.readMarkers } : {}),
321
356
  ...(opts.away !== undefined ? { away: opts.away } : {}),
357
+ ...(opts.services !== undefined ? { services: opts.services } : {}),
322
358
  ...(opts.label !== undefined ? { label: opts.label } : {}),
323
359
  };
324
360
  return ctx;