feat: add bounded IMAPS authentication (#766) #874

Open
kayg wants to merge 3 commits from feat/imap-notes-auth into feat/imap-notes-server-foundation
Owner

Plan

Size: 3 files, approximately 1,200 changed lines. This is a separate auth layer stacked on #873; #873 remains within the #830 listener foundation estimate.

Big-O: Protocol framing uses O(L) time and at most 8 KiB line storage, where L is the command length. Credential literals are limited to 4 KiB. Connection permit checks and accounting are O(1). Login backoff uses 4,096 fixed keyed buckets, so memory is O(1) and username values are never stored.

Failure Effect Detection Recovery Gate
TLS handshake does not complete No IMAP greeting or command parsing. 10-second handshake deadline. Reconnect after the TLS endpoint is available. Trusted handshake and stalled handshake checks.
Pre-auth mailbox command or malformed framing Rejected before mailbox lookup, UID access or literal continuation. State and parser result; no credential data in logs. Reconnect and authenticate with supported framing. State matrix, malformed lines, pipelined commands and literal checks.
Wrong, revoked or wrong-scope password Same generic authentication failure; no mailbox data. Auth failure accounting by existing app-password verifier. Mint or revoke a Notes-scoped app password. Wrong scope, revocation and generic-error checks.
Authentication storage unavailable NO [UNAVAILABLE]; no identity or mailbox access. Auth database error. Restore auth storage, then reconnect. Injected auth-store failure.
Credential exchange exceeds limits or deadline Exchange closes or receives a bounded failure; credentials are not logged. 4 KiB literal, 6 KiB encoded PLAIN, 4.5 KiB decoded and 15-second deadline. Retry with a valid app password. Credential boundary, cancellation and stalled-input checks.
Connection cap reached New connection receives BYE or closes; existing sessions continue. Process, source, Installation and user counters. Reconnect after a permit is released. 128 process, 32 source IP, 16 Installation and 48 user boundaries.

Design

  • Builds on #873, which builds on #824 head f1c1aa961e169ef99e132063a97ccbd9a89a5def.
  • Adds bounded IMAP line and literal framing, LOGIN and AUTHENTICATE PLAIN (SASL-IR and continuation), three attempts per connection, cancellation, and keyed source/account backoff.
  • Verifies the existing mail.notes.read app-password scope through the same app-password verifier used by DAV. Username remains a hint; credential failures are generic.
  • Holds process/source permits from TCP accept through TLS and auth; adds Installation/user permits after successful verification and releases all permits on close.
  • The listener remains disabled unless explicitly enabled. This layer does not expose a Notes projection or mailbox data; the next layer adds the read-only Notes mailbox.

Gates

Run on dev-02 through ~/bin/slot in the Nix devshell:

  • cargo fmt --all -- --check
  • cargo test -p calternal-auth -j4
  • cargo test -p calternal-imap -j4
  • cargo clippy -p calternal-imap -j4 -- -D warnings
  • Protocol subset: both login forms, cancellation, limits, pre-auth rejection, literal refusal and permit release.

Gate output will be posted as PR comments, not committed to the repository.

## Plan **Size:** 3 files, approximately 1,200 changed lines. This is a separate auth layer stacked on #873; #873 remains within the #830 listener foundation estimate. **Big-O:** Protocol framing uses O(L) time and at most 8 KiB line storage, where L is the command length. Credential literals are limited to 4 KiB. Connection permit checks and accounting are O(1). Login backoff uses 4,096 fixed keyed buckets, so memory is O(1) and username values are never stored. | Failure | Effect | Detection | Recovery | Gate | | --- | --- | --- | --- | --- | | TLS handshake does not complete | No IMAP greeting or command parsing. | 10-second handshake deadline. | Reconnect after the TLS endpoint is available. | Trusted handshake and stalled handshake checks. | | Pre-auth mailbox command or malformed framing | Rejected before mailbox lookup, UID access or literal continuation. | State and parser result; no credential data in logs. | Reconnect and authenticate with supported framing. | State matrix, malformed lines, pipelined commands and literal checks. | | Wrong, revoked or wrong-scope password | Same generic authentication failure; no mailbox data. | Auth failure accounting by existing app-password verifier. | Mint or revoke a Notes-scoped app password. | Wrong scope, revocation and generic-error checks. | | Authentication storage unavailable | `NO [UNAVAILABLE]`; no identity or mailbox access. | Auth database error. | Restore auth storage, then reconnect. | Injected auth-store failure. | | Credential exchange exceeds limits or deadline | Exchange closes or receives a bounded failure; credentials are not logged. | 4 KiB literal, 6 KiB encoded PLAIN, 4.5 KiB decoded and 15-second deadline. | Retry with a valid app password. | Credential boundary, cancellation and stalled-input checks. | | Connection cap reached | New connection receives BYE or closes; existing sessions continue. | Process, source, Installation and user counters. | Reconnect after a permit is released. | 128 process, 32 source IP, 16 Installation and 48 user boundaries. | ### Design - Builds on #873, which builds on #824 head `f1c1aa961e169ef99e132063a97ccbd9a89a5def`. - Adds bounded IMAP line and literal framing, LOGIN and AUTHENTICATE PLAIN (SASL-IR and continuation), three attempts per connection, cancellation, and keyed source/account backoff. - Verifies the existing `mail.notes.read` app-password scope through the same app-password verifier used by DAV. Username remains a hint; credential failures are generic. - Holds process/source permits from TCP accept through TLS and auth; adds Installation/user permits after successful verification and releases all permits on close. - The listener remains disabled unless explicitly enabled. This layer does not expose a Notes projection or mailbox data; the next layer adds the read-only `Notes` mailbox. ### Gates Run on dev-02 through `~/bin/slot` in the Nix devshell: - `cargo fmt --all -- --check` - `cargo test -p calternal-auth -j4` - `cargo test -p calternal-imap -j4` - `cargo clippy -p calternal-imap -j4 -- -D warnings` - Protocol subset: both login forms, cancellation, limits, pre-auth rejection, literal refusal and permit release. Gate output will be posted as PR comments, not committed to the repository.
feat: add bounded IMAPS app-password authentication
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
aebd80735b
Author
Owner

Gate results (dev-02)

  • At b045dbba, cargo test -p calternal-auth -j4 passed: 186 passed, 7 ignored, 0 failed. The schema migration regression now expects version 11.
  • At 67d4d066, cargo fmt --all -- --check passed.
  • At 67d4d066, cargo test -p calternal-imap -j4 passed: 10 passed, 0 failed.
  • At 67d4d066, cargo clippy -p calternal-imap -j4 -- -D warnings passed.

All commands ran in the dev-02 Nix devshell through ~/bin/slot.

## Gate results (dev-02) - At `b045dbba`, `cargo test -p calternal-auth -j4` passed: 186 passed, 7 ignored, 0 failed. The schema migration regression now expects version 11. - At `67d4d066`, `cargo fmt --all -- --check` passed. - At `67d4d066`, `cargo test -p calternal-imap -j4` passed: 10 passed, 0 failed. - At `67d4d066`, `cargo clippy -p calternal-imap -j4 -- -D warnings` passed. All commands ran in the dev-02 Nix devshell through `~/bin/slot`.
Author
Owner

housekeeping #1009: rebased+failing, left open for review.

restacked IMAP auth on #873, preserving current schema expectations and rustls lockfile entries. inherits foundation ownership, certificate-receiver and canonical TLS fixture repairs.

head: a8d1bda0f5bbf9ed2913d8178ee2f30a9ead9752; based on dev cd7f84432febfc63b788fbb9b9f9950452982d74, pushed with the original exact force-with-lease.

frontend frozen install, check, lint, build and unit tests passed. bundle gate passed. first-paint brotli vs dev: JS 230777 B (+0 B), CSS 18386 B (+0 B).

Rust gate results (Mac VM):

exit command
0 cargo fmt -p calternal-auth -p calternal -p calternal-imap -- --check
0 cargo check -j4 --locked -p calternal --bins
101 cargo check -j4 --locked -p calternal-auth -p calternal -p calternal-imap --all-targets
101 cargo clippy -j4 --locked -p calternal-auth -p calternal -p calternal-imap --all-targets -- -D warnings
0 cargo test -j4 --locked -p calternal-auth --lib
0 cargo test -j4 --locked -p calternal-imap --lib
101 cargo test -j4 --locked -p calternal-auth -p calternal -p calternal-imap
0 cargo check -j4 --locked -p calternal-auth -p calternal-imap --all-targets
0 cargo clippy -j4 --locked -p calternal-auth -p calternal-imap --all-targets -- -D warnings
0 cargo test -j4 --locked -p calternal-auth -p calternal-imap --lib

server-wide all-target checks/tests are blocked on macOS by storage_startup.rs:147 borrowing Child immutably; clippy also reports unused state in storage.rs:265. both reproduce on dev. scoped auth/IMAP component checks, clippy and unit tests passed. native Apple Notes client qualification and perf qualification remain unrun.

all executed gates used the VM Nix environment, shared Mac lock and slot. exact commands, source heads, logs and dev baseline. frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head.

original-head cherry/patch inventory, pr-874.md; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.

housekeeping #1009: rebased+failing, left open for review. restacked IMAP auth on #873, preserving current schema expectations and rustls lockfile entries. inherits foundation ownership, certificate-receiver and canonical TLS fixture repairs. head: `a8d1bda0f5bbf9ed2913d8178ee2f30a9ead9752`; based on dev `cd7f84432febfc63b788fbb9b9f9950452982d74`, pushed with the original exact force-with-lease. frontend frozen install, check, lint, build and unit tests passed. bundle gate passed. first-paint brotli vs dev: JS 230777 B (+0 B), CSS 18386 B (+0 B). Rust gate results (Mac VM): | exit | command | |---|---| | 0 | `cargo fmt -p calternal-auth -p calternal -p calternal-imap -- --check` | | 0 | `cargo check -j4 --locked -p calternal --bins` | | 101 | `cargo check -j4 --locked -p calternal-auth -p calternal -p calternal-imap --all-targets` | | 101 | `cargo clippy -j4 --locked -p calternal-auth -p calternal -p calternal-imap --all-targets -- -D warnings` | | 0 | `cargo test -j4 --locked -p calternal-auth --lib` | | 0 | `cargo test -j4 --locked -p calternal-imap --lib` | | 101 | `cargo test -j4 --locked -p calternal-auth -p calternal -p calternal-imap` | | 0 | `cargo check -j4 --locked -p calternal-auth -p calternal-imap --all-targets` | | 0 | `cargo clippy -j4 --locked -p calternal-auth -p calternal-imap --all-targets -- -D warnings` | | 0 | `cargo test -j4 --locked -p calternal-auth -p calternal-imap --lib` | server-wide all-target checks/tests are blocked on macOS by `storage_startup.rs:147` borrowing Child immutably; clippy also reports unused `state` in `storage.rs:265`. both reproduce on dev. scoped auth/IMAP component checks, clippy and unit tests passed. native Apple Notes client qualification and perf qualification remain unrun. all executed gates used the VM Nix environment, shared Mac lock and slot. [exact commands, source heads, logs and dev baseline](https://git.kayg.org/attachments/bdb8d284-b80d-4185-b0be-6fa74f48a191). frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head. [original-head cherry/patch inventory](https://git.kayg.org/attachments/e05a38dc-c028-4d77-8655-efa9d5c8ea50), `pr-874.md`; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
This pull request is broken due to missing fork information.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/imap-notes-auth:feat/imap-notes-auth
git switch feat/imap-notes-auth

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch feat/imap-notes-server-foundation
git merge --no-ff feat/imap-notes-auth
git switch feat/imap-notes-auth
git rebase feat/imap-notes-server-foundation
git switch feat/imap-notes-server-foundation
git merge --ff-only feat/imap-notes-auth
git switch feat/imap-notes-auth
git rebase feat/imap-notes-server-foundation
git switch feat/imap-notes-server-foundation
git merge --no-ff feat/imap-notes-auth
git switch feat/imap-notes-server-foundation
git merge --squash feat/imap-notes-auth
git switch feat/imap-notes-server-foundation
git merge --ff-only feat/imap-notes-auth
git switch feat/imap-notes-server-foundation
git merge feat/imap-notes-auth
git push origin feat/imap-notes-server-foundation
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!874
No description provided.