Documentation
¶
Overview ¶
Package api implements the HTTP handlers for dmcnd's built-in web mail client: authentication, identity lookup, message send, mailbox sync, and — depending on how the domain is provisioned — either open registration (dev) or the mailbox petition flow (a live domain, whose root key is not on this machine).
The backend holds no key material and no user directory. Every identity is resolved from its domain's fleet and verified by challenge-response against the record's own Ed25519 key; all message crypto happens in the browser.
Index ¶
- Constants
- Variables
- func AdminAssignBound(code, address string) string
- func AdminGetBound(code string) string
- func AdminSignableBytes(sigContext string, nonce []byte, bound string) []byte
- type AuthHandler
- func (h *AuthHandler) HandleImport(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) HandleImportChallenge(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) HandleLogin(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) HandleLoginVerify(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) HandleLogout(w http.ResponseWriter, r *http.Request)
- type BridgeResolver
- type IdentityHandler
- type MailboxHandler
- type MessageHandler
- type PetitionHandler
- func (h *PetitionHandler) HandleAdminAssign(w http.ResponseWriter, r *http.Request)
- func (h *PetitionHandler) HandleAdminChallenge(w http.ResponseWriter, r *http.Request)
- func (h *PetitionHandler) HandleAdminGet(w http.ResponseWriter, r *http.Request)
- func (h *PetitionHandler) HandleComplete(w http.ResponseWriter, r *http.Request)
- func (h *PetitionHandler) HandleCreate(w http.ResponseWriter, r *http.Request)
- func (h *PetitionHandler) HandleStatus(w http.ResponseWriter, r *http.Request)
- type ProvisionFunc
- type PublishFunc
- type RegisterHandler
- type RelayProxy
- type RelayRouter
Constants ¶
const ( AdminGetContext = adminGetContext AdminAssignContext = adminAssignContext )
Variables ¶
var ( // ErrMailboxFull is the send-path counterpart: a STORE the recipient's mailbox // rejected for being over its total-storage cap. The relay adapter maps the relay's // ErrMailboxFull onto it so the send handler returns 507 rather than a generic 502. ErrMailboxFull = errors.New("recipient mailbox full") // ErrAccessSuspended / ErrAccessClosed: the account's node-enforced access entitlement // blocks reads. The relay adapter maps the relay's sentinels onto these so the mailbox // challenge returns 403 with a machine code the client shows as an account state. ErrAccessSuspended = errors.New("account access suspended") ErrAccessClosed = errors.New("account access closed") )
var ( // ErrRegisterDomainNotServed: the requested address is not on the domain this daemon serves. ErrRegisterDomainNotServed = errors.New("address domain is not served by this node") // ErrRegisterExists: an identity is already registered for this address. ErrRegisterExists = errors.New("address is already registered") // ErrRegisterInvalidAddress: the address local-part is empty or malformed. ErrRegisterInvalidAddress = errors.New("invalid address") )
Functions ¶
func AdminAssignBound ¶ added in v0.7.0
func AdminGetBound ¶ added in v0.7.0
Signature contexts + bound-value builders, exported for the CLI.
Types ¶
type AuthHandler ¶
type AuthHandler struct {
// contains filtered or unexported fields
}
AuthHandler handles login, logout, and identity import. It keeps NO user directory: identities are verified against the fleet-resolved registry record — the system of record — so any registered address can log into any client instance (challenge-response with the record's Ed25519 key; the relay's FETCH auth remains the real gate on mailbox access).
func NewAuthHandler ¶
func NewAuthHandler( sessions *webcore.SessionStore, registryLookup func(ctx context.Context, address string) (*identity.IdentityRecord, error), log logr.Logger, ) *AuthHandler
NewAuthHandler creates a new AuthHandler. registryLookup resolves an address's IdentityRecord via its domain's fleet; login and import verify possession against it.
func (*AuthHandler) HandleImport ¶
func (h *AuthHandler) HandleImport(w http.ResponseWriter, r *http.Request)
HandleImport completes an import: it verifies the signed challenge against the directory's authoritative Ed25519 key (proof the caller holds the identity's private key) and mints a session. Because login verifies against the resolved record, there is no local record to create — import is login for an identity whose keys arrived on this device out-of-band. No keys are generated, stored, or published.
func (*AuthHandler) HandleImportChallenge ¶
func (h *AuthHandler) HandleImportChallenge(w http.ResponseWriter, r *http.Request)
HandleImportChallenge begins importing an existing (e.g. CLI-created) identity into this client. It confirms the address is registered in the directory and returns the authoritative public keys plus a challenge nonce; the browser proves possession of the private key by signing the nonce. Nothing is published.
func (*AuthHandler) HandleLogin ¶
func (h *AuthHandler) HandleLogin(w http.ResponseWriter, r *http.Request)
HandleLogin handles the first step of login: it confirms the address is registered on its domain's fleet and returns its public key plus a challenge nonce.
func (*AuthHandler) HandleLoginVerify ¶
func (h *AuthHandler) HandleLoginVerify(w http.ResponseWriter, r *http.Request)
HandleLoginVerify handles the second step of login: it verifies the signed challenge against the resolved record's Ed25519 key and mints a session.
func (*AuthHandler) HandleLogout ¶
func (h *AuthHandler) HandleLogout(w http.ResponseWriter, r *http.Request)
HandleLogout handles session logout.
type BridgeResolver ¶ added in v0.7.0
BridgeResolver discovers the outbound bridge for a sender domain: where to STORE mail bound for the legacy email world, and the key to seal it under. See node.ResolveBridge.
type IdentityHandler ¶
type IdentityHandler struct {
// contains filtered or unexported fields
}
IdentityHandler handles identity lookup requests.
func NewIdentityHandler ¶
func NewIdentityHandler( lookup func(ctx context.Context, address string) (*identity.IdentityRecord, error), verifyManaged func(ctx context.Context, rec *identity.IdentityRecord) (identity.VerificationTier, error), requiresOnion func(ctx context.Context, rec *identity.IdentityRecord) bool, relayHints func(ctx context.Context, address string) ([]string, error), log logr.Logger, ) *IdentityHandler
NewIdentityHandler creates a new IdentityHandler. verifyManaged (optional, may be nil) cryptographically verifies a countersigned record's tier against the domain authority + DNS + removal records, so lookups can report a trustworthy verified_tier (used to anchor bridge attestations, gap #7/#9). requiresOnion (optional, may be nil) reports the effective onion-delivery policy (mailbox flag OR domain DAR), so the compose UI can reflect/lock the onion toggle. adminCustody (optional, may be nil) reports the domain's admin-key-custody policy bit — display-only (the managed-account badge); the enforcing gate lives in the register path, so this one fails open to false.
func (*IdentityHandler) HandleLookup ¶
func (h *IdentityHandler) HandleLookup(w http.ResponseWriter, r *http.Request)
HandleLookup handles an identity lookup by address query parameter.
func (*IdentityHandler) HandleRelayHints ¶
func (h *IdentityHandler) HandleRelayHints(w http.ResponseWriter, r *http.Request)
HandleRelayHints returns the load-aware mailbox relay hints for an address — the ranked mailbox relays of its domain. This is read-only placement (no reservation; the authoritative reservation happens at registration). It returns 503 when the domain has no reachable mailbox relay, so the client refuses to create/pair a mailbox it could never durably receive at, rather than embedding a placeholder hint.
func (*IdentityHandler) SetBridgeResolver ¶ added in v0.7.0
func (h *IdentityHandler) SetBridgeResolver(f BridgeResolver)
SetBridgeResolver installs the outbound-bridge resolver. Wired separately from the constructor because a self-hosted daemon only knows whether it HAS a bridge after its domain and credential are in place, which is after the handlers are built.
type MailboxHandler ¶
type MailboxHandler struct {
// contains filtered or unexported fields
}
MailboxHandler backs the durable mailbox over plain REST: a two-phase challenge/complete exchange. The relay requires the client to sign a per-op nonce, so each op is a challenge (returns a nonce) followed by a complete (the client signs; the server finishes the relay op).
func NewMailboxHandler ¶
func NewMailboxHandler(relay RelayProxy, log logr.Logger) *MailboxHandler
NewMailboxHandler builds a MailboxHandler and starts its pending-challenge sweeper.
func (*MailboxHandler) HandleChallenge ¶
func (h *MailboxHandler) HandleChallenge(w http.ResponseWriter, r *http.Request)
HandleChallenge issues a challenge nonce for a mailbox op on the caller's mailbox and returns a correlation ID to complete the op with.
func (*MailboxHandler) HandleComplete ¶
func (h *MailboxHandler) HandleComplete(w http.ResponseWriter, r *http.Request)
HandleComplete finishes a mailbox op with the client's signature over the nonce and returns the relay result.
type MessageHandler ¶
type MessageHandler struct {
// contains filtered or unexported fields
}
MessageHandler handles outbound message send (STORE to the recipient's relay).
func NewMessageHandler ¶
func NewMessageHandler( storePreSigned func(ctx context.Context, senderAddr string, signature []byte, env *message.EncryptedEnvelope) ([32]byte, error), registryLookup func(ctx context.Context, address string) (*identity.IdentityRecord, error), relayRouter RelayRouter, replicates func(ctx context.Context, address string) bool, verifyRouting func(ctx context.Context, rec *identity.IdentityRecord) error, log logr.Logger, ) *MessageHandler
NewMessageHandler creates a new MessageHandler.
func (*MessageHandler) HandleSend ¶
func (h *MessageHandler) HandleSend(w http.ResponseWriter, r *http.Request)
HandleSend handles sending a message.
func (*MessageHandler) SetBridgeResolver ¶ added in v0.7.0
func (h *MessageHandler) SetBridgeResolver(f BridgeResolver)
SetBridgeResolver installs the outbound-bridge resolver — see IdentityHandler.SetBridgeResolver for why this is wired after construction.
type PetitionHandler ¶ added in v0.7.0
type PetitionHandler struct {
// contains filtered or unexported fields
}
PetitionHandler backs the petition endpoints.
func NewPetitionHandler ¶ added in v0.7.0
func NewPetitionHandler(store *petition.Store, domain string, rootPub func() ed25519.PublicKey, relayHints func() []string, publish PublishFunc, log logr.Logger) *PetitionHandler
NewPetitionHandler builds the handler. rootPub returns the domain root's public key as carried in the DAR the node serves; relayHints returns this node's own mailbox hints, which the admin needs in order to sign a routing credential without being able to reach the node's config.
func (*PetitionHandler) HandleAdminAssign ¶ added in v0.7.0
func (h *PetitionHandler) HandleAdminAssign(w http.ResponseWriter, r *http.Request)
HandleAdminAssign records the admin's decision: the address, and the two credentials the offline root signed for it.
The credentials are re-verified here against the same root key that authorised the call, so a bug or a mix-up on the admin's side cannot park credentials this node would later refuse to serve — the failure surfaces now, to the person who can fix it, rather than at the petitioner's browser an hour later.
func (*PetitionHandler) HandleAdminChallenge ¶ added in v0.7.0
func (h *PetitionHandler) HandleAdminChallenge(w http.ResponseWriter, r *http.Request)
HandleAdminChallenge issues the nonce the admin's offline root signs. Unauthenticated by necessity — it is the first half of proving who you are — and harmless: a nonce is worth nothing without the root key, and it is single-use.
func (*PetitionHandler) HandleAdminGet ¶ added in v0.7.0
func (h *PetitionHandler) HandleAdminGet(w http.ResponseWriter, r *http.Request)
HandleAdminGet returns one petition, by code, to a caller who can sign with the domain root.
By code only: there is no list endpoint, and there never should be. The admin is meant to act on a code somebody gave them, not to browse a queue — which is what keeps unwanted petitions from being work.
func (*PetitionHandler) HandleComplete ¶ added in v0.7.0
func (h *PetitionHandler) HandleComplete(w http.ResponseWriter, r *http.Request)
HandleComplete takes the petitioner's self-signed record for their assigned address, attaches the operator credentials the admin left behind, and publishes it.
Everything here is a match against what was already agreed: the record must be for the assigned address and carry the exact keys the petition proved possession of. That is what stops a code — which travelled by voice and may have been overheard — from being redeemed by anyone else.
func (*PetitionHandler) HandleCreate ¶ added in v0.7.0
func (h *PetitionHandler) HandleCreate(w http.ResponseWriter, r *http.Request)
HandleCreate files a petition and returns the code the petitioner reads to their admin.
It deliberately returns nothing else. No address is chosen, nothing is published, and the response reveals nothing about the domain — so a flood of these costs an attacker effort and gains them nothing.
func (*PetitionHandler) HandleStatus ¶ added in v0.7.0
func (h *PetitionHandler) HandleStatus(w http.ResponseWriter, r *http.Request)
HandleStatus lets the petitioner's browser poll for the address the admin assigned, so nobody has to read an email address down a phone line — the code was the only thing that had to travel by voice.
A code is a bearer token for exactly this: learning an address that is about to be public anyway. It reveals nothing else, and the record it names cannot be completed by anyone who does not hold the petitioner's private key.
type ProvisionFunc ¶
type ProvisionFunc func(ctx context.Context, rec *identity.IdentityRecord) (status string, err error)
ProvisionFunc is the operator-side half of registration: given a verified, self-signed record, attach the operator routing attestation and publish it to the fleet, returning the account status ("active"). It is where the daemon applies its domain + duplicate checks.
type PublishFunc ¶ added in v0.7.0
type PublishFunc func(ctx context.Context, rec *identity.IdentityRecord) error
PublishFunc publishes a completed identity record to the local store and the fleet.
type RegisterHandler ¶
type RegisterHandler struct {
// contains filtered or unexported fields
}
RegisterHandler backs POST /api/v1/register.
func NewRegisterHandler ¶
func NewRegisterHandler(provision ProvisionFunc, log logr.Logger) *RegisterHandler
NewRegisterHandler builds a RegisterHandler around the operator provision hook.
func (*RegisterHandler) HandleRegister ¶
func (h *RegisterHandler) HandleRegister(w http.ResponseWriter, r *http.Request)
HandleRegister verifies a self-signed IdentityRecord and provisions the account. It returns only the account status — it mints NO session; the browser logs in with its fresh keys next.
type RelayProxy ¶
type RelayProxy interface {
Challenge(ctx context.Context, address string) (nonce []byte, err error)
List(ctx context.Context, address string, nonce, signature []byte, limit int, cursor []byte) (entries []*dmcnpb.MailboxEntry, next []byte, err error)
Body(ctx context.Context, address string, nonce, signature []byte, hash [32]byte) (*dmcnpb.MailboxBody, error)
Delete(ctx context.Context, address string, nonce, signature []byte, hash [32]byte) error
// Personal storage: the owner's sealed per-account state (contacts, Sent, flags, labels,
// settings). The relay only ever sees ciphertext.
KvGet(ctx context.Context, address string, nonce, signature []byte, key string) (sealed []byte, version uint64, found bool, err error)
KvPut(ctx context.Context, address string, nonce, signature []byte, key string, sealed []byte, expectedVersion uint64) (version uint64, err error)
KvList(ctx context.Context, address string, nonce, signature []byte, prefix string, limit int, cursor string, values bool) (items []relay.KvItem, next string, err error)
KvDelete(ctx context.Context, address string, nonce, signature []byte, key string) error
KvStat(ctx context.Context, address string, nonce, signature []byte) (used, quota, count uint64, err error)
}
RelayProxy is the relay-facing surface the mailbox endpoints need. Challenge issues a fresh single-use nonce the caller must sign to authorize an op for its mailbox; the List/Body/Delete methods verify that signature (against the address's record) and run the op. The private key never leaves the browser — the server only holds the nonce between challenge and complete.
type RelayRouter ¶
type RelayRouter interface {
ConnectPeer(addr string) error
StorePreSignedOnPeer(ctx context.Context, peerID string, senderAddr string, signature []byte, env *message.EncryptedEnvelope) ([32]byte, error)
// SendOnionPreSigned delivers the envelope to the recipient via a fixed 3-hop
// onion route ending at the recipient's relay. The signature (over the envelope
// hash) is produced in the browser; the onion layers use only ephemeral keys +
// relay public keys, so no user key material reaches the server.
SendOnionPreSigned(ctx context.Context, senderAddr string, signature []byte, recipientRec *identity.IdentityRecord, env *message.EncryptedEnvelope) ([32]byte, error)
}
RelayRouter provides the ability to connect to peers and store envelopes on remote relays identified by their peer ID.