Skip to content

fix: use an OU's handle as its mail domain - #308

Merged
maneesha-xyz merged 6 commits into
mainfrom
ou-handle-as-mail-domain
Nov 13, 2026
Merged

maneesha-xyz merged 6 commits into
mainfrom
ou-handle-as-mail-domain

Conversation

@maneesha-xyz

@maneesha-xyz maneesha-xyz commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

📌 Description

An OU's handle is now its mail domain. Raven no longer works out the domain from where the OU sits in the Thunder tree, so a domain can have any number of labels, a child OU's domain doesn't have to be a subdomain of its parent's, and a grouping OU (such as foundations) can sit above OUs that receive mail.


🔍 Changes Made

  • internal/maildomain: new FromHandle, which returns an OU handle as a lowercased domain, or reports that the handle isn't a domain (foundations, default).
  • internal/socketmap/thunder: buildOUPath and the never-expiring ouCache are removed. ValidateDomain and GetOrgUnitIDForDomain now look the domain up in a domain → OU ID index built by walking GET /organization-units and GET /organization-units/{id}/ous (paged at 100, following next links).
    • The index is rebuilt every CACHE_TTL_SECONDS. If a rebuild fails, the previous index stays in use.
    • A domain that is the handle of more than one OU is left out of the index.
  • internal/server/auth and internal/delivery/groupresolver: the parent-walk in resolveOrganizationUnitDomain is replaced with a single GET /organization-units/{id} and FromHandle. The callers (IMAP LOGIN domain check, OAUTHBEARER, group expansion) are unchanged.
  • docs/THUNDER_CONSUMER_CONTRACT_GUIDE.md: endpoint [TASK] Write Test for "LOGIN" Command #3 is now the list/children endpoints, and [BUG] Copy of the send mail was not saved on the sqlite database. #9 no longer describes a parent walk.

✅ Checklist (Email System)

  • Core IMAP commands tested (LOGIN, CAPABILITY, LIST, SELECT, FETCH, LOGOUT).
  • Authentication is tested.
  • Docker build & run validated.
  • Configuration loading verified for default and custom paths.
  • Persistent storage with Docker volume verified.
  • Error handling and logging verified
  • Documentation updated (README, config samples).

🧪 Testing Instructions

To test the server, use the instructions in the README in the test directory.

go test ./... passes. The new and updated tests:

  • internal/maildomain: handle parsing, including deep domains, co.uk, mixed case, trailing dot, non-domain handles and path-traversal characters.
  • internal/socketmap/thunder/index_test.go: a child OU silver.x.y under foundations resolves with no x.y OU present; duplicate handles resolve to nothing; pagination; index reuse within the TTL; the previous index is served when a rebuild fails; an error when no index was ever built.
  • internal/server/auth and internal/delivery/groupresolver: fixtures use full-domain handles and fail the test if a parent OU is fetched.

Against a real Thunder, with raven's own IDP_CLIENT_ID/IDP_CLIENT_SECRET, run the socketmap service and check that virtual-domains <child OU domain> returns OK 1 and virtual-domains <grouping OU handle> returns NOTFOUND. This also confirms raven's token may call the list endpoints; so far they have only been checked with an admin token.


⚠️ Notes for Reviewers

  • Breaking for existing trees. Before upgrading, rename every OU that receives mail so that its handle is its full mail domain. For example, a child silver under x.y becomes silver.x.y. Otherwise mail for that OU stops resolving and its users can't log in.
  • A new or renamed OU takes up to CACHE_TTL_SECONDS (default 300) to take effect. Socketmap answers from the domain index, and a stale index is refreshed in the background, so Postfix rejects mail for a newly created domain until the next refresh lands. A renamed OU's old domain is accepted for the same window.
  • Handles of OUs that receive mail must be unique across the whole tree, not just among siblings. Socketmap leaves a duplicated domain out of the index and logs both OU IDs, but IMAP LOGIN and group expansion read a single OU's handle and can't see the duplicate. Two users with the same username in those OUs would open the same mailbox. Thunder-side uniqueness for mailDomains would enforce this.
  • Each OU has one domain for now. Multiple domains wait for a mailDomains attribute on OUs in Thunder.
  • Out of scope: SASL still ignores the domain when authenticating senders ([BUG] IMAP authentication allows login with username-only and mismatched domain #267), and a Thunder outage still makes domains look nonexistent.

@maneesha-xyz maneesha-xyz self-assigned this Sep 28, 2026
@maneesha-xyz maneesha-xyz added the bug Something isn't working label Sep 28, 2026
@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5 Tier: plus

Greptile CLI A Greptile CLI review exists for this PR, but a new review ran because the CLI review did not re-check every open finding of this PR's earlier Greptile reviews.

[High impact] Rebuilds mail domain resolution to use OU handles instead of hierarchy.

The PR does not yet appear safe to merge because cached positive results can outlive the index-age cutoff.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Thunder[Thunder OU listings] --> Index[Refreshed domain-to-OU index]
  Index --> SocketMap[SocketMap domain and address checks]
  OU[Individual OU handle] --> Auth[IMAP authentication]
  OU --> Groups[Group member resolution]
  Auth --> DB[Domain-normalized mailbox database]
  Groups --> DB
Loading

Reviews (8) · Last reviewed commit: "fix(db): open one mailbox for every spel..." · Reviewed by Greptile

Comment thread internal/socketmap/thunder/index.go Resolved
Comment thread cmd/socketmap/main.go Resolved
Comment thread internal/socketmap/thunder/index.go Resolved
Comment thread internal/socketmap/thunder/index.go Resolved
Comment thread internal/maildomain/maildomain.go Resolved
Comment thread cmd/socketmap/main.go Resolved
Comment thread cmd/socketmap/main.go Resolved
@maneesha-xyz
maneesha-xyz marked this pull request as draft September 29, 2026 05:07
@CLAassistant

CLAassistant commented Nov 9, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Comment thread cmd/socketmap/main.go Resolved
Thunder can only find an OU by its full handle path, and a domain no
longer says where its OU sits, so socketmap reads the whole OU tree
into a domain index.

The index is built alongside startup and rebuilt every
CACHE_TTL_SECONDS, with the wait starting when a walk ends. Lookups
only read it; they wait for a walk only while no index exists, and
share a failed first build's error for a few seconds. A domain claimed
by two OUs is left out. Each page of a walk uses the current token.
@maneesha-xyz
maneesha-xyz force-pushed the ou-handle-as-mail-domain branch from 299dd98 to 0b5c17e Compare November 12, 2026 06:45
@maneesha-xyz
maneesha-xyz marked this pull request as ready for review November 12, 2026 08:26
…nswered

- TEMP instead of NOTFOUND for a domain several OUs claim
- TEMP when a Thunder lookup fails, instead of bouncing the recipient
- Stop serving the domain index after 12 failed refresh intervals
- Lowercase the domain in normalizeEmail
Comment thread internal/server/auth/auth.go Resolved
Comment thread internal/socketmap/thunder/index.go Resolved
- Lowercase the domain in GetUserDB, which login and delivery both use
- Docs: cached answers can outlast the index age cutoff by one cache interval
@maneesha-xyz
maneesha-xyz merged commit b82c330 into main Nov 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Mail domain is derived from the OU tree, so deep domains and OUs under a grouping OU don't resolve

3 participants