diff --git a/SECURITY.md b/SECURITY.md index 81f028e..5c09b3c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -36,7 +36,7 @@ to Stalwart Labs with credit to you, and you'll be told that has happened. This repository is the mail server. The web front ends have their own: - [inbuxa-admin](https://git.coffeylabs.org/inbuxa/inbuxa-admin) -- [ihasmail-inbuxa](https://git.coffeylabs.org/inbuxa/ihasmail-inbuxa) +- [inbuxa-webmail](https://git.coffeylabs.org/inbuxa/inbuxa-webmail) Upstream's own security documents are kept in `.github-upstream/` for reference. They describe Stalwart Labs' process, not this project's. diff --git a/crates/common/src/manager/first_party.rs b/crates/common/src/manager/first_party.rs index e13838a..a83128e 100644 --- a/crates/common/src/manager/first_party.rs +++ b/crates/common/src/manager/first_party.rs @@ -14,7 +14,7 @@ //! application names another; //! - INBUXA Admin hosted elsewhere, as `inbuxa-admin`, when `INBUXA_ADMIN_URL` //! is set; -//! - ihasmail-inbuxa, as the confidential client `ihasmail-inbuxa`, when +//! - inbuxa-webmail, as the confidential client `ihasmail-inbuxa`, when //! `INBUXA_WEBMAIL_URL` and `INBUXA_WEBMAIL_CLIENT_SECRET` are set. //! //! inbuxa: the environment variables stand in for `x:FrontEnds` (C-4) until @@ -22,7 +22,7 @@ //! it instead. //! //! A missing client is created. An existing one gains any redirect URI it -//! lacks and, for ihasmail-inbuxa, the configured secret; nothing an operator +//! lacks and, for inbuxa-webmail, the configured secret; nothing an operator //! added is removed. use directory::core::secret::{hash_secret, verify_secret_hash}; diff --git a/docs/spec/SPEC.md b/docs/spec/SPEC.md index b417873..0dfdc71 100644 --- a/docs/spec/SPEC.md +++ b/docs/spec/SPEC.md @@ -618,7 +618,7 @@ the tenant administrators' own view is not yet recorded ## 8. Open decisions -- The INBUXA fork of ihasmail is **ihasmail-inbuxa** (named 2026-09-18). Open: its repository, and how it tracks +- The INBUXA fork of ihasmail is **inbuxa-webmail** (named ihasmail-inbuxa on 2026-09-18, renamed 2026-10-05). Open: how it tracks public ihasmail (§5). - Product name: whether the shipped product is called inbuxa-server or something else inside the INBUXA brand. diff --git a/docs/spec/contract.md b/docs/spec/contract.md index 6baa437..994d36a 100644 --- a/docs/spec/contract.md +++ b/docs/spec/contract.md @@ -7,11 +7,11 @@ Status: draft, 2026-09-18. Expands SPEC.md §5.2. | Party | What it is | How it reaches the server | |---|---|---| | **inbuxa-server** | The mail server | — | -| **ihasmail-inbuxa** | The INBUXA fork of ihasmail: a Node server and a web app. Public ihasmail stays Stalwart-facing and isn't party to this (SPEC.md §5) | Its **Node server** calls inbuxa-server, server to server. The browser only ever talks to ihasmail-inbuxa | +| **inbuxa-webmail** | The INBUXA fork of ihasmail: a Node server and a web app. Public ihasmail stays Stalwart-facing and isn't party to this (SPEC.md §5) | Its **Node server** calls inbuxa-server, server to server. The browser only ever talks to inbuxa-webmail | | **INBUXA Admin** (`inbuxa-admin`) | A static web app, a fork of Stalwart WebUI | The **browser** calls inbuxa-server directly, cross-origin | That split decides most of what follows. Cross-origin rules matter only for -INBUXA Admin. Token custody matters most for ihasmail-inbuxa, which holds +INBUXA Admin. Token custody matters most for inbuxa-webmail, which holds tokens on its server for people who aren't there. ## What upstream does today @@ -45,7 +45,7 @@ Observed in the source at `v0.16.22` and against a running inbuxa-server on - **Endpoint gating:** `x:Http.allowedEndpoints` is an expression that can refuse endpoints by path and client IP. JMAP administration shares `/jmap` with everything else, so it can't separate admin calls on its own. -- **ihasmail today** (public, and so the starting point for ihasmail-inbuxa) +- **ihasmail today** (public, and so the starting point for inbuxa-webmail) signs in with HTTP Basic auth and keeps the password sealed in its session store (`sealedCredentials: {username, password}`), sending it on every upstream call. It registers JMAP push subscriptions to its own URL, and reads @@ -76,7 +76,7 @@ Each has an ID, and tests name the IDs they check. (LP-19). Added 2026-09-21. - **C-2.** Each front end states the contract versions it supports and checks `contract` after signing in. Outside its range it stops, with a message - naming both versions. For ihasmail-inbuxa this replaces public ihasmail's + naming both versions. For inbuxa-webmail this replaces public ihasmail's "Stalwart 0.16 or later" check. - **C-3.** A breaking change to anything in this document bumps `contract`. Adding optional fields doesn't. @@ -117,7 +117,7 @@ Each has an ID, and tests name the IDs they check. - **`inbuxa-admin`**: a public client (no secret), authorization code with PKCE S256, redirect URI `{adminUrl}/oauth/callback`. - **`ihasmail-inbuxa`**: a confidential client with a secret held by the - ihasmail-inbuxa server, authorization code with PKCE S256, redirect URI + inbuxa-webmail server, authorization code with PKCE S256, redirect URI `{webmailUrl}/api/auth/callback`. INBUXA Admin's `` is set to `inbuxa-admin`. Served by the server itself it uses the web interface's client, @@ -159,7 +159,7 @@ Each has an ID, and tests name the IDs they check. - **C-8.** People sign in on **the server's own sign-in page** (`/login`, already INBUXA-branded), never on a front end's form. Two-factor happens there, on the page's existing one-time-code step. Front ends never see a - password. ihasmail-inbuxa's own sign-in form is retired in favor of a + password. inbuxa-webmail's own sign-in form is retired in favor of a redirect. - **C-9.** **Consent for anything that isn't first-party.** When a client other than the two first-party ones asks to sign someone in, the sign-in page names @@ -170,10 +170,10 @@ Each has an ID, and tests name the IDs they check. ### Tokens -- **C-10.** ihasmail-inbuxa holds tokens, never passwords. It keeps the access +- **C-10.** inbuxa-webmail holds tokens, never passwords. It keeps the access and refresh token for each session sealed in its session store, where it now keeps sealed credentials, and refreshes the access token before it expires. - The browser still holds only ihasmail-inbuxa's own session cookie. Public + The browser still holds only inbuxa-webmail's own session cookie. Public ihasmail's "the browser never holds a credential" property is kept. - **C-11.** INBUXA Admin holds its tokens in the browser, as upstream WebUI does, since it has no server of its own. So admin tokens are short-lived @@ -193,9 +193,9 @@ Each has an ID, and tests name the IDs they check. A revoked token stops working on its next use, and never later than one access-token lifetime. - **C-13.** Grants are listed per account (client, device description, created, - last used, IP), so ihasmail-inbuxa's "your sessions" screen shows server-side + last used, IP), so inbuxa-webmail's "your sessions" screen shows server-side truth. Lifetimes, all configurable: access tokens 1 hour and refresh 30 days - for ihasmail-inbuxa; access tokens 15 minutes and refresh 8 hours for + for inbuxa-webmail; access tokens 15 minutes and refresh 8 hours for `inbuxa-admin`. ### Cross-origin @@ -237,7 +237,7 @@ Each has an ID, and tests name the IDs they check. scope `inbuxa:admin`, which only that client is ever granted. An admin account signing in through a mail client can't administer the server with that token, even though the account could. -- **C-19.** ihasmail-inbuxa's own administration (accounts, domains, groups, +- **C-19.** inbuxa-webmail's own administration (accounts, domains, groups, lists, roles, tenants, the dashboard) uses the scope `inbuxa:account-admin`, granted only to `ihasmail-inbuxa`, and limited to those object types, plus `x:Metric` get and query for the dashboard's message cards (monitoring @@ -250,7 +250,7 @@ Each has an ID, and tests name the IDs they check. ### Push -- **C-22.** Unchanged from public ihasmail: ihasmail-inbuxa registers JMAP push +- **C-22.** Unchanged from public ihasmail: inbuxa-webmail registers JMAP push subscriptions to its own URL, with VAPID for browser notifications. The only difference is that it authenticates with its token rather than the password. @@ -279,7 +279,7 @@ Each has an ID, and tests name the IDs they check. `INBUXA_HTTP_BASIC_AUTH=all`; `dav`, the default, is this rule. Any other value logs a warning and keeps the default. The setting moves to the registry with `x:FrontEnds` (C-4). - ihasmail-inbuxa confirms a typed password, which it does before creating + inbuxa-webmail confirms a typed password, which it does before creating an app password, on `/api/auth` as its own client, to its registered redirect URI, with a PKCE challenge whose verifier it discards. A "two-factor code needed" answer counts as confirmed, since the server gives @@ -294,7 +294,7 @@ Each has an ID, and tests name the IDs they check. didn't register refused. Observed before the change, in INBUXA's production logs from 2026-09-20 to 2026-09-29: every HTTPS password sign-in was the operator's own, apart from - ihasmail-inbuxa's password sign-in on 2026-09-22, before it moved to OAuth. + inbuxa-webmail's password sign-in on 2026-09-22, before it moved to OAuth. The logs don't say whether a sign-in used a Basic header or the sign-in page. @@ -304,8 +304,8 @@ Each has an ID, and tests name the IDs they check. (SPEC.md §6.2). In bootstrap mode, CORS is permissive (C-16) and the recovery administrator applies. 2. It sets `x:FrontEnds` (webmail and admin URLs, the public URL), which - registers both first-party clients (C-6). For ihasmail-inbuxa it returns the - client secret once, for the installer to write into ihasmail-inbuxa's + registers both first-party clients (C-6). For inbuxa-webmail it returns the + client secret once, for the installer to write into inbuxa-webmail's environment. 3. After the restart out of bootstrap, CORS follows C-14, registration is required (C-5), and the recovery administrator is ignored (SPEC.md §6.2). @@ -376,7 +376,7 @@ client and reload settings. This is the state C-5 and C-6 make the default. 5. The phishing flow in the security note fails at step 1, and a registered third-party client with a non-first-party redirect shows the consent page (C-9). -6. ihasmail-inbuxa signs in without ever handling a password. Its session +6. inbuxa-webmail signs in without ever handling a password. Its session store holds tokens only (C-8, C-10). 7. Revoking one grant stops that session within one access-token lifetime, leaves others working, and "sign out other sessions" keeps the current one @@ -388,7 +388,7 @@ client and reload settings. This is the state C-5 and C-6 make the default. 10. In bootstrap mode, INBUXA Admin reaches the server from any origin (C-16). 11. An admin account's token from a third-party mail client can't read `x:NetworkListener`. The same account through `inbuxa-admin` can (C-18). -12. ihasmail-inbuxa's token can manage accounts and tenants but not listeners +12. inbuxa-webmail's token can manage accounts and tenants but not listeners or certificates (C-19). 13. With `adminNetworks` set, an `inbuxa:admin` request from outside is refused (C-20). @@ -402,4 +402,4 @@ client and reload settings. This is the state C-5 and C-6 make the default. 2. The consent page's wording and whether it remembers a decision per client. 3. API keys and app passwords with explicit scopes (C-21): what upstream's `x:ApiKey` already supports, to observe before specifying. -4. Whether ihasmail-inbuxa's secret should rotate, and how. +4. Whether inbuxa-webmail's secret should rotate, and how. diff --git a/docs/spec/cutover.md b/docs/spec/cutover.md index 7e986cf..9cfb534 100644 --- a/docs/spec/cutover.md +++ b/docs/spec/cutover.md @@ -584,7 +584,7 @@ Three consequences: name whose DNS points at the mail addresses. - Whether the front ends need anything at cutover, or follow separately (SPEC.md §5). The rehearsal does not start them. -- Whether ihasmail-inbuxa and INBUXA Admin behave under real use, rather +- Whether inbuxa-webmail and INBUXA Admin behave under real use, rather than at first sign-in. Both were verified as far as signing in and, for the webmail, mail flowing. - The checks only users can make: the second account, the mailbox comparison diff --git a/docs/spec/features/ai-spam-classification.md b/docs/spec/features/ai-spam-classification.md index 10f22fc..6c47973 100644 --- a/docs/spec/features/ai-spam-classification.md +++ b/docs/spec/features/ai-spam-classification.md @@ -353,7 +353,7 @@ adds at most 2. ## ihasmail changes -These go in the INBUXA fork of ihasmail, ihasmail-inbuxa, never in public +These go in the INBUXA fork of ihasmail, inbuxa-webmail, never in public ihasmail, which stays Stalwart-facing (SPEC.md §5). - **Reading:** when a message has an `X-Spam-LLM` header, the message details diff --git a/docs/spec/features/branding-and-templates.md b/docs/spec/features/branding-and-templates.md index 0d0a6aa..7b5e3c7 100644 --- a/docs/spec/features/branding-and-templates.md +++ b/docs/spec/features/branding-and-templates.md @@ -275,7 +275,7 @@ Each requirement has an ID, and tests name the IDs they check. ## ihasmail changes -These go in the INBUXA fork of ihasmail, ihasmail-inbuxa, never in public +These go in the INBUXA fork of ihasmail, inbuxa-webmail, never in public ihasmail, which stays Stalwart-facing (SPEC.md §5). - **Administration, Domains:** a logo field on each domain. Upload a PNG, JPEG @@ -389,7 +389,7 @@ files carry hooks marked `inbuxa:`. Acceptance tests 1 to 17 pass as `tests/src/system/branding.rs`. - **BT-1 to BT-26:** built. -- **ihasmail changes** belong to ihasmail-inbuxa and aren't part of this +- **ihasmail changes** belong to inbuxa-webmail and aren't part of this repository. - **Test 18 (compat)** is written as `branding_compat`, ignored, and unrun until a copy of INBUXA's data is provided. INBUXA holds no logos or diff --git a/docs/spec/features/dlp-and-mail-flow-rules.md b/docs/spec/features/dlp-and-mail-flow-rules.md index 6db0d68..8603239 100644 --- a/docs/spec/features/dlp-and-mail-flow-rules.md +++ b/docs/spec/features/dlp-and-mail-flow-rules.md @@ -14,7 +14,7 @@ Written for the record SPEC.md §3 rule 3 asks for. Sources, and nothing else: |---|---|---| | This repository at `0502eb4` (2026-09-28): `crates/smtp/src/inbound/data.rs`, `crates/smtp/src/queue/`, `crates/jmap/src/submission/set.rs`, `crates/common/src/scripts/`, `vendor/sieve-rs`, `resources/schema/schema.json.gz` | AGPL-3.0-only | Where a check can run, what the queue stores, what a sender sees on a refusal | | inbuxa-admin at `b82904c` | AGPL-3.0-only | Where the pages go | -| ihasmail-inbuxa (the webmail) at `290bc63` | AGPL-3.0-or-later | How a refused send reaches the person sending | +| inbuxa-webmail (the webmail) at `290bc63` | AGPL-3.0-or-later | How a refused send reaches the person sending | | `inbuxa-drafts/queue/dlp.md`, `rule-builder.md` | Own | What John asked for and settled | | The personal-data catalog spec and the audit-hold-lock spec | Own | Roles, the audit log, legal holds, the catalog check | | RFC 5321, RFC 3463 (enhanced status codes), RFC 8620/8621 (JMAP) | Public | Refusal codes and the submission error shape | @@ -385,7 +385,7 @@ first). The inspection limit caps the worst case. Every form previews the rule in words ("If a recipient is outside and the message contains 5 or more card numbers, hold it for review"). -## 4. Webmail (ihasmail-inbuxa) +## 4. Webmail (inbuxa-webmail) - A warning dialog: the notice, a reason field, **Send anyway** and **Edit message**. diff --git a/docs/spec/features/masked-email.md b/docs/spec/features/masked-email.md index 89a4d89..63f8c9f 100644 --- a/docs/spec/features/masked-email.md +++ b/docs/spec/features/masked-email.md @@ -291,7 +291,7 @@ upstream files carry hooks marked `inbuxa:`. Acceptance tests 1 to 11 pass as `createdBy`. The server-set name is **deferred** until sign-in goes through OAuth (contract C-8): with Basic auth there's no client name, so a mask created through the Fastmail API has none. -- **ihasmail changes** belong to ihasmail-inbuxa and aren't part of this +- **ihasmail changes** belong to inbuxa-webmail and aren't part of this repository. - **Test 12 (compat)** is written as `masked_email_compat`, ignored, and unrun until a copy of INBUXA's data with masks made on it is provided. Its doc diff --git a/docs/spec/features/monitoring.md b/docs/spec/features/monitoring.md index 26913ce..b99dbb9 100644 --- a/docs/spec/features/monitoring.md +++ b/docs/spec/features/monitoring.md @@ -411,7 +411,7 @@ unchanged. ## ihasmail changes -These go in ihasmail-inbuxa, not public ihasmail, which stays Stalwart-facing +These go in inbuxa-webmail, not public ihasmail, which stays Stalwart-facing (SPEC.md §5). - The dashboard already reads `x:Metric` for received, sent and memory. Keep diff --git a/docs/spec/features/multi-tenancy.md b/docs/spec/features/multi-tenancy.md index 32839af..ab153fd 100644 --- a/docs/spec/features/multi-tenancy.md +++ b/docs/spec/features/multi-tenancy.md @@ -336,9 +336,9 @@ called from `system_tests` with no gate. - **MT-1 to MT-18, MT-20 to MT-23:** built. - **MT-19, MT-19a:** built, except the submission-time warning, **deferred** (see MT-19a) until the contract defines a warnings shape. -- **ihasmail changes** (the section above) belong to ihasmail-inbuxa and +- **ihasmail changes** (the section above) belong to inbuxa-webmail and aren't part of this repository. Its branding and quota warnings wait for - ihasmail-inbuxa. + inbuxa-webmail. - **Test 15 (compat)** is written as `tenant_compat`, ignored, and unrun until a copy of INBUXA's data is provided. Its doc comment says how to run it. - **Known limits, not requirements of this spec:** diff --git a/docs/spec/features/per-domain-directories.md b/docs/spec/features/per-domain-directories.md index 2f83f76..4bfdff6 100644 --- a/docs/spec/features/per-domain-directories.md +++ b/docs/spec/features/per-domain-directories.md @@ -412,7 +412,7 @@ check it and the fork keeps it. ## ihasmail changes -These go in ihasmail-inbuxa, the INBUXA fork of ihasmail, never in public +These go in inbuxa-webmail, the INBUXA fork of ihasmail, never in public ihasmail, which stays Stalwart-facing (SPEC.md §5). - **Domain editor:** a directory picker offering "server default" and the diff --git a/docs/spec/features/scim.md b/docs/spec/features/scim.md index 1ace14f..14f7517 100644 --- a/docs/spec/features/scim.md +++ b/docs/spec/features/scim.md @@ -679,7 +679,7 @@ SCIM error documents (RFC 7644 §3.12): `schemas` ## ihasmail changes -These go in ihasmail-inbuxa, not public ihasmail, which stays +These go in inbuxa-webmail, not public ihasmail, which stays Stalwart-facing (SPEC.md §5). - **Domains:** an "Allow SCIM provisioning" switch on the domain form. Turning diff --git a/docs/spec/features/undelete.md b/docs/spec/features/undelete.md index c5e7125..3f55ad2 100644 --- a/docs/spec/features/undelete.md +++ b/docs/spec/features/undelete.md @@ -276,7 +276,7 @@ marked `inbuxa:`. Acceptance tests 1 to 15 pass as `tests/src/system/undelete.rs`, with `/changes` and the `/query` filters. - **UD-1 to UD-17a:** built. -- **ihasmail changes** belong to ihasmail-inbuxa and aren't part of this +- **ihasmail changes** belong to inbuxa-webmail and aren't part of this repository. - **Test 16 (compat)** is written as `undelete_compat`, ignored, and unrun until a copy of INBUXA's data with archived items made on it is provided. diff --git a/docs/spec/handoff/multi-tenancy.md b/docs/spec/handoff/multi-tenancy.md index 453e628..143e7a5 100644 --- a/docs/spec/handoff/multi-tenancy.md +++ b/docs/spec/handoff/multi-tenancy.md @@ -103,7 +103,7 @@ conflicts. 1. Every requirement MT-1 to MT-23 is implemented, or deliberately deferred with a line in the spec saying so. The branding and quota warnings for - ihasmail can wait for ihasmail-inbuxa. + ihasmail can wait for inbuxa-webmail. 2. Acceptance tests 1 to 14 pass as integration tests (`tests/src/system/tenant.rs`), with the `pending-rebuild` gate removed from the tenant call. diff --git a/tests/e2e/http_basic_auth.py b/tests/e2e/http_basic_auth.py index 6e509c4..5119f16 100644 --- a/tests/e2e/http_basic_auth.py +++ b/tests/e2e/http_basic_auth.py @@ -12,7 +12,7 @@ Boots the debug binary and checks that: - DAV still takes Basic, and its 401 still offers it; - a token from the sign-in endpoint (`/api/auth`, the password in the body) and the token endpoint works on JMAP: the path the front ends use, and the - one ihasmail-inbuxa's password check relies on; + one inbuxa-webmail's password check relies on; - INBUXA_HTTP_BASIC_AUTH=all puts Basic back everywhere, an unknown value keeps the default with a warning, and recovery mode accepts Basic. @@ -64,7 +64,7 @@ def start(env=None): "-p", f"127.0.0.1:{PORT}:8080", # A debug build's workers need more than the default stack. "-e", "RUST_MIN_STACK=16777216", - # Registers inbuxa-admin and ihasmail-inbuxa (C-6). + # Registers inbuxa-admin and inbuxa-webmail (C-6). "-e", f"INBUXA_ADMIN_URL={ADMIN_URL}", "-e", f"INBUXA_WEBMAIL_URL={WEBMAIL_URL}"] env_file = f"{DIR}/secrets/basic-env" with open(env_file, "w") as f: @@ -243,7 +243,7 @@ def main(): status, _, _ = request("/api/account", f"Bearer {access}") check(status == 200, f"a token works on /api/account ({status})") - # ihasmail-inbuxa's password check before an app password: its own client, + # inbuxa-webmail's password check before an app password: its own client, # its registered redirect URI, a verifier it throws away. for password, want in ((user_pw, "authenticated"), ("not-the-password", "failure")): got = sign_in(user, password, "ihasmail-inbuxa", WEBMAIL_REDIRECT, secrets.token_urlsafe(48))