serverless-ircd 0.1.0 → 0.2.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 +96 -2
- package/.github/workflows/deploy-aws.yml +129 -0
- package/.github/workflows/deploy-cf.yml +0 -2
- package/.gitmodules +3 -0
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +352 -0
- package/MOTD.txt +3 -0
- package/PLAN-FIXES.md +358 -0
- package/PLAN.md +420 -0
- package/README.md +115 -81
- package/apps/aws-stack/README.md +73 -0
- package/apps/aws-stack/bin/aws.ts +49 -0
- package/apps/aws-stack/cdk.json +10 -0
- package/apps/aws-stack/package.json +41 -0
- package/apps/aws-stack/scripts/smoke-helpers.d.mts +22 -0
- package/apps/aws-stack/scripts/smoke-helpers.mjs +89 -0
- package/apps/aws-stack/scripts/smoke.mjs +142 -0
- package/apps/aws-stack/src/aws-stack.ts +263 -0
- package/apps/aws-stack/src/tables.ts +10 -0
- package/apps/aws-stack/tests/localstack.test.ts +46 -0
- package/apps/aws-stack/tests/smoke-helpers.test.ts +98 -0
- package/apps/aws-stack/tests/stack.test.ts +464 -0
- package/apps/aws-stack/tsconfig.build.json +11 -0
- package/apps/aws-stack/tsconfig.test.json +8 -0
- package/apps/aws-stack/vitest.config.ts +20 -0
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/wrangler.toml +25 -0
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +56 -0
- package/apps/local-cli/src/server.ts +241 -32
- package/apps/local-cli/tests/config-loader.test.ts +107 -0
- package/apps/local-cli/tests/e2e.test.ts +74 -0
- package/apps/local-cli/tests/security-e2e.test.ts +239 -0
- package/biome.json +3 -1
- package/dashboards/cloudwatch-irc.json +139 -0
- package/docs/AWS-Adapter-Architecture.md +494 -0
- package/docs/AWS-Deployment.md +1107 -0
- package/docs/Cloudflare-Deployment-Guide.md +660 -0
- package/docs/Home.md +1 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanIRCv3Websocket.md +489 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +440 -0
- package/package.json +8 -2
- package/packages/aws-adapter/README.md +35 -0
- package/packages/aws-adapter/package.json +52 -0
- package/packages/aws-adapter/src/aws-runtime.ts +783 -0
- package/packages/aws-adapter/src/cdk-table-defs.ts +69 -0
- package/packages/aws-adapter/src/config-loader.ts +96 -0
- package/packages/aws-adapter/src/dynamo.ts +61 -0
- package/packages/aws-adapter/src/handlers/connect.ts +44 -0
- package/packages/aws-adapter/src/handlers/default.ts +322 -0
- package/packages/aws-adapter/src/handlers/disconnect.ts +29 -0
- package/packages/aws-adapter/src/handlers/index.ts +248 -0
- package/packages/aws-adapter/src/handlers/ping-checker.ts +217 -0
- package/packages/aws-adapter/src/handlers/state.ts +13 -0
- package/packages/aws-adapter/src/handlers/sweeper.ts +84 -0
- package/packages/aws-adapter/src/index.ts +67 -0
- package/packages/aws-adapter/src/message-store.ts +34 -0
- package/packages/aws-adapter/src/serialize.ts +283 -0
- package/packages/aws-adapter/src/tables.ts +53 -0
- package/packages/aws-adapter/tests/aws-harness.ts +408 -0
- package/packages/aws-adapter/tests/aws-integration.test.ts +17 -0
- package/packages/aws-adapter/tests/aws-runtime.test.ts +654 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +158 -0
- package/packages/aws-adapter/tests/dynamo.test.ts +57 -0
- package/packages/aws-adapter/tests/global-setup.ts +301 -0
- package/packages/aws-adapter/tests/gone-exception.test.ts +271 -0
- package/packages/aws-adapter/tests/handlers.test.ts +427 -0
- package/packages/aws-adapter/tests/message-store.test.ts +55 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +571 -0
- package/packages/aws-adapter/tests/serialize.test.ts +249 -0
- package/packages/aws-adapter/tests/smoke.test.ts +48 -0
- package/packages/aws-adapter/tests/sweeper.test.ts +420 -0
- package/packages/aws-adapter/tests/tables.test.ts +74 -0
- package/packages/aws-adapter/tests/transactions.test.ts +446 -0
- package/packages/aws-adapter/tsconfig.build.json +16 -0
- package/packages/aws-adapter/tsconfig.test.json +14 -0
- package/packages/aws-adapter/vitest.config.ts +23 -0
- package/packages/cf-adapter/package.json +2 -1
- package/packages/cf-adapter/src/config-loader.ts +106 -0
- package/packages/cf-adapter/src/connection-do.ts +34 -0
- package/packages/cf-adapter/tests/cf-harness.ts +2 -2
- package/packages/cf-adapter/tests/cf-integration.test.ts +2 -2
- package/packages/cf-adapter/tests/config-loader.test.ts +149 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +42 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +108 -3
- package/packages/in-memory-runtime/src/index.ts +1 -1
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +115 -0
- package/packages/irc-core/package.json +12 -2
- package/packages/irc-core/src/admission.ts +216 -0
- package/packages/irc-core/src/caps/capabilities.ts +1 -0
- package/packages/irc-core/src/cloak.ts +81 -0
- package/packages/irc-core/src/commands/cap.ts +1 -2
- package/packages/irc-core/src/commands/chathistory.ts +305 -0
- package/packages/irc-core/src/commands/index.ts +7 -0
- package/packages/irc-core/src/commands/join.ts +59 -7
- package/packages/irc-core/src/commands/privmsg.ts +24 -0
- package/packages/irc-core/src/commands/registration.ts +71 -14
- package/packages/irc-core/src/commands/sasl.ts +251 -0
- package/packages/irc-core/src/config.ts +247 -0
- package/packages/irc-core/src/flood-control.ts +175 -0
- package/packages/irc-core/src/index.ts +5 -0
- package/packages/irc-core/src/ports.ts +655 -0
- package/packages/irc-core/src/protocol/base64.ts +16 -0
- package/packages/irc-core/src/protocol/index.ts +2 -1
- package/packages/irc-core/src/protocol/numerics.ts +11 -0
- package/packages/irc-core/src/protocol/parser.ts +27 -2
- package/packages/irc-core/src/state/connection.ts +41 -0
- package/packages/irc-core/src/types.ts +66 -2
- package/packages/irc-core/stryker.commands.conf.json +41 -0
- package/packages/irc-core/stryker.protocol.conf.json +26 -0
- package/packages/irc-core/tests/admission.test.ts +229 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +22 -0
- package/packages/irc-core/tests/cloak.test.ts +78 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +996 -0
- package/packages/irc-core/tests/commands/join.test.ts +246 -2
- package/packages/irc-core/tests/commands/privmsg.test.ts +146 -2
- package/packages/irc-core/tests/commands/registration.test.ts +277 -9
- package/packages/irc-core/tests/commands/sasl.test.ts +536 -0
- package/packages/irc-core/tests/config.test.ts +646 -0
- package/packages/irc-core/tests/flood-control.test.ts +394 -0
- package/packages/irc-core/tests/message-store.test.ts +530 -0
- package/packages/irc-core/tests/parser.test.ts +44 -1
- package/packages/irc-core/tests/ports.test.ts +263 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +162 -44
- package/packages/irc-server/src/dispatch.ts +89 -5
- package/packages/irc-server/src/index.ts +2 -0
- package/packages/irc-server/src/routing.ts +151 -0
- package/packages/irc-server/tests/actor.test.ts +470 -14
- package/packages/irc-server/tests/dispatch.test.ts +84 -0
- package/packages/irc-server/tests/routing.test.ts +201 -0
- package/packages/irc-test-support/README.md +32 -0
- package/packages/irc-test-support/package.json +37 -0
- package/packages/{cf-adapter/tests/integration → irc-test-support/src}/in-memory-harness.ts +1 -1
- package/packages/irc-test-support/src/index.ts +24 -0
- package/packages/{cf-adapter/tests/integration/harness.test.ts → irc-test-support/tests/in-memory-harness.test.ts} +1 -1
- package/packages/{cf-adapter/tests/integration/scenarios.test.ts → irc-test-support/tests/in-memory-scenarios.test.ts} +1 -2
- package/packages/irc-test-support/tests/smoke.test.ts +11 -0
- package/packages/irc-test-support/tsconfig.build.json +16 -0
- package/packages/irc-test-support/tsconfig.test.json +14 -0
- package/packages/irc-test-support/vitest.config.ts +20 -0
- package/progress.md +107 -0
- package/tickets.md +2485 -0
- package/tools/ci-hardening/package.json +30 -0
- package/tools/ci-hardening/src/index.ts +6 -0
- package/tools/ci-hardening/src/validate.ts +103 -0
- package/tools/ci-hardening/tests/__no_thresholds__.txt +11 -0
- package/tools/ci-hardening/tests/__partial_thresholds__.txt +16 -0
- package/tools/ci-hardening/tests/__stryker_bad__.json +4 -0
- package/tools/ci-hardening/tests/validate.test.ts +177 -0
- package/tools/ci-hardening/tsconfig.build.json +12 -0
- package/tools/ci-hardening/tsconfig.test.json +10 -0
- package/tools/ci-hardening/vitest.config.ts +21 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/src/forwarder.ts +34 -23
- package/tools/tcp-ws-forwarder/src/logger.ts +155 -0
- package/tools/tcp-ws-forwarder/src/main.ts +33 -14
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +86 -0
- package/tools/tcp-ws-forwarder/tests/logger.test.ts +136 -0
- package/webircgateway/LICENSE +201 -0
- package/webircgateway/Makefile +44 -0
- package/webircgateway/README.md +134 -0
- package/webircgateway/config.conf.example +135 -0
- package/webircgateway/go.mod +16 -0
- package/webircgateway/go.sum +89 -0
- package/webircgateway/main.go +118 -0
- package/webircgateway/pkg/dnsbl/dnsbl.go +121 -0
- package/webircgateway/pkg/identd/identd.go +86 -0
- package/webircgateway/pkg/identd/rpcclient.go +59 -0
- package/webircgateway/pkg/irc/isupport.go +56 -0
- package/webircgateway/pkg/irc/message.go +217 -0
- package/webircgateway/pkg/irc/state.go +79 -0
- package/webircgateway/pkg/proxy/proxy.go +129 -0
- package/webircgateway/pkg/proxy/server.go +237 -0
- package/webircgateway/pkg/recaptcha/recaptcha.go +59 -0
- package/webircgateway/pkg/webircgateway/client.go +741 -0
- package/webircgateway/pkg/webircgateway/client_command_handlers.go +495 -0
- package/webircgateway/pkg/webircgateway/config.go +385 -0
- package/webircgateway/pkg/webircgateway/gateway.go +278 -0
- package/webircgateway/pkg/webircgateway/gateway_utils.go +133 -0
- package/webircgateway/pkg/webircgateway/hooks.go +152 -0
- package/webircgateway/pkg/webircgateway/letsencrypt.go +41 -0
- package/webircgateway/pkg/webircgateway/messagetags.go +103 -0
- package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +206 -0
- package/webircgateway/pkg/webircgateway/transport_sockjs.go +107 -0
- package/webircgateway/pkg/webircgateway/transport_tcp.go +113 -0
- package/webircgateway/pkg/webircgateway/transport_websocket.go +126 -0
- package/webircgateway/pkg/webircgateway/utils.go +147 -0
- package/webircgateway/plugins/example/plugin.go +11 -0
- package/webircgateway/plugins/stats/plugin.go +52 -0
- package/webircgateway/staticcheck.conf +1 -0
- package/webircgateway/webircgateway.svg +3 -0
- package/packages/cf-adapter/tests/integration/index.ts +0 -17
- /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/harness.ts +0 -0
- /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/scenarios.ts +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { cloakHost } from '../../src/cloak';
|
|
2
3
|
import { generateIsupport } from '../../src/commands/isupport';
|
|
3
4
|
import {
|
|
4
5
|
buildWelcomeLines,
|
|
@@ -75,9 +76,9 @@ function expectedWelcome(nick: string, user: string, host: string | undefined):
|
|
|
75
76
|
);
|
|
76
77
|
return [
|
|
77
78
|
L(`${prefix} 001 ${nick} :Welcome to the ExampleNet IRC Network, ${hostmask}`),
|
|
78
|
-
L(`${prefix} 002 ${nick} :Your host is ${sv}, running version 0.
|
|
79
|
+
L(`${prefix} 002 ${nick} :Your host is ${sv}, running version 0.2.0`),
|
|
79
80
|
L(`${prefix} 003 ${nick} :This server was created in Phase 1`),
|
|
80
|
-
L(`${prefix} 004 ${nick} ${sv} 0.
|
|
81
|
+
L(`${prefix} 004 ${nick} ${sv} 0.2.0 iosw biklmnstp`),
|
|
81
82
|
...isupport,
|
|
82
83
|
L(`${prefix} 375 ${nick} :- ${sv} Message of the day -`),
|
|
83
84
|
L(`${prefix} 372 ${nick} :- MOTD placeholder`),
|
|
@@ -567,33 +568,300 @@ describe('emitWelcomeIfReady', () => {
|
|
|
567
568
|
state.user = 'alice';
|
|
568
569
|
const ctx = makeCtx(state);
|
|
569
570
|
const effect = emitWelcomeIfReady(state, ctx);
|
|
570
|
-
expect(effect).toEqual(Effect.send('c1', expectedWelcome('alice', 'alice', undefined)));
|
|
571
|
+
expect(effect).toEqual([Effect.send('c1', expectedWelcome('alice', 'alice', undefined))]);
|
|
571
572
|
expect(state.registration).toBe('registered');
|
|
572
573
|
});
|
|
573
574
|
|
|
574
|
-
it('returns
|
|
575
|
+
it('returns an empty array and does not transition when nick is missing', () => {
|
|
575
576
|
const state = makeState();
|
|
576
577
|
state.user = 'alice';
|
|
577
578
|
const ctx = makeCtx(state);
|
|
578
579
|
const effect = emitWelcomeIfReady(state, ctx);
|
|
579
|
-
expect(effect).
|
|
580
|
+
expect(effect).toEqual([]);
|
|
580
581
|
expect(state.registration).toBe('pre-registration');
|
|
581
582
|
});
|
|
582
583
|
|
|
583
|
-
it('returns
|
|
584
|
+
it('returns an empty array and does not transition when user is missing', () => {
|
|
584
585
|
const state = makeState();
|
|
585
586
|
state.nick = 'alice';
|
|
586
587
|
const ctx = makeCtx(state);
|
|
587
588
|
const effect = emitWelcomeIfReady(state, ctx);
|
|
588
|
-
expect(effect).
|
|
589
|
+
expect(effect).toEqual([]);
|
|
589
590
|
expect(state.registration).toBe('pre-registration');
|
|
590
591
|
});
|
|
591
592
|
|
|
592
|
-
it('returns
|
|
593
|
+
it('returns an empty array when already registered', () => {
|
|
593
594
|
const state = registeredState();
|
|
594
595
|
const ctx = makeCtx(state);
|
|
595
596
|
const effect = emitWelcomeIfReady(state, ctx);
|
|
596
|
-
expect(effect).
|
|
597
|
+
expect(effect).toEqual([]);
|
|
597
598
|
expect(state.registration).toBe('registered');
|
|
598
599
|
});
|
|
599
600
|
});
|
|
601
|
+
|
|
602
|
+
// ============================================================================
|
|
603
|
+
// emitWelcomeIfReady — server-password gate
|
|
604
|
+
// ============================================================================
|
|
605
|
+
|
|
606
|
+
/** Builds a ctx whose serverConfig has a serverPassword set. */
|
|
607
|
+
function makeCtxWithPassword(state: ConnectionState, password: string): Ctx {
|
|
608
|
+
return buildCtx({
|
|
609
|
+
serverConfig: { ...serverConfig, serverPassword: password },
|
|
610
|
+
clock: new FakeClock(1_000),
|
|
611
|
+
ids: new SequentialIdFactory(),
|
|
612
|
+
motd: EmptyMotdProvider,
|
|
613
|
+
connection: state,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
describe('emitWelcomeIfReady — server-password gate', () => {
|
|
618
|
+
it('proceeds with registration when no serverPassword is configured', () => {
|
|
619
|
+
const state = makeState();
|
|
620
|
+
state.nick = 'alice';
|
|
621
|
+
state.user = 'alice';
|
|
622
|
+
const ctx = makeCtx(state);
|
|
623
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
624
|
+
expect(effects).toEqual([Effect.send('c1', expectedWelcome('alice', 'alice', undefined))]);
|
|
625
|
+
expect(state.registration).toBe('registered');
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('proceeds with registration when serverPassword is empty (disabled)', () => {
|
|
629
|
+
const state = makeState();
|
|
630
|
+
state.nick = 'alice';
|
|
631
|
+
state.user = 'alice';
|
|
632
|
+
const ctx = makeCtxWithPassword(state, '');
|
|
633
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
634
|
+
expect(state.registration).toBe('registered');
|
|
635
|
+
expect(effects).toEqual([Effect.send('c1', expectedWelcome('alice', 'alice', undefined))]);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('blocks registration with 464 + Disconnect when passAttempt is missing', () => {
|
|
639
|
+
const state = makeState();
|
|
640
|
+
state.nick = 'alice';
|
|
641
|
+
state.user = 'alice';
|
|
642
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
643
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
644
|
+
expect(state.registration).not.toBe('registered');
|
|
645
|
+
expect(effects).toEqual<EffectType[]>([
|
|
646
|
+
Effect.send('c1', [L(':irc.example.com 464 alice :Password mismatch')]),
|
|
647
|
+
Effect.disconnect('c1', 'Bad Password'),
|
|
648
|
+
]);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('blocks registration with 464 + Disconnect when passAttempt is wrong', () => {
|
|
652
|
+
const state = makeState();
|
|
653
|
+
state.nick = 'alice';
|
|
654
|
+
state.user = 'alice';
|
|
655
|
+
state.passAttempt = 'wrong';
|
|
656
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
657
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
658
|
+
expect(state.registration).not.toBe('registered');
|
|
659
|
+
expect(effects).toEqual<EffectType[]>([
|
|
660
|
+
Effect.send('c1', [L(':irc.example.com 464 alice :Password mismatch')]),
|
|
661
|
+
Effect.disconnect('c1', 'Bad Password'),
|
|
662
|
+
]);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('proceeds with registration when passAttempt matches serverPassword', () => {
|
|
666
|
+
const state = makeState();
|
|
667
|
+
state.nick = 'alice';
|
|
668
|
+
state.user = 'alice';
|
|
669
|
+
state.passAttempt = 's3cret';
|
|
670
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
671
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
672
|
+
expect(state.registration).toBe('registered');
|
|
673
|
+
expect(effects).toEqual([Effect.send('c1', expectedWelcome('alice', 'alice', undefined))]);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('satisfies the gate when SASL has authenticated an account (no PASS needed)', () => {
|
|
677
|
+
const state = makeState();
|
|
678
|
+
state.nick = 'alice';
|
|
679
|
+
state.user = 'alice';
|
|
680
|
+
state.account = 'alice';
|
|
681
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
682
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
683
|
+
expect(state.registration).toBe('registered');
|
|
684
|
+
expect(effects).toEqual([Effect.send('c1', expectedWelcome('alice', 'alice', undefined))]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('does not register when nick is missing even if password is configured', () => {
|
|
688
|
+
const state = makeState();
|
|
689
|
+
state.user = 'alice';
|
|
690
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
691
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
692
|
+
expect(effects).toEqual([]);
|
|
693
|
+
expect(state.registration).toBe('pre-registration');
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('does not register when capNegotiating is true even if password is configured', () => {
|
|
697
|
+
const state = makeState();
|
|
698
|
+
state.nick = 'alice';
|
|
699
|
+
state.user = 'alice';
|
|
700
|
+
state.capNegotiating = true;
|
|
701
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
702
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
703
|
+
expect(effects).toEqual([]);
|
|
704
|
+
expect(state.registration).toBe('pre-registration');
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// ============================================================================
|
|
709
|
+
// userReducer / nickReducer — server-password gate integration
|
|
710
|
+
// ============================================================================
|
|
711
|
+
|
|
712
|
+
describe('userReducer — server-password gate integration', () => {
|
|
713
|
+
it('emits 464 + Disconnect on the completing USER when passAttempt is wrong', () => {
|
|
714
|
+
const state = makeState();
|
|
715
|
+
state.nick = 'alice';
|
|
716
|
+
state.registration = 'registering';
|
|
717
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
718
|
+
const out = userReducer(
|
|
719
|
+
state,
|
|
720
|
+
{ command: 'USER', params: ['alice', '0', '*', 'Alice'], tags: {} },
|
|
721
|
+
ctx,
|
|
722
|
+
);
|
|
723
|
+
expect(state.registration).not.toBe('registered');
|
|
724
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
725
|
+
Effect.send('c1', [L(':irc.example.com 464 alice :Password mismatch')]),
|
|
726
|
+
Effect.disconnect('c1', 'Bad Password'),
|
|
727
|
+
]);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it('completes registration on the completing USER when passAttempt matches', () => {
|
|
731
|
+
const state = makeState();
|
|
732
|
+
state.nick = 'alice';
|
|
733
|
+
state.passAttempt = 's3cret';
|
|
734
|
+
state.registration = 'registering';
|
|
735
|
+
const ctx = makeCtxWithPassword(state, 's3cret');
|
|
736
|
+
const out = userReducer(
|
|
737
|
+
state,
|
|
738
|
+
{ command: 'USER', params: ['alice', '0', '*', 'Alice'], tags: {} },
|
|
739
|
+
ctx,
|
|
740
|
+
);
|
|
741
|
+
expect(state.registration).toBe('registered');
|
|
742
|
+
expect(out.effects).toEqual<EffectType[]>([
|
|
743
|
+
Effect.send('c1', expectedWelcome('alice', 'alice', undefined)),
|
|
744
|
+
]);
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// ============================================================================
|
|
749
|
+
// emitWelcomeIfReady — hostmask cloaking integration
|
|
750
|
+
// ============================================================================
|
|
751
|
+
|
|
752
|
+
function makeCtxWithCloaking(
|
|
753
|
+
state: ConnectionState,
|
|
754
|
+
cloaking: { enabled: boolean; secret: string; cloakedSuffix?: string },
|
|
755
|
+
): Ctx {
|
|
756
|
+
return buildCtx({
|
|
757
|
+
serverConfig: { ...serverConfig, cloaking },
|
|
758
|
+
clock: new FakeClock(1_000),
|
|
759
|
+
ids: new SequentialIdFactory(),
|
|
760
|
+
motd: EmptyMotdProvider,
|
|
761
|
+
connection: state,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
describe('emitWelcomeIfReady — hostmask cloaking', () => {
|
|
766
|
+
it('leaves host untouched when cloaking is disabled', () => {
|
|
767
|
+
const state = makeState();
|
|
768
|
+
state.nick = 'alice';
|
|
769
|
+
state.user = 'alice';
|
|
770
|
+
state.host = '203.0.113.5';
|
|
771
|
+
const ctx = makeCtxWithCloaking(state, { enabled: false, secret: 's3cret' });
|
|
772
|
+
emitWelcomeIfReady(state, ctx);
|
|
773
|
+
expect(state.host).toBe('203.0.113.5');
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it('replaces the real host with a deterministic cloak when enabled', () => {
|
|
777
|
+
const state = makeState();
|
|
778
|
+
state.nick = 'alice';
|
|
779
|
+
state.user = 'alice';
|
|
780
|
+
state.host = '203.0.113.5';
|
|
781
|
+
const ctx = makeCtxWithCloaking(state, { enabled: true, secret: 's3cret' });
|
|
782
|
+
emitWelcomeIfReady(state, ctx);
|
|
783
|
+
expect(state.host).toBe(
|
|
784
|
+
cloakHost('203.0.113.5', { enabled: true, secret: 's3cret' }, 'ExampleNet'),
|
|
785
|
+
);
|
|
786
|
+
expect(state.host).not.toBe('203.0.113.5');
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('uses the configured cloakedSuffix when supplied', () => {
|
|
790
|
+
const state = makeState();
|
|
791
|
+
state.nick = 'alice';
|
|
792
|
+
state.user = 'alice';
|
|
793
|
+
state.host = '203.0.113.5';
|
|
794
|
+
const ctx = makeCtxWithCloaking(state, {
|
|
795
|
+
enabled: true,
|
|
796
|
+
secret: 's3cret',
|
|
797
|
+
cloakedSuffix: 'hidden.lan',
|
|
798
|
+
});
|
|
799
|
+
emitWelcomeIfReady(state, ctx);
|
|
800
|
+
expect(state.host).toBe(
|
|
801
|
+
cloakHost(
|
|
802
|
+
'203.0.113.5',
|
|
803
|
+
{ enabled: true, secret: 's3cret', cloakedSuffix: 'hidden.lan' },
|
|
804
|
+
'ExampleNet',
|
|
805
|
+
),
|
|
806
|
+
);
|
|
807
|
+
expect(state.host.endsWith('.hidden.lan')).toBe(true);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it('cloaks the host before building the welcome hostmask so 001 shows the cloak', () => {
|
|
811
|
+
const state = makeState();
|
|
812
|
+
state.nick = 'alice';
|
|
813
|
+
state.user = 'alice';
|
|
814
|
+
state.host = '203.0.113.5';
|
|
815
|
+
const ctx = makeCtxWithCloaking(state, { enabled: true, secret: 's3cret' });
|
|
816
|
+
const effects = emitWelcomeIfReady(state, ctx);
|
|
817
|
+
const expectedCloak = cloakHost(
|
|
818
|
+
'203.0.113.5',
|
|
819
|
+
{ enabled: true, secret: 's3cret' },
|
|
820
|
+
'ExampleNet',
|
|
821
|
+
);
|
|
822
|
+
const expectedHostmask = `alice!alice@${expectedCloak}`;
|
|
823
|
+
expect(effects).toEqual([
|
|
824
|
+
Effect.send('c1', expectedWelcomeWithHost('alice', expectedHostmask)),
|
|
825
|
+
]);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it('does not cloak when host is already absent (nothing to cloak)', () => {
|
|
829
|
+
const state = makeState();
|
|
830
|
+
state.nick = 'alice';
|
|
831
|
+
state.user = 'alice';
|
|
832
|
+
const ctx = makeCtxWithCloaking(state, { enabled: true, secret: 's3cret' });
|
|
833
|
+
emitWelcomeIfReady(state, ctx);
|
|
834
|
+
expect(state.host).toBeUndefined();
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
/** Builds the welcome block with an explicit cloaked host (used for cloak tests). */
|
|
839
|
+
function expectedWelcomeWithHost(nick: string, hostmask: string): RawLine[] {
|
|
840
|
+
const sv = 'irc.example.com';
|
|
841
|
+
const prefix = `:${sv}`;
|
|
842
|
+
const isupport = generateIsupport(
|
|
843
|
+
{
|
|
844
|
+
serverName: sv,
|
|
845
|
+
networkName: 'ExampleNet',
|
|
846
|
+
maxChannelsPerUser: 30,
|
|
847
|
+
maxTargetsPerCommand: 10,
|
|
848
|
+
maxListEntries: 50,
|
|
849
|
+
nickLen: 30,
|
|
850
|
+
channelLen: 50,
|
|
851
|
+
topicLen: 390,
|
|
852
|
+
quitMessage: 'Client Quit',
|
|
853
|
+
},
|
|
854
|
+
new Set<string>(),
|
|
855
|
+
nick,
|
|
856
|
+
);
|
|
857
|
+
return [
|
|
858
|
+
L(`${prefix} 001 ${nick} :Welcome to the ExampleNet IRC Network, ${hostmask}`),
|
|
859
|
+
L(`${prefix} 002 ${nick} :Your host is ${sv}, running version 0.2.0`),
|
|
860
|
+
L(`${prefix} 003 ${nick} :This server was created in Phase 1`),
|
|
861
|
+
L(`${prefix} 004 ${nick} ${sv} 0.2.0 iosw biklmnstp`),
|
|
862
|
+
...isupport,
|
|
863
|
+
L(`${prefix} 375 ${nick} :- ${sv} Message of the day -`),
|
|
864
|
+
L(`${prefix} 372 ${nick} :- MOTD placeholder`),
|
|
865
|
+
L(`${prefix} 376 ${nick} :End of MOTD command`),
|
|
866
|
+
];
|
|
867
|
+
}
|