serverless-ircd 0.8.0 → 0.10.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 (179) hide show
  1. package/.github/workflows/ci.yml +4 -0
  2. package/.github/workflows/deploy-aws.yml +156 -32
  3. package/.github/workflows/deploy-cf-tcp.yml +11 -9
  4. package/.github/workflows/deploy-cf.yml +14 -14
  5. package/CHANGELOG.md +550 -0
  6. package/README.md +275 -222
  7. package/apps/aws-stack/README.md +3 -5
  8. package/apps/aws-stack/bin/aws.ts +82 -9
  9. package/apps/aws-stack/cdk.json +0 -3
  10. package/apps/aws-stack/package.json +3 -4
  11. package/apps/aws-stack/src/aws-stack.ts +177 -52
  12. package/apps/aws-stack/src/static-site.ts +323 -0
  13. package/apps/aws-stack/tests/smoke-helpers.test.ts +1 -1
  14. package/apps/aws-stack/tests/stack.test.ts +267 -92
  15. package/apps/aws-stack/tests/static-site.test.ts +491 -0
  16. package/apps/aws-stack/tests/synth-no-bundle.test.ts +0 -1
  17. package/apps/cf-tcp-container/package.json +2 -3
  18. package/apps/cf-tcp-container/src/container-server.ts +33 -10
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
  20. package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
  21. package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
  22. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
  23. package/apps/cf-tcp-container/wrangler.toml +1 -10
  24. package/apps/cf-worker/package.json +3 -4
  25. package/apps/cf-worker/wrangler.toml +12 -71
  26. package/apps/local-cli/package.json +1 -1
  27. package/apps/local-cli/src/server.ts +115 -48
  28. package/apps/local-cli/tests/config-resolution.test.ts +65 -0
  29. package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
  30. package/apps/local-cli/tests/rehash.test.ts +147 -0
  31. package/apps/local-cli/tests/server-helpers.test.ts +63 -0
  32. package/apps/local-cli/tests/tcp.test.ts +89 -0
  33. package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
  34. package/apps/web/landing/favicon.ico +0 -0
  35. package/apps/web/landing/index.html +227 -3
  36. package/apps/web/package.json +3 -2
  37. package/apps/web/scripts/build.mjs +91 -6
  38. package/apps/web/src/build-env.ts +125 -4
  39. package/apps/web/src/config-schema.ts +20 -6
  40. package/apps/web/src/render-docs.ts +292 -0
  41. package/apps/web/static/{config.staging.json → config.prod-aws.json} +3 -2
  42. package/apps/web/tests/build-env.test.ts +210 -9
  43. package/apps/web/tests/build-smoke.test.ts +33 -4
  44. package/apps/web/tests/config-schema.test.ts +149 -25
  45. package/apps/web/tests/landing-content.test.ts +103 -0
  46. package/apps/web/tests/render-docs.test.ts +198 -0
  47. package/docs/AWS-Adapter-Architecture.md +3 -2
  48. package/docs/AWS-Deployment.md +670 -96
  49. package/docs/AWS-TCP-Deployment.md +20 -45
  50. package/docs/Cloudflare-Deployment-Guide.md +87 -113
  51. package/docs/Cloudflare-TCP-Deployment.md +25 -49
  52. package/docs/Release-Process.md +27 -23
  53. package/docs/Services.md +102 -23
  54. package/docs/WebClientGuide.md +35 -26
  55. package/package.json +7 -10
  56. package/packages/aws-adapter/package.json +1 -1
  57. package/packages/aws-adapter/src/aws-runtime.ts +15 -1
  58. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -11
  59. package/packages/aws-adapter/src/config-loader.ts +19 -2
  60. package/packages/aws-adapter/src/dynamo-services-store.ts +7 -0
  61. package/packages/aws-adapter/src/handlers/connect.ts +26 -0
  62. package/packages/aws-adapter/src/handlers/default.ts +190 -123
  63. package/packages/aws-adapter/src/handlers/index.ts +67 -23
  64. package/packages/aws-adapter/src/handlers/nlb-stream.ts +13 -8
  65. package/packages/aws-adapter/src/index.ts +5 -7
  66. package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
  67. package/packages/aws-adapter/src/serialize.ts +15 -0
  68. package/packages/aws-adapter/src/tables.ts +2 -12
  69. package/packages/aws-adapter/tests/aws-harness.ts +0 -1
  70. package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
  71. package/packages/aws-adapter/tests/config-loader.test.ts +66 -0
  72. package/packages/aws-adapter/tests/connect.test.ts +124 -1
  73. package/packages/aws-adapter/tests/connection-counter.test.ts +17 -0
  74. package/packages/aws-adapter/tests/default-occ.test.ts +219 -0
  75. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +11 -0
  76. package/packages/aws-adapter/tests/global-setup.ts +28 -1
  77. package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
  78. package/packages/aws-adapter/tests/handlers.test.ts +117 -11
  79. package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
  80. package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
  81. package/packages/aws-adapter/tests/origin-allowlist.test.ts +110 -0
  82. package/packages/aws-adapter/tests/ping-checker.test.ts +0 -1
  83. package/packages/aws-adapter/tests/stats.test.ts +0 -3
  84. package/packages/aws-adapter/tests/sweeper.test.ts +20 -1
  85. package/packages/aws-adapter/tests/tables.test.ts +1 -8
  86. package/packages/aws-adapter/tests/transactions.test.ts +0 -1
  87. package/packages/cf-adapter/package.json +1 -5
  88. package/packages/cf-adapter/src/cf-runtime.ts +59 -8
  89. package/packages/cf-adapter/src/channel-do.ts +13 -3
  90. package/packages/cf-adapter/src/connection-do.ts +284 -115
  91. package/packages/cf-adapter/src/d1-services-store.ts +63 -26
  92. package/packages/cf-adapter/src/env.ts +11 -10
  93. package/packages/cf-adapter/src/index.ts +0 -6
  94. package/packages/cf-adapter/tests/cf-runtime.test.ts +101 -1
  95. package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
  96. package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
  97. package/packages/cf-adapter/tests/connection-do-pure.test.ts +222 -51
  98. package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
  99. package/packages/cf-adapter/tests/d1-services-store.test.ts +53 -2
  100. package/packages/cf-adapter/tests/serialize.test.ts +25 -0
  101. package/packages/in-memory-runtime/package.json +1 -1
  102. package/packages/irc-core/package.json +1 -1
  103. package/packages/irc-core/src/account-migration.ts +140 -0
  104. package/packages/irc-core/src/commands/account-auth.ts +60 -35
  105. package/packages/irc-core/src/commands/chanserv.ts +288 -4
  106. package/packages/irc-core/src/commands/hostserv.ts +38 -3
  107. package/packages/irc-core/src/commands/index.ts +1 -0
  108. package/packages/irc-core/src/commands/join.ts +41 -35
  109. package/packages/irc-core/src/commands/memoserv.ts +1 -1
  110. package/packages/irc-core/src/commands/nickserv.ts +138 -15
  111. package/packages/irc-core/src/commands/registration.ts +28 -17
  112. package/packages/irc-core/src/commands/sasl.ts +22 -31
  113. package/packages/irc-core/src/commands/service-aliases.ts +52 -0
  114. package/packages/irc-core/src/commands/topic.ts +23 -10
  115. package/packages/irc-core/src/config.ts +35 -9
  116. package/packages/irc-core/src/credential-hashing.ts +11 -54
  117. package/packages/irc-core/src/index.ts +1 -0
  118. package/packages/irc-core/src/ports.ts +159 -179
  119. package/packages/irc-core/src/state/channel.ts +17 -0
  120. package/packages/irc-core/src/types.ts +38 -10
  121. package/packages/irc-core/tests/account-migration.test.ts +133 -0
  122. package/packages/irc-core/tests/commands/chanserv.test.ts +668 -1
  123. package/packages/irc-core/tests/commands/hostserv.test.ts +71 -0
  124. package/packages/irc-core/tests/commands/join.test.ts +179 -0
  125. package/packages/irc-core/tests/commands/markread.test.ts +54 -0
  126. package/packages/irc-core/tests/commands/memoserv.test.ts +19 -0
  127. package/packages/irc-core/tests/commands/nickserv.test.ts +422 -3
  128. package/packages/irc-core/tests/commands/oper.test.ts +15 -0
  129. package/packages/irc-core/tests/commands/registration.test.ts +336 -108
  130. package/packages/irc-core/tests/commands/sasl.test.ts +194 -169
  131. package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
  132. package/packages/irc-core/tests/commands/unified-account.test.ts +102 -84
  133. package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
  134. package/packages/irc-core/tests/message-store.test.ts +5 -0
  135. package/packages/irc-core/tests/persistent-services-store.test.ts +71 -12
  136. package/packages/irc-core/tests/ports.test.ts +71 -0
  137. package/packages/irc-core/tests/services-store.test.ts +204 -0
  138. package/packages/irc-core/vitest.config.ts +6 -1
  139. package/packages/irc-server/package.json +1 -1
  140. package/packages/irc-server/src/actor.ts +80 -44
  141. package/packages/irc-server/tests/actor.test.ts +384 -50
  142. package/packages/irc-test-support/package.json +1 -1
  143. package/packages/irc-test-support/src/in-memory-harness.ts +8 -5
  144. package/packages/irc-test-support/src/scenarios.ts +21 -6
  145. package/packages/irc-test-support/tests/in-memory-harness.test.ts +19 -0
  146. package/packages/irc-test-support/vitest.config.ts +6 -1
  147. package/pnpm-workspace.yaml +1 -0
  148. package/scripts/__tests__/deploy-web-aws.test.ts +491 -0
  149. package/scripts/deploy-web-aws.mjs +290 -0
  150. package/scripts/package.json +23 -0
  151. package/scripts/tsconfig.test.json +12 -0
  152. package/scripts/vitest.config.ts +19 -0
  153. package/tools/ci-hardening/package.json +1 -1
  154. package/tools/ci-hardening/src/index.ts +2 -0
  155. package/tools/ci-hardening/src/validate.ts +57 -0
  156. package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
  157. package/tools/ci-hardening/tests/validate.test.ts +42 -0
  158. package/tools/load-test/package.json +1 -1
  159. package/tools/load-test/src/client.ts +13 -13
  160. package/tools/load-test/tests/client.test.ts +258 -2
  161. package/tools/load-test/tests/config.test.ts +39 -0
  162. package/tools/load-test/tests/harness.test.ts +21 -0
  163. package/tools/load-test/tests/metrics.test.ts +7 -0
  164. package/tools/migrate-accounts-to-services.ts +270 -0
  165. package/tools/package.json +2 -1
  166. package/tools/seed-aws-accounts.ts +35 -10
  167. package/tools/seed-cf-accounts.ts +42 -9
  168. package/tools/tcp-ws-forwarder/package.json +1 -1
  169. package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
  170. package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
  171. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +51 -0
  172. package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
  173. package/packages/aws-adapter/src/account-store.ts +0 -121
  174. package/packages/aws-adapter/src/dynamo-account-store.ts +0 -95
  175. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +0 -223
  176. package/packages/aws-adapter/tests/account-store.test.ts +0 -276
  177. package/packages/cf-adapter/src/d1-account-store.ts +0 -198
  178. package/packages/cf-adapter/tests/d1-account-store.test.ts +0 -274
  179. package/packages/irc-core/tests/account-store.test.ts +0 -131
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Service shortcut command aliases (Atheme/Anope convention).
3
+ *
4
+ * Clients and user expectations support shortcut verbs like
5
+ * `NICKSERV REGISTER pw` as an alias for `PRIVMSG NickServ :REGISTER pw`.
6
+ * These are not RFC-standardized but are a de facto convention from
7
+ * services packages (Atheme/Anope) and IRCd alias modules (InspIRCd
8
+ * `m_alias`, UnrealIRCd `alias{}`).
9
+ *
10
+ * The actor's dispatcher rewrites any of these verbs to a
11
+ * `PRIVMSG <ServiceNick> :<joined args>` and delegates to the existing
12
+ * `routeMessageTarget` path, so the service reducers see the canonical
13
+ * `params=[serviceNick, body]` shape unchanged.
14
+ *
15
+ * This module is the **single source of truth** for the alias → service
16
+ * nick mapping. Both the dispatcher rewrite (in `irc-server`'s actor) and
17
+ * the service-nick recognition in `serviceReducerFor` consult the derived
18
+ * {@link SERVICE_NICKS} list rather than maintaining their own copies.
19
+ */
20
+
21
+ import { CHANSERV_NICK } from './chanserv.js';
22
+ import { HOSTSERV_NICK } from './hostserv.js';
23
+ import { MEMOSERV_NICK } from './memoserv.js';
24
+ import { NICKSERV_NICK } from './nickserv.js';
25
+ import { OPERSERV_NICK } from './operserv.js';
26
+
27
+ /**
28
+ * Shortcut command verb (uppercased) → canonical service nick. Keyed by
29
+ * uppercased command so the dispatcher's `msg.command.toUpperCase()` lookup
30
+ * is case-insensitive. The full set of Atheme/Anope-convention aliases.
31
+ */
32
+ export const SHORTCUT_TO_SERVICE: ReadonlyMap<string, string> = new Map<string, string>([
33
+ ['NICKSERV', NICKSERV_NICK],
34
+ ['NS', NICKSERV_NICK],
35
+ ['CHANSERV', CHANSERV_NICK],
36
+ ['CS', CHANSERV_NICK],
37
+ ['HOSTSERV', HOSTSERV_NICK],
38
+ ['HS', HOSTSERV_NICK],
39
+ ['MEMOSERV', MEMOSERV_NICK],
40
+ ['MS', MEMOSERV_NICK],
41
+ ['OPERSERV', OPERSERV_NICK],
42
+ ['OS', OPERSERV_NICK],
43
+ ]);
44
+
45
+ /**
46
+ * The canonical, de-duplicated list of service nicks derived from
47
+ * {@link SHORTCUT_TO_SERVICE}'s values. Used by `serviceReducerFor` for the
48
+ * "is this target a service nick?" membership check so the service-nick list
49
+ * lives in exactly one place (this module), not duplicated across an
50
+ * `if`-chain.
51
+ */
52
+ export const SERVICE_NICKS: readonly string[] = Array.from(new Set(SHORTCUT_TO_SERVICE.values()));
@@ -153,7 +153,8 @@ function readTopic(
153
153
  ctx: Ctx,
154
154
  effects: EffectType[],
155
155
  ): { state: ChannelState; effects: EffectType[] } {
156
- if (state.topic === undefined) {
156
+ const topicReply = topicNumerics(state, ctx);
157
+ if (topicReply === undefined) {
157
158
  effects.push(
158
159
  Effect.send(ctx.connId, [
159
160
  numericErr(ctx, Numerics.RPL_NOTOPIC, 'No topic is set', state.name),
@@ -161,19 +162,31 @@ function readTopic(
161
162
  );
162
163
  return { state, effects };
163
164
  }
165
+ effects.push(topicReply);
166
+ return { state, effects };
167
+ }
164
168
 
169
+ /**
170
+ * Builds the `332 RPL_TOPIC` + `333 RPL_TOPICWHOTIME` {@link Effect.send}
171
+ * for the connection's current nick, or `undefined` when no topic is set
172
+ * on `state.topic`.
173
+ *
174
+ * Shared by the TOPIC read-form ({@link readTopic}) and the JOIN success
175
+ * path so both emit an identical numeric block whenever a topic is
176
+ * present, regardless of whether the topic came from a ChanServ KEEPTOPIC
177
+ * restore, a deploy snapshot, or an earlier joiner.
178
+ */
179
+ export function topicNumerics(state: ChannelState, ctx: Ctx): EffectType | undefined {
180
+ if (state.topic === undefined) return undefined;
165
181
  const nick = ctx.connection.nick ?? '*';
166
182
  const codeStr332 = Numerics.RPL_TOPIC.toString().padStart(3, '0');
167
183
  const codeStr333 = Numerics.RPL_TOPICWHOTIME.toString().padStart(3, '0');
168
- effects.push(
169
- Effect.send(ctx.connId, [
170
- { text: `:${ctx.serverName} ${codeStr332} ${nick} ${state.name} :${state.topic.text}` },
171
- {
172
- text: `:${ctx.serverName} ${codeStr333} ${nick} ${state.name} ${state.topic.setter} ${state.topic.setAt}`,
173
- },
174
- ]),
175
- );
176
- return { state, effects };
184
+ return Effect.send(ctx.connId, [
185
+ { text: `:${ctx.serverName} ${codeStr332} ${nick} ${state.name} :${state.topic.text}` },
186
+ {
187
+ text: `:${ctx.serverName} ${codeStr333} ${nick} ${state.name} ${state.topic.setter} ${state.topic.setAt}`,
188
+ },
189
+ ]);
177
190
  }
178
191
 
179
192
  // ============================================================================
@@ -34,8 +34,8 @@ export const OperCredSchema = z.object({
34
34
  /**
35
35
  * Schema for a single SASL PLAIN account credential. The `username` is also
36
36
  * the canonical account name recorded on the connection (for `extended-join`
37
- * / `account-tag`). Both fields are non-empty strings; adapters seed an
38
- * {@link InMemoryAccountStore} from this list at boot.
37
+ * / `account-tag`). Both fields are non-empty strings; adapters seed the
38
+ * `InMemoryServicesStore` from this list at boot.
39
39
  */
40
40
  export const SaslAccountSchema = z.object({
41
41
  username: z.string().min(1),
@@ -173,6 +173,15 @@ export const DEFAULT_CREATED_TEXT: string = BUILD_DATE;
173
173
  */
174
174
  export const DEFAULT_NICK_ENFORCE_GRACE_MS = 30_000;
175
175
 
176
+ /**
177
+ * Default minimum password length enforced by NickServ `SET PASSWORD`.
178
+ * Mirrors common IRC services (Atheme/Anope) and modern guidance (NIST
179
+ * SP 800-63B). Overridable per deployment via
180
+ * `ServerConfig.nickServ.minPasswordLength`. A new password shorter than
181
+ * the floor is rejected before hashing.
182
+ */
183
+ export const DEFAULT_MIN_PASSWORD_LENGTH = 8;
184
+
176
185
  /**
177
186
  * The full server-config schema. Optional fields default to deployment-
178
187
  * sensible values so a minimal `{ serverName, networkName }` config is
@@ -271,13 +280,12 @@ export const ServerConfigSchema = z.object({
271
280
  operCreds: z.array(OperCredSchema).default([]),
272
281
 
273
282
  /**
274
- * SASL PLAIN account credentials. The adapter seeds an
275
- * `InMemoryAccountStore` from this list at boot so `AUTHENTICATE PLAIN`
276
- * succeeds end-to-end for the listed accounts. Empty array (the default)
277
- * disables SASL account verification: `ctx.accounts` is then omitted and
278
- * a client completing `AUTHENTICATE PLAIN` receives `904 ERR_SASLFAIL`.
279
- * A persistent variant (DynamoDB `Accounts`, D1) is a documented
280
- * follow-up; swapping it in means replacing the construction call.
283
+ * SASL PLAIN seed credentials. The adapter ingests these into the
284
+ * `ServicesStore` at boot via `registerNick` (idempotent — existing
285
+ * registrations win). `AUTHENTICATE PLAIN`, `PASS <nick>:<password>`,
286
+ * and NickServ `IDENTIFY` all verify against the resulting
287
+ * `nickserv_accounts` rows. Empty array (the default) leaves the seed
288
+ * empty; accounts can still be created at runtime via NickServ REGISTER.
281
289
  */
282
290
  saslAccounts: z.array(SaslAccountSchema).default([]),
283
291
 
@@ -357,6 +365,24 @@ export const ServerConfigSchema = z.object({
357
365
  */
358
366
  nickEnforceGraceMs: z.number().int().nonnegative().optional(),
359
367
 
368
+ /**
369
+ * NickServ-specific tunables. Currently only the minimum length enforced
370
+ * by `SET PASSWORD` is configurable; the maximum (256) is fixed to bound
371
+ * the scrypt input budget. Omit entirely to inherit the documented
372
+ * defaults.
373
+ */
374
+ nickServ: z
375
+ .object({
376
+ /**
377
+ * Minimum length a new password must meet to be accepted by
378
+ * `SET PASSWORD`. Defaults to {@link DEFAULT_MIN_PASSWORD_LENGTH}
379
+ * (8) when unset. A shorter new password is rejected before hashing
380
+ * with `Password is too short (minimum <n> characters).`.
381
+ */
382
+ minPasswordLength: z.number().int().positive().optional(),
383
+ })
384
+ .optional(),
385
+
360
386
  /**
361
387
  * HostServ approval mode for `REQUEST <vhost>`. `true` (the default)
362
388
  * preserves the legacy auto-approve behaviour — the requested vhost is
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Shared SASL credential hashing primitives.
3
3
  *
4
- * Both the AWS adapter (`DynamoAccountStore`) and the CF adapter
5
- * (`D1AccountStore`) store SASL PLAIN credentials as **scrypt hashes**.
6
- * These helpers live in `irc-core` so both adapters share one hashing
7
- * implementation the AWS path previously owned the only copy.
4
+ * Both the AWS adapter (`DynamoServicesStore`) and the CF adapter
5
+ * (`D1ServicesStore`) store NickServ/SASL credentials as **scrypt hashes**
6
+ * via the unified `ServicesStore`. These helpers live in `irc-core` so both
7
+ * adapters share one hashing implementation.
8
8
  *
9
9
  * Uses `node:crypto` (`scryptSync`), which is available on Node (AWS Lambda)
10
10
  * natively and on Cloudflare Workers via the `nodejs_compat` flag. The public
@@ -14,16 +14,16 @@
14
14
  */
15
15
 
16
16
  import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
17
- import type { AccountStore, SaslPayload, SaslResult } from './ports.js';
18
17
 
19
18
  const SCRYPT_KEY_LEN = 64;
20
19
  const SALT_LEN = 16;
21
20
 
22
21
  /**
23
- * A single SASL PLAIN credential stored as a scrypt hash.
22
+ * A single credential stored as a scrypt hash.
24
23
  *
25
- * Every field is a persistence-backend attribute — this object IS the table
26
- * row (DynamoDB `Accounts` item / D1 `accounts` row, keyed by `account`).
24
+ * Every field is a persistence-backend attribute — this object IS the
25
+ * credential portion of a `nickserv_accounts` row (DynamoDB `Services`
26
+ * item / D1 `nickserv_accounts` row, keyed by the folded nick).
27
27
  * `algorithm` is retained so future migrations to Argon2/bcrypt can be
28
28
  * detected and handled gracefully by {@link verifyHashedPassword}.
29
29
  */
@@ -36,8 +36,9 @@ export interface HashedAccountCredential {
36
36
 
37
37
  /**
38
38
  * Hashes a plaintext password into a {@link HashedAccountCredential}
39
- * suitable for writing to the accounts backend (`putAccountCredential` /
40
- * `seed-cf-accounts`).
39
+ * suitable for writing to the services backend
40
+ * (`ServicesStore.registerNick` stores the result on the registered-nick
41
+ * row).
41
42
  *
42
43
  * Uses `scryptSync` (memory-hard, GPU-resistant) with a random salt.
43
44
  * Pass `{ salt }` for deterministic test scenarios; pass `{ keyLen }` to
@@ -78,47 +79,3 @@ export function verifyHashedPassword(password: string, entry: HashedAccountCrede
78
79
  const computed = scryptSync(password, salt, expected.length);
79
80
  return computed.length === expected.length && timingSafeEqual(computed, expected);
80
81
  }
81
-
82
- /**
83
- * Synchronous {@link AccountStore} backed by pre-loaded hashed credentials.
84
- *
85
- * Construct with entries produced by {@link hashAccountCredential} (for unit
86
- * tests / seeding) or pre-loaded from a persistence backend at boot. Both
87
- * adapters use this class:
88
- *
89
- * - AWS: `loadDynamoAccountStore` scans the `Accounts` table at Lambda
90
- * cold start and constructs this store; `DynamoAccountStore` is a
91
- * re-export alias for back-compat.
92
- * - CF: `loadD1AccountStore` queries the D1 `accounts` table at
93
- * `ConnectionDO` construction (mirroring the two-phase load pattern).
94
- *
95
- * The class has zero backend coupling — it works equally well with entries
96
- * from any source. The `AccountStore.verify` port stays synchronous, so
97
- * the async pre-load MUST complete before the first frame dispatches.
98
- */
99
- export class HashedAccountStore implements AccountStore {
100
- private readonly entries: ReadonlyMap<string, HashedAccountCredential>;
101
-
102
- constructor(entries: ReadonlyArray<HashedAccountCredential>) {
103
- this.entries = new Map(entries.map((e) => [e.account, e]));
104
- }
105
-
106
- verify(mech: string, payload: SaslPayload): SaslResult {
107
- if (mech.toUpperCase() !== 'PLAIN' || payload.kind !== 'PLAIN') {
108
- return { ok: false, reason: `unsupported mechanism: ${mech}` };
109
- }
110
- const entry = this.entries.get(payload.username);
111
- if (entry === undefined) {
112
- return { ok: false, reason: 'invalid credentials' };
113
- }
114
- if (!verifyHashedPassword(payload.password, entry)) {
115
- return { ok: false, reason: 'invalid credentials' };
116
- }
117
- return { ok: true, account: payload.username };
118
- }
119
-
120
- /** Number of loaded accounts (diagnostics / logging). */
121
- get size(): number {
122
- return this.entries.size;
123
- }
124
- }
@@ -11,6 +11,7 @@ export * from './commands/index.js';
11
11
  export * from './effects.js';
12
12
  export * from './ports.js';
13
13
  export * from './credential-hashing.js';
14
+ export * from './account-migration.js';
14
15
  export * from './flood-control.js';
15
16
  export * from './cloak.js';
16
17
  export * from './case-fold.js';