serverless-ircd 0.9.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.
- package/.github/workflows/ci.yml +28 -0
- package/.github/workflows/deploy-aws.yml +156 -32
- package/.github/workflows/deploy-cf-tcp.yml +35 -9
- package/.github/workflows/deploy-cf.yml +40 -14
- package/CHANGELOG.md +594 -0
- package/README.md +286 -60
- package/apps/aws-stack/README.md +3 -5
- package/apps/aws-stack/bin/aws.ts +118 -9
- package/apps/aws-stack/cdk.json +0 -3
- package/apps/aws-stack/package.json +3 -4
- package/apps/aws-stack/src/aws-stack.ts +398 -67
- package/apps/aws-stack/src/static-site.ts +323 -0
- package/apps/aws-stack/tests/smoke-helpers.test.ts +1 -1
- package/apps/aws-stack/tests/stack.test.ts +714 -105
- package/apps/aws-stack/tests/static-site.test.ts +491 -0
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +0 -1
- package/apps/cf-tcp-container/Dockerfile +37 -5
- package/apps/cf-tcp-container/package.json +7 -3
- package/apps/cf-tcp-container/src/config-loader.ts +113 -2
- package/apps/cf-tcp-container/src/container-server.ts +267 -87
- package/apps/cf-tcp-container/src/main.ts +22 -7
- package/apps/cf-tcp-container/src/proxy-protocol.ts +112 -0
- package/apps/cf-tcp-container/terraform/spectrum.tf +40 -11
- package/apps/cf-tcp-container/tests/config-loader.test.ts +170 -0
- package/apps/cf-tcp-container/tests/container-server-tls.test.ts +382 -0
- package/apps/cf-tcp-container/tests/container-server.test.ts +358 -31
- package/apps/cf-tcp-container/tests/dockerfile.test.ts +110 -0
- package/apps/cf-tcp-container/tests/proxy-protocol.test.ts +187 -0
- package/apps/cf-tcp-container/tests/spectrum-terraform.test.ts +135 -0
- package/apps/cf-tcp-container/tests/tls-e2e.test.ts +5 -1
- package/apps/cf-tcp-container/wrangler.toml +18 -14
- package/apps/cf-worker/package.json +3 -4
- package/apps/cf-worker/src/worker.ts +77 -5
- package/apps/cf-worker/tests/raw-modules.d.ts +11 -0
- package/apps/cf-worker/tests/smoke.test.ts +4 -0
- package/apps/cf-worker/tests/wrangler-config.test.ts +47 -0
- package/apps/cf-worker/tests/ws-admission.test.ts +112 -0
- package/apps/cf-worker/tests/ws-rate-limit.test.ts +133 -0
- package/apps/cf-worker/wrangler.test.toml +15 -1
- package/apps/cf-worker/wrangler.toml +95 -77
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +14 -2
- package/apps/local-cli/src/line-scanner.ts +26 -0
- package/apps/local-cli/src/server.ts +44 -19
- package/apps/local-cli/tests/line-scanner.test.ts +64 -0
- package/apps/local-cli/tests/tcp.test.ts +29 -0
- package/apps/web/landing/favicon.ico +0 -0
- package/apps/web/landing/index.html +1 -0
- package/apps/web/package.json +2 -2
- package/apps/web/scripts/build.mjs +66 -4
- package/apps/web/src/build-env.ts +125 -4
- package/apps/web/src/config-schema.ts +20 -6
- package/apps/web/static/{config.staging.json → config.prod-aws.json} +3 -2
- package/apps/web/tests/build-env.test.ts +210 -9
- package/apps/web/tests/build-smoke.test.ts +2 -2
- package/apps/web/tests/config-schema.test.ts +149 -25
- package/docs/AWS-Deployment.md +793 -118
- package/docs/AWS-TCP-Deployment.md +57 -47
- package/docs/Chat-History.md +55 -0
- package/docs/Cloudflare-Deployment-Guide.md +95 -114
- package/docs/Cloudflare-TCP-Deployment.md +160 -101
- package/docs/Release-Process.md +27 -23
- package/docs/SASL-EXTERNAL.md +175 -0
- package/docs/Services.md +69 -22
- package/docs/WebClientGuide.md +35 -26
- package/package.json +7 -10
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/admission.ts +28 -13
- package/packages/aws-adapter/src/aws-runtime.ts +30 -3
- package/packages/aws-adapter/src/cdk-table-defs.ts +39 -16
- package/packages/aws-adapter/src/config-loader.ts +153 -8
- package/packages/aws-adapter/src/dynamo-services-store.ts +19 -0
- package/packages/aws-adapter/src/handlers/connect.ts +73 -1
- package/packages/aws-adapter/src/handlers/default.ts +279 -123
- package/packages/aws-adapter/src/handlers/index.ts +98 -25
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +135 -14
- package/packages/aws-adapter/src/index.ts +5 -7
- package/packages/aws-adapter/src/ip-admission.ts +79 -0
- package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
- package/packages/aws-adapter/src/serialize.ts +23 -0
- package/packages/aws-adapter/src/tables.ts +11 -12
- package/packages/aws-adapter/tests/admission.test.ts +60 -2
- package/packages/aws-adapter/tests/aws-harness.ts +23 -2
- package/packages/aws-adapter/tests/aws-runtime.test.ts +64 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +217 -0
- package/packages/aws-adapter/tests/connect.test.ts +323 -3
- package/packages/aws-adapter/tests/default-frame-limit.test.ts +231 -0
- package/packages/aws-adapter/tests/default-occ.test.ts +226 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +134 -1
- package/packages/aws-adapter/tests/handlers.test.ts +174 -12
- package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
- package/packages/aws-adapter/tests/nlb-secure.test.ts +362 -0
- package/packages/aws-adapter/tests/nlb-stream.test.ts +628 -9
- package/packages/aws-adapter/tests/origin-allowlist.test.ts +110 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +0 -1
- package/packages/aws-adapter/tests/stats.test.ts +0 -3
- package/packages/aws-adapter/tests/sweeper.test.ts +0 -1
- package/packages/aws-adapter/tests/tables.test.ts +1 -8
- package/packages/aws-adapter/tests/transactions.test.ts +0 -1
- package/packages/cf-adapter/package.json +1 -5
- package/packages/cf-adapter/src/cf-runtime.ts +100 -10
- package/packages/cf-adapter/src/channel-do.ts +13 -3
- package/packages/cf-adapter/src/config-loader.ts +133 -8
- package/packages/cf-adapter/src/connection-do.ts +406 -116
- package/packages/cf-adapter/src/counter-do.ts +142 -0
- package/packages/cf-adapter/src/d1-services-store.ts +105 -26
- package/packages/cf-adapter/src/env.ts +99 -10
- package/packages/cf-adapter/src/index.ts +17 -7
- package/packages/cf-adapter/src/rate-limit-do.ts +87 -0
- package/packages/cf-adapter/tests/cf-runtime.test.ts +205 -16
- package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
- package/packages/cf-adapter/tests/config-loader.test.ts +159 -0
- package/packages/cf-adapter/tests/connection-do-counter.test.ts +165 -0
- package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
- package/packages/cf-adapter/tests/connection-do-frame-limit.test.ts +177 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +164 -54
- package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
- package/packages/cf-adapter/tests/connection-do-ws-spec-contract.test.ts +7 -4
- package/packages/cf-adapter/tests/counter-do.test.ts +181 -0
- package/packages/cf-adapter/tests/d1-services-store.test.ts +245 -3
- package/packages/cf-adapter/tests/rate-limit-do.test.ts +160 -0
- package/packages/cf-adapter/tests/serialize.test.ts +25 -0
- package/packages/cf-adapter/tests/worker/main.ts +4 -0
- package/packages/cf-adapter/wrangler.test.toml +18 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +25 -0
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +74 -0
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/account-migration.ts +140 -0
- package/packages/irc-core/src/caps/capabilities.ts +20 -10
- package/packages/irc-core/src/certfp.ts +178 -0
- package/packages/irc-core/src/commands/account-auth.ts +16 -19
- package/packages/irc-core/src/commands/cap.ts +10 -2
- package/packages/irc-core/src/commands/chanserv.ts +117 -14
- package/packages/irc-core/src/commands/chathistory.ts +13 -5
- package/packages/irc-core/src/commands/hostserv.ts +84 -8
- package/packages/irc-core/src/commands/index.ts +2 -1
- package/packages/irc-core/src/commands/invite.ts +1 -7
- package/packages/irc-core/src/commands/join.ts +1 -16
- package/packages/irc-core/src/commands/kick.ts +1 -8
- package/packages/irc-core/src/commands/list.ts +1 -8
- package/packages/irc-core/src/commands/memoserv.ts +1 -1
- package/packages/irc-core/src/commands/mode.ts +1 -8
- package/packages/irc-core/src/commands/multiline.ts +4 -10
- package/packages/irc-core/src/commands/names.ts +53 -13
- package/packages/irc-core/src/commands/nickserv.ts +161 -11
- package/packages/irc-core/src/commands/oper.ts +361 -8
- package/packages/irc-core/src/commands/part.ts +4 -10
- package/packages/irc-core/src/commands/privmsg.ts +8 -4
- package/packages/irc-core/src/commands/registration.ts +148 -4
- package/packages/irc-core/src/commands/sasl.ts +154 -46
- package/packages/irc-core/src/commands/topic.ts +10 -12
- package/packages/irc-core/src/commands/who.ts +1 -8
- package/packages/irc-core/src/config.ts +424 -25
- package/packages/irc-core/src/credential-hashing.ts +11 -54
- package/packages/irc-core/src/effects.ts +24 -0
- package/packages/irc-core/src/flood-control.ts +10 -10
- package/packages/irc-core/src/frame-rate-limit.ts +82 -0
- package/packages/irc-core/src/index.ts +9 -0
- package/packages/irc-core/src/oper-hashing.ts +43 -0
- package/packages/irc-core/src/oper-lockout.ts +87 -0
- package/packages/irc-core/src/ports.ts +529 -190
- package/packages/irc-core/src/protocol/bytes.ts +65 -0
- package/packages/irc-core/src/protocol/channel-name.ts +37 -0
- package/packages/irc-core/src/protocol/index.ts +12 -1
- package/packages/irc-core/src/protocol/outbound.ts +43 -10
- package/packages/irc-core/src/protocol/parser.ts +79 -10
- package/packages/irc-core/src/state/connection.ts +13 -0
- package/packages/irc-core/src/types.ts +266 -23
- package/packages/irc-core/src/ws-framing.ts +5 -4
- package/packages/irc-core/tests/account-migration.test.ts +133 -0
- package/packages/irc-core/tests/bytes.test.ts +89 -0
- package/packages/irc-core/tests/certfp.test.ts +117 -0
- package/packages/irc-core/tests/commands/cap.test.ts +76 -2
- package/packages/irc-core/tests/commands/chanserv.test.ts +166 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +140 -0
- package/packages/irc-core/tests/commands/hostserv.test.ts +316 -0
- package/packages/irc-core/tests/commands/join.test.ts +78 -1
- package/packages/irc-core/tests/commands/markread.test.ts +54 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +19 -0
- package/packages/irc-core/tests/commands/names.test.ts +193 -0
- package/packages/irc-core/tests/commands/nickserv.test.ts +419 -3
- package/packages/irc-core/tests/commands/oper.test.ts +574 -1
- package/packages/irc-core/tests/commands/privmsg.test.ts +16 -0
- package/packages/irc-core/tests/commands/registration.test.ts +602 -133
- package/packages/irc-core/tests/commands/sasl.test.ts +742 -172
- package/packages/irc-core/tests/commands/topic.test.ts +137 -2
- package/packages/irc-core/tests/commands/unified-account.test.ts +104 -84
- package/packages/irc-core/tests/config.test.ts +534 -2
- package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
- package/packages/irc-core/tests/effects.test.ts +14 -0
- package/packages/irc-core/tests/flood-control.test.ts +29 -1
- package/packages/irc-core/tests/frame-rate-limit.test.ts +98 -0
- package/packages/irc-core/tests/message-store.test.ts +5 -0
- package/packages/irc-core/tests/oper-hashing.test.ts +60 -0
- package/packages/irc-core/tests/oper-lockout.test.ts +74 -0
- package/packages/irc-core/tests/outbound.test.ts +148 -0
- package/packages/irc-core/tests/parser.test.ts +287 -5
- package/packages/irc-core/tests/persistent-services-store.test.ts +212 -12
- package/packages/irc-core/tests/ports.test.ts +170 -7
- package/packages/irc-core/tests/services-store.test.ts +567 -1
- package/packages/irc-core/tests/ws-framing.test.ts +45 -0
- package/packages/irc-core/vitest.config.ts +6 -1
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +123 -22
- package/packages/irc-server/src/dispatch.ts +1 -0
- package/packages/irc-server/src/index.ts +7 -0
- package/packages/irc-server/src/redact.ts +159 -0
- package/packages/irc-server/src/runtime.ts +14 -0
- package/packages/irc-server/src/transport.ts +28 -1
- package/packages/irc-server/tests/actor.test.ts +563 -54
- package/packages/irc-server/tests/dispatch.test.ts +31 -0
- package/packages/irc-server/tests/redact.test.ts +198 -0
- package/packages/irc-server/tests/runtime.test.ts +2 -0
- package/packages/irc-server/tests/transport.test.ts +66 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +4 -0
- package/pnpm-workspace.yaml +1 -0
- package/scripts/__tests__/deploy-web-aws.test.ts +491 -0
- package/scripts/deploy-web-aws.mjs +290 -0
- package/scripts/package.json +23 -0
- package/scripts/tsconfig.test.json +12 -0
- package/scripts/vitest.config.ts +19 -0
- package/tools/ci-hardening/package.json +2 -2
- package/tools/ci-hardening/src/cf-deploy-cli.ts +3 -0
- package/tools/ci-hardening/src/cf-deploy.ts +118 -0
- package/tools/ci-hardening/src/deploy-hostname.ts +118 -0
- package/tools/ci-hardening/src/env-var-drift.ts +192 -0
- package/tools/ci-hardening/src/hostname-guard.ts +11 -0
- package/tools/ci-hardening/src/index.ts +19 -0
- package/tools/ci-hardening/src/validate.ts +57 -0
- package/tools/ci-hardening/tests/__wrangler_missing__.toml +2 -0
- package/tools/ci-hardening/tests/__wrangler_placeholder__.toml +3 -0
- package/tools/ci-hardening/tests/__wrangler_real__.toml +3 -0
- package/tools/ci-hardening/tests/cf-deploy.test.ts +200 -0
- package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
- package/tools/ci-hardening/tests/deploy-hostname.test.ts +348 -0
- package/tools/ci-hardening/tests/env-var-drift.test.ts +284 -0
- package/tools/ci-hardening/tests/validate.test.ts +42 -0
- package/tools/ci-hardening/vitest.config.ts +5 -1
- package/tools/hash-oper-cred.ts +85 -0
- package/tools/load-test/package.json +1 -1
- package/tools/migrate-accounts-to-services.ts +270 -0
- package/tools/package.json +2 -1
- package/tools/seed-aws-accounts.ts +35 -10
- package/tools/seed-cf-accounts.ts +42 -9
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/packages/aws-adapter/src/account-store.ts +0 -121
- package/packages/aws-adapter/src/dynamo-account-store.ts +0 -95
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +0 -223
- package/packages/aws-adapter/tests/account-store.test.ts +0 -276
- package/packages/cf-adapter/src/d1-account-store.ts +0 -198
- package/packages/cf-adapter/tests/d1-account-store.test.ts +0 -274
- package/packages/irc-core/tests/account-store.test.ts +0 -131
|
@@ -26,10 +26,15 @@
|
|
|
26
26
|
*
|
|
27
27
|
* The reducer is a pure `Reducer<ConnectionState>`: all side effects are
|
|
28
28
|
* emitted as {@link Effect}s. The services store is synchronous (mirrors
|
|
29
|
-
* the {@link
|
|
29
|
+
* the {@link MessageStore} pattern).
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
DEFAULT_IDENTIFY_FREEZE_SECONDS,
|
|
34
|
+
DEFAULT_MAX_IDENTIFY_FAILURES,
|
|
35
|
+
DEFAULT_MIN_PASSWORD_LENGTH,
|
|
36
|
+
DEFAULT_NICK_ENFORCE_GRACE_MS,
|
|
37
|
+
} from '../config.js';
|
|
33
38
|
import { Effect } from '../effects.js';
|
|
34
39
|
import type { Effect as EffectType, RawLine } from '../effects.js';
|
|
35
40
|
import type { NickEnforcePolicy, ServicesStore } from '../ports.js';
|
|
@@ -48,6 +53,41 @@ const SERVICES_HOST = 'services';
|
|
|
48
53
|
/** Policies accepted by `SET ENFORCE` (lowercased input forms). */
|
|
49
54
|
const ENFORCE_POLICIES: ReadonlySet<NickEnforcePolicy> = new Set(['none', 'ghost', 'kill']);
|
|
50
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Fixed maximum password length accepted by `SET PASSWORD`. Bounds the
|
|
58
|
+
* scrypt input budget (256 is the practical limit of the scrypt input);
|
|
59
|
+
* not configurable. Over-long passwords are rejected before hashing.
|
|
60
|
+
*/
|
|
61
|
+
const MAX_PASSWORD_LENGTH = 256;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gate NOTICE sent to an unidentified caller (or one identified as a
|
|
65
|
+
* different account) attempting `SET ENFORCE`. Wording kept verbatim from
|
|
66
|
+
* the original inline literal so the existing ENFORCE test suite is not
|
|
67
|
+
* churned by the addition of `SET PASSWORD` (a sibling constant covers the
|
|
68
|
+
* PASSWORD gate).
|
|
69
|
+
*/
|
|
70
|
+
const ENFORCE_GATE_NOTICE = 'You must identify before changing enforcement.';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gate NOTICE sent to an unidentified caller (or one identified as a
|
|
74
|
+
* different account) attempting `SET PASSWORD`. Wording mirrors the ENFORCE
|
|
75
|
+
* gate's intent but speaks to "password" rather than "enforcement" so a
|
|
76
|
+
* caller who hits it understands which subcommand they tripped.
|
|
77
|
+
*/
|
|
78
|
+
const PASSWORD_GATE_NOTICE = 'You must identify before changing your password.';
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Formats the owner NOTICE delivered on the first successful identify
|
|
82
|
+
* after failed identifies were recorded against the account. Delivered
|
|
83
|
+
* once (the queued count is cleared with the delivery), so the owner
|
|
84
|
+
* learns their account was targeted even though every attempt failed.
|
|
85
|
+
*/
|
|
86
|
+
function identifyFailureNoticeText(count: number): string {
|
|
87
|
+
const plural = count === 1 ? 'attempt' : 'attempts';
|
|
88
|
+
return `Warning: ${count} failed identify ${plural} recorded against your account since your last successful login.`;
|
|
89
|
+
}
|
|
90
|
+
|
|
51
91
|
/**
|
|
52
92
|
* Reducer for `PRIVMSG NickServ :<subcommand> <args…>`.
|
|
53
93
|
* Authority: the invoking connection's {@link ConnectionState}.
|
|
@@ -157,13 +197,36 @@ function handleIdentify(
|
|
|
157
197
|
return { state, effects };
|
|
158
198
|
}
|
|
159
199
|
|
|
200
|
+
// Per-account freeze: once maxIdentifyFailures failed attempts were
|
|
201
|
+
// recorded within the sliding freeze window, reject BEFORE running
|
|
202
|
+
// scrypt (the freeze is the cheap rejection path; verifyNick is the
|
|
203
|
+
// expensive one an attacker would otherwise keep hitting).
|
|
204
|
+
const maxFailures =
|
|
205
|
+
ctx.serverConfig.nickServ?.maxIdentifyFailures ?? DEFAULT_MAX_IDENTIFY_FAILURES;
|
|
206
|
+
const freezeWindowMs =
|
|
207
|
+
(ctx.serverConfig.nickServ?.identifyFreezeSeconds ?? DEFAULT_IDENTIFY_FREEZE_SECONDS) * 1000;
|
|
208
|
+
if (services.isNickIdentifyFrozen(nick, { maxFailures, windowMs: freezeWindowMs })) {
|
|
209
|
+
effects.push(
|
|
210
|
+
notice(state, `Nick ${nick} is temporarily frozen due to too many failed identify attempts.`),
|
|
211
|
+
);
|
|
212
|
+
return { state, effects };
|
|
213
|
+
}
|
|
214
|
+
|
|
160
215
|
const result = services.verifyNick(nick, password);
|
|
161
216
|
if (!result.ok) {
|
|
217
|
+
services.recordIdentifyFailure(nick);
|
|
162
218
|
effects.push(notice(state, 'Invalid password.'));
|
|
163
219
|
return { state, effects };
|
|
164
220
|
}
|
|
165
221
|
|
|
222
|
+
// A successful login delivers the queued owner notice (if any failed
|
|
223
|
+
// identifies were recorded since the last successful login) and resets
|
|
224
|
+
// the per-account failure accounting.
|
|
225
|
+
const failedSinceLogin = services.clearIdentifyFailures(nick);
|
|
166
226
|
applyIdentify(state, result.account, services, effects);
|
|
227
|
+
if (failedSinceLogin > 0) {
|
|
228
|
+
effects.push(notice(state, identifyFailureNoticeText(failedSinceLogin)));
|
|
229
|
+
}
|
|
167
230
|
return { state, effects };
|
|
168
231
|
}
|
|
169
232
|
|
|
@@ -318,12 +381,24 @@ export function enforceRegisteredNick(state: ConnectionState, ctx: Ctx): EffectT
|
|
|
318
381
|
// ============================================================================
|
|
319
382
|
|
|
320
383
|
/**
|
|
321
|
-
* Handles `SET <subkey> <args…>`.
|
|
322
|
-
*
|
|
384
|
+
* Handles `SET <subkey> <args…>`. Supported subkeys:
|
|
385
|
+
*
|
|
386
|
+
* - `ENFORCE none|ghost|kill` — sets the per-account enforcement policy.
|
|
387
|
+
* - `PASSWORD <old> <new>` — self-service password change. Requires the
|
|
388
|
+
* current password to be re-supplied as a defence against a hijacked
|
|
389
|
+
* identified session (a stolen `+r` session should not let an attacker
|
|
390
|
+
* silently rotate the password and lock the real owner out — this is
|
|
391
|
+
* Atheme's shape, not the alternative "trust the gate" one-arg form).
|
|
392
|
+
* The new password is re-hashed via `hashAccountCredential` and
|
|
393
|
+
* overwrites the stored credential via
|
|
394
|
+
* {@link ServicesStore.setNickPassword}; the current session stays
|
|
395
|
+
* `+r` and other sessions of the same account are NOT kicked.
|
|
323
396
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
397
|
+
* Unknown subkeys fall back to the ENFORCE syntax notice.
|
|
398
|
+
*
|
|
399
|
+
* Both subkeys require the caller to be identified as the account bound to
|
|
400
|
+
* the current nick: an unidentified client (or one identified as a
|
|
401
|
+
* different account) cannot mutate the account.
|
|
327
402
|
*/
|
|
328
403
|
function handleSet(
|
|
329
404
|
state: ConnectionState,
|
|
@@ -331,8 +406,10 @@ function handleSet(
|
|
|
331
406
|
ctx: Ctx,
|
|
332
407
|
effects: EffectType[],
|
|
333
408
|
): { state: ConnectionState; effects: EffectType[] } {
|
|
334
|
-
const services = ctx.services as ServicesStore;
|
|
335
409
|
const subkey = args[0]?.toUpperCase();
|
|
410
|
+
if (subkey === 'PASSWORD') {
|
|
411
|
+
return handleSetPassword(state, args.slice(1), ctx, effects);
|
|
412
|
+
}
|
|
336
413
|
if (subkey !== 'ENFORCE') {
|
|
337
414
|
effects.push(enforceSyntaxNotice(state));
|
|
338
415
|
return { state, effects };
|
|
@@ -347,10 +424,11 @@ function handleSet(
|
|
|
347
424
|
|
|
348
425
|
const nick = state.nick;
|
|
349
426
|
if (nick === undefined || state.account === undefined) {
|
|
350
|
-
effects.push(notice(state,
|
|
427
|
+
effects.push(notice(state, ENFORCE_GATE_NOTICE));
|
|
351
428
|
return { state, effects };
|
|
352
429
|
}
|
|
353
430
|
|
|
431
|
+
const services = ctx.services as ServicesStore;
|
|
354
432
|
const rec = services.getNick(nick);
|
|
355
433
|
if (rec === undefined) {
|
|
356
434
|
effects.push(notice(state, `Nick ${nick} is not registered.`));
|
|
@@ -358,7 +436,7 @@ function handleSet(
|
|
|
358
436
|
}
|
|
359
437
|
// The caller must be identified as the account that owns this nick.
|
|
360
438
|
if (state.account !== rec.account) {
|
|
361
|
-
effects.push(notice(state,
|
|
439
|
+
effects.push(notice(state, ENFORCE_GATE_NOTICE));
|
|
362
440
|
return { state, effects };
|
|
363
441
|
}
|
|
364
442
|
|
|
@@ -367,11 +445,80 @@ function handleSet(
|
|
|
367
445
|
return { state, effects };
|
|
368
446
|
}
|
|
369
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Handles `SET PASSWORD <old> <new>`. Mirrors the `SET ENFORCE` identify
|
|
450
|
+
* gate (`state.account === rec.account`), then re-verifies the current
|
|
451
|
+
* password (no-leak parity with `IDENTIFY`'s `Invalid password.` notice)
|
|
452
|
+
* and enforces the configurable minimum / fixed maximum length on the new
|
|
453
|
+
* password before re-hashing and persisting.
|
|
454
|
+
*/
|
|
455
|
+
function handleSetPassword(
|
|
456
|
+
state: ConnectionState,
|
|
457
|
+
args: string[],
|
|
458
|
+
ctx: Ctx,
|
|
459
|
+
effects: EffectType[],
|
|
460
|
+
): { state: ConnectionState; effects: EffectType[] } {
|
|
461
|
+
const oldPassword = args[0];
|
|
462
|
+
const newPassword = args[1];
|
|
463
|
+
if (oldPassword === undefined || newPassword === undefined) {
|
|
464
|
+
effects.push(passwordSyntaxNotice(state));
|
|
465
|
+
return { state, effects };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const nick = state.nick;
|
|
469
|
+
if (nick === undefined || state.account === undefined) {
|
|
470
|
+
effects.push(notice(state, PASSWORD_GATE_NOTICE));
|
|
471
|
+
return { state, effects };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const services = ctx.services as ServicesStore;
|
|
475
|
+
const rec = services.getNick(nick);
|
|
476
|
+
if (rec === undefined) {
|
|
477
|
+
effects.push(notice(state, `Nick ${nick} is not registered.`));
|
|
478
|
+
return { state, effects };
|
|
479
|
+
}
|
|
480
|
+
// The caller must be identified as the account that owns this nick.
|
|
481
|
+
if (state.account !== rec.account) {
|
|
482
|
+
effects.push(notice(state, PASSWORD_GATE_NOTICE));
|
|
483
|
+
return { state, effects };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Re-verify the current password (defence against a hijacked `+r`
|
|
487
|
+
// session). Same `verifyNick` path `IDENTIFY` uses → same `Invalid
|
|
488
|
+
// password.` notice → no information leak about whether the account
|
|
489
|
+
// exists (the gate above already proved it does).
|
|
490
|
+
if (!services.verifyNick(nick, oldPassword).ok) {
|
|
491
|
+
effects.push(notice(state, 'Invalid password.'));
|
|
492
|
+
return { state, effects };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const minLen = ctx.serverConfig.nickServ?.minPasswordLength ?? DEFAULT_MIN_PASSWORD_LENGTH;
|
|
496
|
+
if (newPassword.length < minLen) {
|
|
497
|
+
effects.push(notice(state, `Password is too short (minimum ${minLen} characters).`));
|
|
498
|
+
return { state, effects };
|
|
499
|
+
}
|
|
500
|
+
if (newPassword.length > MAX_PASSWORD_LENGTH) {
|
|
501
|
+
effects.push(
|
|
502
|
+
notice(state, `Password is too long (maximum ${MAX_PASSWORD_LENGTH} characters).`),
|
|
503
|
+
);
|
|
504
|
+
return { state, effects };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
services.setNickPassword(nick, newPassword);
|
|
508
|
+
effects.push(notice(state, 'Password changed.'));
|
|
509
|
+
return { state, effects };
|
|
510
|
+
}
|
|
511
|
+
|
|
370
512
|
/** Builds the `SET ENFORCE` syntax NOTICE. */
|
|
371
513
|
function enforceSyntaxNotice(state: ConnectionState): EffectType {
|
|
372
514
|
return notice(state, 'Syntax: SET ENFORCE none|ghost|kill');
|
|
373
515
|
}
|
|
374
516
|
|
|
517
|
+
/** Builds the `SET PASSWORD` syntax NOTICE. */
|
|
518
|
+
function passwordSyntaxNotice(state: ConnectionState): EffectType {
|
|
519
|
+
return notice(state, 'Syntax: SET PASSWORD <old-password> <new-password>');
|
|
520
|
+
}
|
|
521
|
+
|
|
375
522
|
// ============================================================================
|
|
376
523
|
// Notice helpers
|
|
377
524
|
// ============================================================================
|
|
@@ -387,7 +534,10 @@ function helpNotice(state: ConnectionState): EffectType {
|
|
|
387
534
|
}
|
|
388
535
|
|
|
389
536
|
function unknownNotice(state: ConnectionState): EffectType {
|
|
390
|
-
return notice(
|
|
537
|
+
return notice(
|
|
538
|
+
state,
|
|
539
|
+
'Unknown command. Available: REGISTER, IDENTIFY, DROP, INFO, SET (ENFORCE, PASSWORD)',
|
|
540
|
+
);
|
|
391
541
|
}
|
|
392
542
|
|
|
393
543
|
/**
|