serverless-ircd 0.9.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.
- package/.github/workflows/deploy-aws.yml +156 -32
- package/.github/workflows/deploy-cf-tcp.yml +11 -9
- package/.github/workflows/deploy-cf.yml +14 -14
- package/CHANGELOG.md +305 -0
- package/README.md +134 -41
- package/apps/aws-stack/README.md +3 -5
- package/apps/aws-stack/bin/aws.ts +82 -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 +177 -52
- 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 +267 -92
- 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/package.json +2 -3
- package/apps/cf-tcp-container/src/container-server.ts +12 -9
- package/apps/cf-tcp-container/wrangler.toml +1 -10
- package/apps/cf-worker/package.json +3 -4
- package/apps/cf-worker/wrangler.toml +12 -71
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/server.ts +21 -17
- 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 +670 -96
- package/docs/AWS-TCP-Deployment.md +20 -45
- package/docs/Cloudflare-Deployment-Guide.md +87 -113
- package/docs/Cloudflare-TCP-Deployment.md +25 -49
- package/docs/Release-Process.md +27 -23
- 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/cdk-table-defs.ts +6 -11
- package/packages/aws-adapter/src/config-loader.ts +19 -2
- package/packages/aws-adapter/src/dynamo-services-store.ts +7 -0
- package/packages/aws-adapter/src/handlers/connect.ts +26 -0
- package/packages/aws-adapter/src/handlers/default.ts +190 -123
- package/packages/aws-adapter/src/handlers/index.ts +67 -23
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +3 -6
- package/packages/aws-adapter/src/index.ts +5 -7
- package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
- package/packages/aws-adapter/src/serialize.ts +15 -0
- package/packages/aws-adapter/src/tables.ts +2 -12
- package/packages/aws-adapter/tests/aws-harness.ts +0 -1
- package/packages/aws-adapter/tests/config-loader.test.ts +66 -0
- package/packages/aws-adapter/tests/connect.test.ts +124 -1
- package/packages/aws-adapter/tests/default-occ.test.ts +219 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +11 -0
- package/packages/aws-adapter/tests/handlers.test.ts +117 -11
- package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
- 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 +59 -8
- package/packages/cf-adapter/src/channel-do.ts +13 -3
- package/packages/cf-adapter/src/connection-do.ts +266 -109
- package/packages/cf-adapter/src/d1-services-store.ts +63 -26
- package/packages/cf-adapter/src/env.ts +11 -10
- package/packages/cf-adapter/src/index.ts +0 -6
- package/packages/cf-adapter/tests/cf-runtime.test.ts +101 -1
- package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
- package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +95 -54
- package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
- package/packages/cf-adapter/tests/d1-services-store.test.ts +53 -2
- package/packages/cf-adapter/tests/serialize.test.ts +25 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/account-migration.ts +140 -0
- package/packages/irc-core/src/commands/account-auth.ts +16 -19
- package/packages/irc-core/src/commands/memoserv.ts +1 -1
- package/packages/irc-core/src/commands/nickserv.ts +122 -11
- package/packages/irc-core/src/commands/registration.ts +2 -2
- package/packages/irc-core/src/commands/sasl.ts +22 -31
- package/packages/irc-core/src/config.ts +35 -9
- package/packages/irc-core/src/credential-hashing.ts +11 -54
- package/packages/irc-core/src/index.ts +1 -0
- package/packages/irc-core/src/ports.ts +159 -179
- package/packages/irc-core/src/types.ts +38 -10
- package/packages/irc-core/tests/account-migration.test.ts +133 -0
- 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/nickserv.test.ts +237 -1
- package/packages/irc-core/tests/commands/oper.test.ts +15 -0
- package/packages/irc-core/tests/commands/registration.test.ts +140 -133
- package/packages/irc-core/tests/commands/sasl.test.ts +159 -178
- package/packages/irc-core/tests/commands/unified-account.test.ts +102 -84
- package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
- package/packages/irc-core/tests/message-store.test.ts +5 -0
- package/packages/irc-core/tests/persistent-services-store.test.ts +71 -12
- package/packages/irc-core/tests/ports.test.ts +71 -0
- package/packages/irc-core/tests/services-store.test.ts +204 -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 +0 -14
- package/packages/irc-server/tests/actor.test.ts +19 -47
- package/packages/irc-test-support/package.json +1 -1
- 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 +1 -1
- package/tools/ci-hardening/src/index.ts +2 -0
- package/tools/ci-hardening/src/validate.ts +57 -0
- package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
- package/tools/ci-hardening/tests/validate.test.ts +42 -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
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,311 @@ For the release process itself — versioning policy, pre-release checklist,
|
|
|
10
10
|
cutting a tag, rolling back — see [`docs/Release-Process.md`](docs/Release-Process.md).
|
|
11
11
|
Cross-reference `progress.md` / `tickets.md` for per-ticket detail.
|
|
12
12
|
|
|
13
|
+
## [0.10.0] - 2026-08-13
|
|
14
|
+
|
|
15
|
+
The main change in this release is **AWS web client parity** — the
|
|
16
|
+
vendored Kiwi IRC SPA is no longer Cloudflare-only. AWS now hosts it
|
|
17
|
+
via an **S3 + CloudFront + OAC** `StaticSite` construct (opt-in via
|
|
18
|
+
CDK context) with a **stack-output-driven two-phase deploy**, and the
|
|
19
|
+
API Gateway `$connect` route gains an opt-in `WEB_ORIGINS` CSWSH
|
|
20
|
+
defence that mirrors the CF Worker's. Alongside it: the SASL
|
|
21
|
+
`AccountStore` is **collapsed into `ServicesStore`** as the single
|
|
22
|
+
credential home (a one-shot backfill migration ships for existing
|
|
23
|
+
deployments), **NickServ `SET PASSWORD`** lands as self-service
|
|
24
|
+
password change, and the staging/prod deploy split is **collapsed**
|
|
25
|
+
into a single target per platform. A round of **AWS deploy security
|
|
26
|
+
hardening** ships: the CI workflow is **OIDC-only** (no long-lived
|
|
27
|
+
access keys), and API Gateway full-frame body logging
|
|
28
|
+
(`DataTraceEnabled`) is **off by default and hard-locked** so IRC
|
|
29
|
+
frames are never written to CloudWatch. Finally, `UuidIdFactory` now
|
|
30
|
+
sources its random bytes from `globalThis.crypto` (CSPRNG) instead of
|
|
31
|
+
`Math.random`, so generated `msgid` / session ids are cryptographic-
|
|
32
|
+
grade.
|
|
33
|
+
|
|
34
|
+
### Added — AWS web client (`@serverless-ircd/aws-stack`, `@serverless-ircd/aws-adapter`, `@serverless-ircd/web`)
|
|
35
|
+
|
|
36
|
+
The web client is no longer Cloudflare-only. AWS provisions a
|
|
37
|
+
static-site origin for the SPA while the wss IRC endpoint stays on
|
|
38
|
+
API Gateway — two origins, same `apps/web/dist/` artifact.
|
|
39
|
+
|
|
40
|
+
- **`StaticSite` construct** (`apps/aws-stack/src/static-site.ts`): a
|
|
41
|
+
private S3 origin bucket fronted by a CloudFront distribution with
|
|
42
|
+
**Origin Access Control (OAC)** — direct S3 access is denied by the
|
|
43
|
+
bucket policy; only CloudFront URLs resolve. `defaultRootObject` is
|
|
44
|
+
`index.html` (the project landing page at `/`), and a custom error
|
|
45
|
+
response maps S3 `403`/`404` → `200 /webclient/index.html` so
|
|
46
|
+
client-side SPA routes resolve. Custom domain (ACM certificate +
|
|
47
|
+
Route53 `ARecord` alias) is opt-in via CDK context
|
|
48
|
+
(`webSiteCustomDomain` / `webSiteCertificateArn` /
|
|
49
|
+
`webSiteHostedZoneName` / `webSiteHostedZoneId`); omitted by default
|
|
50
|
+
to use `*.cloudfront.net`. Three stable CfnOutputs (`WebsiteURL`,
|
|
51
|
+
`WebsiteBucketName`, `WebsiteDistributionId`) drive the deploy
|
|
52
|
+
pipeline below.
|
|
53
|
+
- **CloudFront directory-index viewer-request function**: S3 REST
|
|
54
|
+
(used with OAC) does not serve index documents, so `/docs/` or
|
|
55
|
+
`/docs` would 404 and fall through to the SPA error response
|
|
56
|
+
(serving the Kiwi shell instead of the rendered docs index). A
|
|
57
|
+
viewer-request CloudFront function now resolves directory-style
|
|
58
|
+
URIs: trailing-slash paths (`/docs/`, `/`) get `index.html`
|
|
59
|
+
appended as an internal rewrite, and extensionless paths without a
|
|
60
|
+
trailing slash (`/docs`) get a `301` redirect to the trailing-slash
|
|
61
|
+
form so the browser resolves relative asset links against the
|
|
62
|
+
directory. Real assets (`.html`, `.css`, `.js`, …) are served
|
|
63
|
+
unchanged. Mirrors the CF Worker `[assets]` binding, which resolves
|
|
64
|
+
directory indexes automatically.
|
|
65
|
+
- **`$connect` Origin allowlist (CSWSH defence)**: API Gateway
|
|
66
|
+
WebSocket has no built-in `Origin` validation, so the `$connect`
|
|
67
|
+
Lambda now enforces it. `WEB_ORIGINS` (comma-separated) is parsed
|
|
68
|
+
into a normalised lowercase set at cold start; a browser-sent
|
|
69
|
+
`Origin` not in the set is denied with `403` (API Gateway closes
|
|
70
|
+
the upgrade). The defence is **opt-in** — unset `WEB_ORIGINS`
|
|
71
|
+
disables the check entirely so existing bare-IRC deployments without
|
|
72
|
+
a web frontend are unchanged on upgrade. Non-browser clients (curl,
|
|
73
|
+
WeeChat, the `tcp-ws-forwarder`, the NLB TCP+TLS path) never send
|
|
74
|
+
`Origin` and always proceed. Deliberate divergence from the CF
|
|
75
|
+
policy: **explicit-allowlist only** (no same-origin auto-derive),
|
|
76
|
+
because the SPA is on a CloudFront origin and the wss endpoint is
|
|
77
|
+
on an API Gateway origin, so the request's own host is never the
|
|
78
|
+
SPA's origin. Mirrors the CF Worker's parsing semantics (trimmed,
|
|
79
|
+
lowercased, scheme+host match).
|
|
80
|
+
- **`prod-aws` build env**: a new `apps/web/static/config.prod-aws.json`
|
|
81
|
+
+ `--env prod-aws` (and a `build:prod-aws` pnpm script) select the
|
|
82
|
+
AWS API-Gateway-shaped config. The `server` field carries
|
|
83
|
+
`{{API_ID}}` / `{{REGION}}` / `{{STAGE}}` placeholders the deploy
|
|
84
|
+
pipeline substitutes at bake time, so no concrete wss URL is
|
|
85
|
+
hardcoded in the repo. Because irc-framework prepends `wss://`
|
|
86
|
+
itself from `tls: true`, the baked URL is **split** into a
|
|
87
|
+
hostname-only `server` and a stage-name `direct_path` (e.g.
|
|
88
|
+
`/<stage>`) — baking the full `wss://` URL into `server` produced
|
|
89
|
+
a doubled-scheme `wss://wss//...` URL that Firefox rejects.
|
|
90
|
+
- **Stack-output-driven deploy pipeline** (`scripts/deploy-web-aws.mjs`,
|
|
91
|
+
a new `@serverless-ircd/deploy-scripts` workspace package): unlike
|
|
92
|
+
the CF Worker (where `wrangler deploy` ships the SPA in the same
|
|
93
|
+
command as the runtime), the AWS SPA deploy is **two-phase** because
|
|
94
|
+
the baked `config.json` carries the API Gateway WebSocket URL as a
|
|
95
|
+
literal that does not exist at synth time. The script runs
|
|
96
|
+
`describe-stacks` → `pnpm --filter web build:prod-aws -- --api-url
|
|
97
|
+
<ConnectUrl>` → `aws s3 sync --delete` →
|
|
98
|
+
`cloudfront create-invalidation /*`. Fails loudly on missing stack
|
|
99
|
+
outputs or unsubstituted `{{...}}` placeholders. The
|
|
100
|
+
`deploy-web` CI job (`.github/workflows/deploy-aws.yml`) runs it
|
|
101
|
+
with `needs: deploy`.
|
|
102
|
+
|
|
103
|
+
### Added — NickServ `SET PASSWORD` (`@serverless-ircd/irc-core`)
|
|
104
|
+
|
|
105
|
+
- **Self-service password change.** `PRIVMSG NickServ :SET PASSWORD
|
|
106
|
+
<old> <new>` re-verifies the current password (defence against a
|
|
107
|
+
hijacked `+r` session), enforces a configurable minimum (default 8)
|
|
108
|
+
and a fixed maximum (256, the scrypt input budget), then re-hashes
|
|
109
|
+
and persists via `ServicesStore.setNickPassword`. The caller's
|
|
110
|
+
session stays `+r`; other sessions on the same account are not
|
|
111
|
+
kicked. Wrong old password is indistinguishable from "no such
|
|
112
|
+
account" (no enumeration vector). Wired through
|
|
113
|
+
`InMemoryServicesStore` and `PersistentServicesStore` with round-
|
|
114
|
+
trip tests on the D1 and DynamoDB backends.
|
|
115
|
+
|
|
116
|
+
### Added — CSPRNG-backed ids (`@serverless-ircd/irc-core`)
|
|
117
|
+
|
|
118
|
+
- **`UuidIdFactory` now uses `globalThis.crypto.getRandomValues`.** The
|
|
119
|
+
previous `Math.random`-based UUIDv4 generation was unsuitable for
|
|
120
|
+
unforgeable `msgid` / session ids — a predictable id would let a
|
|
121
|
+
client forge message attribution. The global `crypto.getRandomValues`
|
|
122
|
+
is available on Node ≥ 19 and Cloudflare Workers without an import.
|
|
123
|
+
Generated ids are now cryptographic-grade. The `IdFactory` port and
|
|
124
|
+
the deterministic `testIdFactory` test seam are unchanged, so
|
|
125
|
+
reducer determinism (reducers never touch real randomness) is
|
|
126
|
+
preserved; only the production factory changed.
|
|
127
|
+
|
|
128
|
+
### Changed — Single credential home (`@serverless-ircd/irc-core`, `@serverless-ircd/cf-adapter`, `@serverless-ircd/aws-adapter`)
|
|
129
|
+
|
|
130
|
+
The SASL `AccountStore` port is **removed**. `ServicesStore` is now
|
|
131
|
+
the single credential home: SASL PLAIN, SASL EXTERNAL (CertFP),
|
|
132
|
+
NickServ `IDENTIFY`, and `PASS <nick>:<password>` all verify through
|
|
133
|
+
the same scrypt-hashed `verifyNick` / `verifyCertFP` surface. This
|
|
134
|
+
closes the cross-store credential drift flagged as a known limitation
|
|
135
|
+
in v0.9.0 — a nick registered via NickServ `REGISTER` after the worker
|
|
136
|
+
booted now authenticates via SASL/PASS immediately, because there is
|
|
137
|
+
no second store to drift out of sync with.
|
|
138
|
+
|
|
139
|
+
- **`ServicesStore` grows CertFP**: `verifyCertFP` / `addCertFP` /
|
|
140
|
+
`removeCertFP` are added to the port, and `RegisteredNick` gains a
|
|
141
|
+
`certSubjects: string[]` field so SASL EXTERNAL resolves through the
|
|
142
|
+
services store (previously SASL EXTERNAL only worked against the
|
|
143
|
+
static `InMemoryAccountStore`, never the hashed D1/DynamoDB table).
|
|
144
|
+
- **D1 `cert_subjects` column auto-migrates**: the CF D1 path runs
|
|
145
|
+
`ALTER TABLE nickserv_accounts ADD COLUMN cert_subjects` inside
|
|
146
|
+
`migrateServicesSchema` on cold start (`CREATE TABLE IF NOT EXISTS`
|
|
147
|
+
+ the `ALTER`), so no manual DDL is needed. DynamoDB is schemaless.
|
|
148
|
+
- **Removed**: `AccountStore` port, `InMemoryAccountStore`,
|
|
149
|
+
`HashedAccountStore`, the D1/DynamoDB `account-store`
|
|
150
|
+
implementations, `SaslPayload` / `SaslResult` / `SaslAccountCredential`
|
|
151
|
+
/ `constantTimeEquals` / `ingestAccountCredential`, and the
|
|
152
|
+
`accounts` / `Accounts` table reads. The new code only reads
|
|
153
|
+
`nickserv_accounts` / `Services`.
|
|
154
|
+
- **`tools/migrate-accounts-to-services.ts`**: a one-shot, idempotent,
|
|
155
|
+
never-overwrite backfill of legacy `accounts` / `Accounts` rows into
|
|
156
|
+
`nickserv_accounts` / `Services`. Reads the still-deployed
|
|
157
|
+
`SASL_ACCOUNTS` env var at run time so env-seeded accounts are
|
|
158
|
+
carried into the services table. `cert_subjects` defaults to `'[]'`
|
|
159
|
+
for every migrated row (the legacy table had no cert column).
|
|
160
|
+
**Run this before the v0.10.0 build deploys** — see the migration
|
|
161
|
+
section below.
|
|
162
|
+
|
|
163
|
+
### Changed — Single deploy target per platform
|
|
164
|
+
|
|
165
|
+
The staging/prod deploy split is **collapsed** into one target per
|
|
166
|
+
platform. Staging vs production isolation is now driven by which
|
|
167
|
+
account/region (AWS) or which Worker credentials (CF) the deploy
|
|
168
|
+
targets, not by stack/table/Worker name templating.
|
|
169
|
+
|
|
170
|
+
- **AWS CDK**: drops the `environmentName` prop, `prefixedTableName`,
|
|
171
|
+
and the `IrcAwsStack-<env>` stack id. Tables use bare logical ids
|
|
172
|
+
(`Connections`, `Nicks`, …); the stack is always `IrcAwsStack`.
|
|
173
|
+
- **Cloudflare**: drops the `env.staging` blocks in the cf-worker and
|
|
174
|
+
cf-tcp-container wrangler configs.
|
|
175
|
+
- **Web build**: drops `build:staging` / `config.staging.json`; `--env`
|
|
176
|
+
now accepts only `prod` (or the default dev config) and `prod-aws`.
|
|
177
|
+
- **CI**: collapses `deploy-*: staging` jobs into single `deploy` /
|
|
178
|
+
`deploy-web` jobs; script names align (`pnpm deploy:cf`,
|
|
179
|
+
`pnpm deploy:aws`, `pnpm deploy:cf:tcp`, `pnpm smoke:aws`,
|
|
180
|
+
`pnpm smoke:cf`). Deploy workflows remain `workflow_dispatch`-only
|
|
181
|
+
(no push trigger).
|
|
182
|
+
|
|
183
|
+
### Added — AWS deploy security
|
|
184
|
+
|
|
185
|
+
- **OIDC-only CI deploys.** The GitHub Actions deploy workflow
|
|
186
|
+
(`deploy-aws.yml`) accepts **only** GitHub OIDC web-identity
|
|
187
|
+
credentials. `aws-access-key-id` / `aws-secret-access-key` inputs
|
|
188
|
+
are dropped from every `configure-aws-credentials` step in both the
|
|
189
|
+
`deploy` and `deploy-web` jobs; the workflow requires
|
|
190
|
+
`AWS_DEPLOY_ROLE_ARN` and fails fast with `::error::` if it is
|
|
191
|
+
unset, then asserts `aws sts get-caller-identity` matches the
|
|
192
|
+
configured role before any `cdk deploy` (defence-in-depth against a
|
|
193
|
+
silent fallback to ambient static credentials). Long-lived access
|
|
194
|
+
keys powerful enough to deploy CloudFormation + IAM + Lambda +
|
|
195
|
+
DynamoDB are effectively account-admin; a single exfil vector
|
|
196
|
+
yields full account takeover. The full IAM trust policy snippet,
|
|
197
|
+
condition keys, and verification steps are in
|
|
198
|
+
`docs/AWS-Deployment.md` §17. A `ci-hardening` lint + test suite
|
|
199
|
+
(`findAwsCredentialSteps`, `readWorkflowPermissions`) locks the
|
|
200
|
+
OIDC-only contract on every PR.
|
|
201
|
+
- **API Gateway `DataTraceEnabled` off by default and hard-locked.**
|
|
202
|
+
Full-frame body tracing wrote every IRC frame (`PASS <password>`,
|
|
203
|
+
`AUTHENTICATE <base64-SASL-PLAIN>`, `JOIN #chan <key>`,
|
|
204
|
+
`PRIVMSG`/`NOTICE`) to the APIGW execution-log group, leaking
|
|
205
|
+
credentials to anyone with `logs:GetLogEvents`. Now safe-by-default
|
|
206
|
+
(`false`); because the staging/prod split collapsed, there is no
|
|
207
|
+
quiet "non-prod" path to re-enable it. The only way back on is an
|
|
208
|
+
explicit **two-step opt-in** (`-c allowDataTrace=true` **and** `-c
|
|
209
|
+
iUnderstandThisLeaksCredentials=true`) that refuses synthesis unless
|
|
210
|
+
both flags are set — see `docs/AWS-Deployment.md` §7.6. Tear the
|
|
211
|
+
sandbox stack down immediately after debugging; never deploy that
|
|
212
|
+
combination to a shared account.
|
|
213
|
+
|
|
214
|
+
### Changed — Documentation
|
|
215
|
+
|
|
216
|
+
- **`README.md` updated** to cover AWS web client hosting (the web
|
|
217
|
+
client section was previously Cloudflare-only), the AWS `$connect`
|
|
218
|
+
CSWSH defence, the OIDC-only deploy model, the `DataTraceEnabled`
|
|
219
|
+
hard-lock, and the `scripts/deploy-web-aws.mjs` two-phase deploy.
|
|
220
|
+
The CF-vs-AWS origin difference is laid out in a table, and the
|
|
221
|
+
CSWSH section now documents both adapters' modes (same-origin
|
|
222
|
+
auto-derive on CF; explicit-allowlist-only on AWS).
|
|
223
|
+
|
|
224
|
+
### ⚠️ Migration required
|
|
225
|
+
|
|
226
|
+
- **Run `tools/migrate-accounts-to-services.ts` BEFORE deploying
|
|
227
|
+
v0.10.0** if the deployment has any rows in the legacy `accounts`
|
|
228
|
+
(CF D1) / `Accounts` (AWS DynamoDB) table. The v0.10.0 build only
|
|
229
|
+
reads `nickserv_accounts` / `Services`, so legacy rows that are not
|
|
230
|
+
backfilled will stop authenticating. The script is one-shot,
|
|
231
|
+
idempotent (`INSERT OR IGNORE` on D1 / conditional `PutItem` on
|
|
232
|
+
DynamoDB), and never-overwrites a nick already registered in the
|
|
233
|
+
services table. It reads the still-deployed `SASL_ACCOUNTS` env var
|
|
234
|
+
at run time so env-seeded accounts are carried through. See the
|
|
235
|
+
script header for the per-platform invocation:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Cloudflare D1
|
|
239
|
+
node --import tsx tools/migrate-accounts-to-services.ts \
|
|
240
|
+
--platform cf --database <d1-name> --remote
|
|
241
|
+
|
|
242
|
+
# AWS DynamoDB
|
|
243
|
+
node --import tsx tools/migrate-accounts-to-services.ts \
|
|
244
|
+
--platform aws --accounts-table Accounts --services-table Services \
|
|
245
|
+
--region us-east-1
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- **Single deploy target: rename your deploy commands.** The
|
|
249
|
+
staging-specific commands are gone. Update any scripts, runbooks, or
|
|
250
|
+
CI that called `pnpm deploy:cf:staging` / `pnpm deploy:aws:staging`
|
|
251
|
+
/ `--env staging` to the single-target form (`pnpm deploy:cf`,
|
|
252
|
+
`pnpm deploy:aws`). Staging vs production isolation is now by
|
|
253
|
+
account/region (AWS) or by Worker credentials (CF), not by command
|
|
254
|
+
suffix.
|
|
255
|
+
- **AWS CI: switch to OIDC.** If the repo still has
|
|
256
|
+
`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` secrets, delete them
|
|
257
|
+
and invalidate the underlying IAM access key. Configure
|
|
258
|
+
`AWS_DEPLOY_ROLE_ARN` (a GitHub-OIDC-trusted role) per
|
|
259
|
+
`docs/AWS-Deployment.md` §17; there is no static-key fallback.
|
|
260
|
+
- **No persisted-DO-state schema change.** `PERSISTED_STATE_VERSION`
|
|
261
|
+
stays `1`. The `cert_subjects` D1 column is added by an idempotent
|
|
262
|
+
`ALTER TABLE` on cold start; the D1/DynamoDB services-row shape
|
|
263
|
+
change is backward-compatible for reads. **Code rollback across
|
|
264
|
+
this release is safe** provided the account-migration backfill was
|
|
265
|
+
run (rolling back the code re-introduces the legacy `accounts`/
|
|
266
|
+
`Accounts` reads, which still work as long as the table still
|
|
267
|
+
exists — it is not auto-dropped).
|
|
268
|
+
|
|
269
|
+
### Known limitations
|
|
270
|
+
|
|
271
|
+
- **The web client remains partial.** The Playwright headless-browser
|
|
272
|
+
e2e against a deployed frontend is still pending; the SPA is
|
|
273
|
+
exercised via the WS smoke (`scripts/smoke.mjs`) and the `apps/web`
|
|
274
|
+
unit / build-smoke suite.
|
|
275
|
+
- **The AWS web client `StaticSite` requires a custom domain to
|
|
276
|
+
provision via `bin/aws.ts`.** Provisioning the construct without a
|
|
277
|
+
custom domain (default `*.cloudfront.net` only) currently requires
|
|
278
|
+
editing `bin/aws.ts` to pass `webSite: {}` directly. Tracked as a
|
|
279
|
+
follow-up.
|
|
280
|
+
- Same transport limits as v0.9.0: the **CF TCP path requires
|
|
281
|
+
Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
|
|
282
|
+
origin; the **AWS TCP path** uses NLB + Lambda streaming and is
|
|
283
|
+
subject to Lambda idle-timeout / stream-duration limits. The
|
|
284
|
+
WebSocket path remains the zero-extra-deps default.
|
|
285
|
+
- **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
|
|
286
|
+
CA/trust-store** on both platforms and is not enabled by default.
|
|
287
|
+
The `cert_subjects` plumbing lands the server-side storage; the
|
|
288
|
+
adapter-side client-cert capture at the TLS edge remains a
|
|
289
|
+
follow-up.
|
|
290
|
+
- **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
|
|
291
|
+
TheLounge / matrix-IRC bridge) remains pending.
|
|
292
|
+
- **Coverage hardening is partial.** `irc-core`, `irc-server`,
|
|
293
|
+
`in-memory-runtime`, and **`cf-adapter`** (new this release) sit at
|
|
294
|
+
100%; `aws-adapter` and `aws-stack` clear the 90% gate. The
|
|
295
|
+
remaining packages (`local-cli`, `load-test`, `cf-tcp-container`,
|
|
296
|
+
`tcp-ws-forwarder`, `irc-test-support`, `web`) are **above the gate
|
|
297
|
+
but below 100%** — follow-ups drive each to full coverage.
|
|
298
|
+
|
|
299
|
+
### Security
|
|
300
|
+
|
|
301
|
+
- **OIDC-only AWS deploys** (above): removes long-lived access keys
|
|
302
|
+
from the deploy path — the highest-impact exfil vector against an
|
|
303
|
+
AWS account with deploy permissions.
|
|
304
|
+
- **APIGW `DataTraceEnabled` hard-locked off** (above): IRC frames
|
|
305
|
+
(including `PASS`, `AUTHENTICATE <SASL-PLAIN>`, channel keys) are
|
|
306
|
+
no longer written to CloudWatch even if an operator toggles the
|
|
307
|
+
console setting by hand; the two-flag escape hatch cannot be set by
|
|
308
|
+
accident.
|
|
309
|
+
- **CSPRNG-backed `msgid` / session ids** (above): generated ids are
|
|
310
|
+
now cryptographic-grade, closing a forgery vector in the previous
|
|
311
|
+
`Math.random`-based `UuidIdFactory`.
|
|
312
|
+
- No other auth path changed. The scrypt-hashed credential store,
|
|
313
|
+
server-password gate, and CF-side CSWSH defence are unchanged from
|
|
314
|
+
v0.9.0.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
13
318
|
## [0.9.0] - 2026-08-10
|
|
14
319
|
|
|
15
320
|
The main change in this release is **rounding out the integrated IRC
|
package/README.md
CHANGED
|
@@ -32,20 +32,39 @@ One TypeScript codebase. Two serverless substrates.
|
|
|
32
32
|
>
|
|
33
33
|
> **Web client.** A vendored **Kiwi IRC** SPA is served at
|
|
34
34
|
> `/webclient/` and a static landing page at `/` directly by the
|
|
35
|
-
> Cloudflare Worker
|
|
36
|
-
>
|
|
37
|
-
>
|
|
38
|
-
>
|
|
39
|
-
> `/docs/`.
|
|
35
|
+
> Cloudflare Worker (via its `[assets]` binding) **or** by AWS via an
|
|
36
|
+
> **S3 + CloudFront + OAC** `StaticSite` construct (opt-in via CDK
|
|
37
|
+
> context). One `apps/web/dist/` artifact ships on both platforms
|
|
38
|
+
> unchanged. The `docs/` wiki is rendered to standalone HTML at
|
|
39
|
+
> `/docs/`. Both platforms wire **Cross-Site WebSocket Hijacking
|
|
40
|
+
> (CSWSH) defense** into the WS upgrade path: the CF Worker uses
|
|
41
|
+
> same-origin auto-derive by default with an optional `WEB_ORIGINS`
|
|
42
|
+
> allowlist for cross-origin deploys; the AWS `$connect` Lambda uses
|
|
43
|
+
> an explicit `WEB_ORIGINS` allowlist only (the SPA and the API
|
|
44
|
+
> Gateway wss endpoint are on different origins, so auto-derive does
|
|
45
|
+
> not apply). AWS deploys are **stack-output-driven** (a two-phase
|
|
46
|
+
> `scripts/deploy-web-aws.mjs` bakes the real `ConnectUrl` into the
|
|
47
|
+
> SPA config, then `s3 sync` + CloudFront invalidation).
|
|
48
|
+
>
|
|
49
|
+
> **Deploy security.** The AWS CI deploy is **OIDC-only** — the
|
|
50
|
+
> GitHub Actions workflow assumes an IAM role via GitHub OIDC web
|
|
51
|
+
> identity (no long-lived access keys, with an in-workflow assertion
|
|
52
|
+
> that the assumed role matches `AWS_DEPLOY_ROLE_ARN` before any
|
|
53
|
+
> `cdk deploy`). API Gateway full-frame body logging
|
|
54
|
+
> (`DataTraceEnabled`) is **off by default and hard-locked**, so IRC
|
|
55
|
+
> frames (`PASS`, `AUTHENTICATE <SASL-PLAIN>`, channel keys,
|
|
56
|
+
> `PRIVMSG`/`NOTICE`) are never written to CloudWatch; the only way
|
|
57
|
+
> back on is an explicit two-flag sandbox escape hatch.
|
|
40
58
|
>
|
|
41
59
|
> **Integrated IRC services.** NickServ, ChanServ, HostServ,
|
|
42
60
|
> OperServ, and MemoServ run inside the daemon (no separate services
|
|
43
61
|
> process, no S2S link) backed by a `ServicesStore` port with
|
|
44
62
|
> **persistent D1 (Cloudflare) and DynamoDB (AWS) backends**
|
|
45
|
-
> (write-behind, surviving redeploys). The
|
|
46
|
-
>
|
|
47
|
-
>
|
|
48
|
-
>
|
|
63
|
+
> (write-behind, surviving redeploys). The `ServicesStore` is the
|
|
64
|
+
> **single credential home**: SASL PLAIN, SASL EXTERNAL (CertFP),
|
|
65
|
+
> NickServ `IDENTIFY`, and `PASS <nick>:<password>` all verify through
|
|
66
|
+
> the same scrypt-hashed `verifyNick` / `verifyCertFP` surface, and
|
|
67
|
+
> `draft/read-marker` / `draft/pre-away` persist through the
|
|
49
68
|
> `ServicesStore`.
|
|
50
69
|
>
|
|
51
70
|
> See `CHANGELOG.md` for the per-release manifests.
|
|
@@ -161,6 +180,8 @@ ServerlessIRCd/
|
|
|
161
180
|
│ ├── ci-hardening/ coverage-gate + mutation-config validators
|
|
162
181
|
│ ├── seed-aws-accounts.ts scrypt-hash SASL PLAIN accounts into DynamoDB
|
|
163
182
|
│ └── seed-cf-accounts.ts scrypt-hash SASL PLAIN accounts into Cloudflare D1
|
|
183
|
+
├── scripts/
|
|
184
|
+
│ └── deploy-web-aws.mjs stack-output-driven AWS web client deploy (describe → bake → s3 sync → invalidate)
|
|
164
185
|
├── pnpm-workspace.yaml turbo.json tsconfig.base.json
|
|
165
186
|
└── README.md CHANGELOG.md
|
|
166
187
|
```
|
|
@@ -296,14 +317,15 @@ treats `state.account !== undefined` as already authorised, so a
|
|
|
296
317
|
deployment with both `SERVER_PASSWORD` and configured SASL accounts
|
|
297
318
|
does not need to hand the shared password to identified users. The
|
|
298
319
|
server-wide gate is a shared deployment secret (never log it); per-user
|
|
299
|
-
credentials still go through the
|
|
300
|
-
(`
|
|
320
|
+
credentials still go through the unified services credential store
|
|
321
|
+
(`ServicesStore.verifyNick` — D1 `nickserv_accounts` / DynamoDB
|
|
322
|
+
`Services`), which is unaffected.
|
|
301
323
|
|
|
302
324
|
**PASS-based account login:** in addition to SASL, a client may identify
|
|
303
325
|
to its NickServ account by sending `PASS <nick>:<password>` (the same
|
|
304
326
|
`<nick>:<password>` literal the `SASL_ACCOUNTS` seed tooling uses).
|
|
305
|
-
When
|
|
306
|
-
+ `USER …` verifies the credentials
|
|
327
|
+
When a `ServicesStore` is configured, `NICK alice` + `PASS alice:hunter2`
|
|
328
|
+
+ `USER …` verifies the credentials via `services.verifyNick` and, on
|
|
307
329
|
success, logs the connection in as `alice` — emitting `900 RPL_LOGGEDIN`
|
|
308
330
|
+ `903 RPL_SASLSUCCESS` (the same "account is set" numerics SASL uses)
|
|
309
331
|
before `001 RPL_WELCOME`, stamping user mode `+r`, and running the same
|
|
@@ -442,12 +464,22 @@ report).
|
|
|
442
464
|
## Web client (`apps/web`)
|
|
443
465
|
|
|
444
466
|
A vendored **Kiwi IRC** SPA served at `/webclient/` and a static project
|
|
445
|
-
landing page served at `/`, both baked out of `apps/web/dist
|
|
446
|
-
served
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
467
|
+
landing page served at `/`, both baked out of `apps/web/dist/`. The same
|
|
468
|
+
artifact is served by **both** platforms:
|
|
469
|
+
|
|
470
|
+
| Platform | HTTP/SPA origin | WebSocket origin | Same-origin? |
|
|
471
|
+
|-------------|----------------------------------------------------|---------------------------------------------------|--------------|
|
|
472
|
+
| Cloudflare | the Worker (`*.workers.dev` / custom domain) | the same Worker | **Yes** — one domain serves both |
|
|
473
|
+
| AWS | CloudFront (`*.cloudfront.net` / custom domain) | API Gateway (`*.execute-api.*.amazonaws.com`) | **No** — two different endpoints |
|
|
474
|
+
|
|
475
|
+
On Cloudflare the Worker's `[assets]` binding serves the SPA and the WS
|
|
476
|
+
upgrade on one origin — the browser opens a native `wss://` directly to
|
|
477
|
+
the Worker, no proxy or gateway. On AWS the SPA is fronted by an
|
|
478
|
+
**S3 + CloudFront + OAC** `StaticSite` construct
|
|
479
|
+
(`apps/aws-stack/src/static-site.ts`, opt-in via CDK context) and a
|
|
480
|
+
two-phase deploy bakes the real `ConnectUrl` into the SPA config (see
|
|
481
|
+
"Deploying on AWS" below). See `docs/WebClientGuide.md` for the CF
|
|
482
|
+
end-to-end guide and `docs/AWS-Deployment.md` §16 for the AWS path.
|
|
451
483
|
|
|
452
484
|
Build the SPA + landing page (needs the submodule + `yarn`, provided by
|
|
453
485
|
`corepack enable`):
|
|
@@ -455,7 +487,7 @@ Build the SPA + landing page (needs the submodule + `yarn`, provided by
|
|
|
455
487
|
```bash
|
|
456
488
|
git submodule update --init apps/web/upstream # one-time per clone
|
|
457
489
|
git submodule update --init docs # one-time per clone (docs site)
|
|
458
|
-
pnpm --filter web build
|
|
490
|
+
pnpm --filter web build # or build:prod (CF) / build:prod-aws (AWS)
|
|
459
491
|
# → apps/web/dist/index.html (landing page, served at /)
|
|
460
492
|
# → apps/web/dist/webclient/index.html (Kiwi SPA, served at /webclient/)
|
|
461
493
|
# → apps/web/dist/webclient/static/config.json (baked, env-specific)
|
|
@@ -470,8 +502,10 @@ existing `[assets]` binding serves them at `/docs/<slug>.html` (and
|
|
|
470
502
|
command if it is missing or empty (mirroring the Kiwi `upstream/` guard).
|
|
471
503
|
`Home.md` is the docs landing page; `ADR-Index.md` lists every ADR.
|
|
472
504
|
|
|
473
|
-
|
|
474
|
-
|
|
505
|
+
### Deploying on Cloudflare
|
|
506
|
+
|
|
507
|
+
Run everything locally (Worker serves SPA + landing page + WS on one
|
|
508
|
+
origin):
|
|
475
509
|
|
|
476
510
|
```bash
|
|
477
511
|
pnpm build # workspace packages
|
|
@@ -482,18 +516,52 @@ pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
|
|
|
482
516
|
# /health → plaintext liveness
|
|
483
517
|
```
|
|
484
518
|
|
|
485
|
-
Deploy
|
|
519
|
+
Deploy the Worker (Worker + assets in one command):
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
pnpm deploy:cf # wrangler deploy
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Deploying on AWS
|
|
526
|
+
|
|
527
|
+
The web client is **opt-in**: provision the `StaticSite` construct by
|
|
528
|
+
passing `webSite*` CDK context on the stack deploy, and (once the SPA
|
|
529
|
+
ships) set `webOrigins` for the CSWSH defence:
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
pnpm deploy:aws -- \
|
|
533
|
+
-c webSiteCustomDomain=app.example.com \
|
|
534
|
+
-c webSiteCertificateArn=arn:aws:acm:us-east-1:... \
|
|
535
|
+
-c webSiteHostedZoneName=example.com. -c webSiteHostedZoneId=... \
|
|
536
|
+
-c webOrigins=https://app.example.com
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Then bake + ship the SPA (stack-output-driven — reads `ConnectUrl`,
|
|
540
|
+
`WebsiteBucketName`, `WebsiteDistributionId` from the stack outputs,
|
|
541
|
+
bakes the real wss URL into `config.json`, `s3 sync`s the build, and
|
|
542
|
+
invalidates the CloudFront edge cache):
|
|
486
543
|
|
|
487
544
|
```bash
|
|
488
|
-
|
|
545
|
+
node scripts/deploy-web-aws.mjs
|
|
489
546
|
```
|
|
490
547
|
|
|
548
|
+
The ACM certificate **must** be in `us-east-1` (CloudFront requirement).
|
|
549
|
+
See `docs/AWS-Deployment.md` §16 for the full two-phase flow, the
|
|
550
|
+
`server` / `direct_path` config split (irc-framework prepends `wss://`
|
|
551
|
+
itself, so the host is baked scheme-less), and the custom-domain setup.
|
|
552
|
+
|
|
491
553
|
### WebSocket Origin policy (CSWSH defense)
|
|
492
554
|
|
|
493
|
-
WebSocket upgrades do **not** follow the same-origin policy, so
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
555
|
+
WebSocket upgrades do **not** follow the same-origin policy, so a
|
|
556
|
+
malicious page can open a WebSocket to the IRC server from a victim's
|
|
557
|
+
browser and drive the session with their credentials (Cross-Site
|
|
558
|
+
WebSocket Hijacking, CSWSH). Both adapters enforce an `Origin` policy
|
|
559
|
+
on the upgrade — but the **modes differ**, because the CF Worker serves
|
|
560
|
+
the SPA and the wss endpoint on the same origin while AWS serves them
|
|
561
|
+
on different origins:
|
|
562
|
+
|
|
563
|
+
**Cloudflare** — see `apps/cf-worker/src/origin-allowlist.ts` and the
|
|
564
|
+
SPA guide §5. Two modes, evaluated in order:
|
|
497
565
|
|
|
498
566
|
1. **Explicit allowlist** — set `WEB_ORIGINS` (comma-separated) for
|
|
499
567
|
cross-origin deploys (SPA on a different domain than the Worker,
|
|
@@ -502,11 +570,27 @@ modes, evaluated in order:
|
|
|
502
570
|
unset/empty, the Worker compares the browser's `Origin` against the
|
|
503
571
|
request's own origin. Match → proceed; mismatch → `403 Forbidden`.
|
|
504
572
|
|
|
505
|
-
`WEB_ORIGINS` is **optional** — same-origin auto-derive needs
|
|
506
|
-
per-env config and works for `*.workers.dev`, custom domains, and
|
|
507
|
-
preview URLs alike.
|
|
508
|
-
|
|
509
|
-
|
|
573
|
+
`WEB_ORIGINS` is **optional** on CF — same-origin auto-derive needs
|
|
574
|
+
zero per-env config and works for `*.workers.dev`, custom domains, and
|
|
575
|
+
preview URLs alike.
|
|
576
|
+
|
|
577
|
+
**AWS** — see `packages/aws-adapter/src/origin-allowlist.ts` and
|
|
578
|
+
`docs/AWS-Deployment.md` §8.2. **Explicit allowlist only** (no
|
|
579
|
+
auto-derive): the SPA is on a CloudFront origin and the wss endpoint is
|
|
580
|
+
on an API Gateway origin, so the request's own host is never the SPA's
|
|
581
|
+
origin. The defence is **opt-in** — unset `WEB_ORIGINS` skips the check
|
|
582
|
+
entirely (existing bare-IRC deployments without a web frontend are
|
|
583
|
+
unchanged on upgrade); set it to the SPA's origin(s) once the web client
|
|
584
|
+
ships:
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
pnpm deploy:aws -- -c webOrigins=https://app.example.com
|
|
588
|
+
# or as a stack prop: webOrigins: 'https://app.example.com,https://staging.app.example.com'
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Non-browser clients (curl, WeeChat, the `tcp-ws-forwarder`, scripted
|
|
592
|
+
harnesses) never send `Origin` and pass through unchanged on both
|
|
593
|
+
platforms.
|
|
510
594
|
|
|
511
595
|
---
|
|
512
596
|
|
|
@@ -637,8 +721,9 @@ for clients with a dedicated slash-command UX. Reached via `PRIVMSG
|
|
|
637
721
|
|
|
638
722
|
- **NickServ** — `REGISTER` / `IDENTIFY` (alias: `ID`) / `DROP` / `INFO`
|
|
639
723
|
(owner + oper only for the `Email:` line) / `SET ENFORCE` + nick
|
|
640
|
-
enforcement on the `NICK` path
|
|
641
|
-
|
|
724
|
+
enforcement on the `NICK` path / `SET PASSWORD` (self-service password
|
|
725
|
+
change; requires the current password re-supplied). `INFO <nick>` with
|
|
726
|
+
no target resolves to the caller's own account.
|
|
642
727
|
- **ChanServ** — `REGISTER` / `DROP` / `SET` (`FOUNDER` / `MLOCK` /
|
|
643
728
|
`RESTRICTED` / `KEEPTOPIC`) / `INFO` / `ACCESS` (`SOP` / `AOP` / `HOP`
|
|
644
729
|
/ `VOP`, each `ADD` / `DEL` / `LIST`) / `LEVELS`, plus the prefix /
|
|
@@ -658,9 +743,11 @@ for clients with a dedicated slash-command UX. Reached via `PRIVMSG
|
|
|
658
743
|
- **MemoServ** — `SEND` / `LIST` / `READ` / `DEL` with queue delivery
|
|
659
744
|
at identify.
|
|
660
745
|
|
|
661
|
-
The
|
|
662
|
-
|
|
663
|
-
|
|
746
|
+
The `ServicesStore` is the **single credential home**: SASL PLAIN, SASL
|
|
747
|
+
EXTERNAL (CertFP), NickServ `IDENTIFY`, and `PASS <nick>:<password>` all
|
|
748
|
+
verify through the same scrypt-hashed `verifyNick` / `verifyCertFP`
|
|
749
|
+
surface, so a registered nick is also a SASL login and vice versa.
|
|
750
|
+
Backends: D1 on Cloudflare, DynamoDB on AWS, in-memory for the
|
|
664
751
|
local CLI / tests (all write-behind; registrations survive redeploys).
|
|
665
752
|
When no store is bound, services commands reply `501` and the rest of
|
|
666
753
|
the daemon is unaffected. See `docs/Services.md` for the full
|
|
@@ -712,9 +799,15 @@ bridges a stock TCP client to a deployed WebSocket endpoint.
|
|
|
712
799
|
account store, service shortcut verbs), adapter backends, and an
|
|
713
800
|
end-to-end registration walkthrough.
|
|
714
801
|
- `docs/WebClientGuide.md` — end-to-end contributor/operator doc for
|
|
715
|
-
the web client: build pipeline, per-env config matrix,
|
|
716
|
-
rationale and the optional `WEB_ORIGINS` var, local dev,
|
|
717
|
-
Cloudflare Pages alternative, and troubleshooting.
|
|
802
|
+
the **Cloudflare** web client: build pipeline, per-env config matrix,
|
|
803
|
+
CSWSH rationale and the optional `WEB_ORIGINS` var, local dev,
|
|
804
|
+
optional Cloudflare Pages alternative, and troubleshooting.
|
|
805
|
+
- `docs/AWS-Deployment.md` — end-to-end AWS deploy guide: first-time
|
|
806
|
+
setup, CDK reference, DynamoDB capacity planning, region strategy,
|
|
807
|
+
cost notes, **OIDC-only CI** (§17), **APIGW `DataTraceEnabled` hard-
|
|
808
|
+
lock** (§7.6), and the **S3 + CloudFront + OAC web client** (§16,
|
|
809
|
+
including the two-phase stack-output-driven deploy and the
|
|
810
|
+
`webOrigins` CSWSH knob).
|
|
718
811
|
- `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md`
|
|
719
812
|
— end-to-end guides for the `:6697` TCP+TLS variants
|
|
720
813
|
(Spectrum/Container on CF, NLB + Lambda streaming on AWS), including
|
package/apps/aws-stack/README.md
CHANGED
|
@@ -21,8 +21,7 @@ The Lambda handler is currently a stub (`{ statusCode: 200 }`); the real
|
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
23
|
pnpm cdk:synth # synthesize the CloudFormation template (cdk.out/)
|
|
24
|
-
pnpm deploy
|
|
25
|
-
pnpm deploy:prod # cdk deploy --all --require-approval never
|
|
24
|
+
pnpm deploy # cdk deploy --all --require-approval never
|
|
26
25
|
pnpm test # synth-time assertions (no AWS, no Docker)
|
|
27
26
|
pnpm typecheck
|
|
28
27
|
```
|
|
@@ -30,9 +29,8 @@ pnpm typecheck
|
|
|
30
29
|
Repo-level shortcuts (from the monorepo root):
|
|
31
30
|
|
|
32
31
|
```sh
|
|
33
|
-
pnpm deploy:aws
|
|
34
|
-
pnpm
|
|
35
|
-
pnpm smoke:aws:staging # deferred (smoke harness not yet wired)
|
|
32
|
+
pnpm deploy:aws
|
|
33
|
+
pnpm smoke:aws # deferred (smoke harness not yet wired)
|
|
36
34
|
```
|
|
37
35
|
|
|
38
36
|
## Optional localstack validation
|