Decided by the owner on 2026-10-13 (in place of preflight), taken up on 2026-10-16; mockupsb2dcefc,cc33890,0feaa55(his: the greeting on a row of its own, the name check a tag beside Names). - The certificate: health.Certificate keeps its names (DNS names, or the CN without any) and ForHost holds it to SELFPOST_HOSTNAME as a TLS client does - VerifyHostname over the DNS names, the CN only without any, as OpenSSL's X509_check_host and so a sending Postfix still take it (src/tls/tls_client.c:439, 513). A certificate for another name fails; the tag beside Names says matches / not for the hostname. - The greeting: postconf -x -h smtpd_banner, read once at the panel's start like the served certificate's path (main.cf is not rewritten after), is what smtpd writes after "220 " (src/smtpd/smtpd.c:5979). A first word other than the hostname warns, with what to set. A fact may now take the whole row (Fact.Wide, sp-wide) and carry a tag beside its label (Fact.LabelTag); both on the kit page. - The proxy: auth.Module.Seen says the address the panel counts sign-ins against, the peer, and whether X-Forwarded-For was read or left unread for an untrusted peer; the Configuration card shows "Your address" and, in the second case, warns that every visitor shares one sign-in limit and names the /32 (or /128) to put in TRUSTED_PROXY_CIDR. Tests: TestCertificateForHost (CN-only, DNS names, a wildcard, the CN ignored beside DNS names), TestSeenAddress, TestHealthSaysWhereItSeesTheViewer, TestHealthGreetingAndCertificateName; e2e reads the stand's right state on Health (matches, "220 mail.e2e.test ESMTP", a direct client). go vet + go test (26 packages) and make e2e on the test server for this tree. Docs: help (word for word with the mockup), guide § Overview and Health, CHANGELOG [Unreleased] § Added, roadmap row done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
111 lines
3.3 KiB
Go
111 lines
3.3 KiB
Go
// Package handlers implements the panel's authenticated HTTP handlers.
|
|
package handlers
|
|
|
|
import (
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/mixeme/selfpost/internal/app"
|
|
"github.com/mixeme/selfpost/internal/dmarc"
|
|
"github.com/mixeme/selfpost/internal/dnscheck"
|
|
"github.com/mixeme/selfpost/internal/domain"
|
|
"github.com/mixeme/selfpost/internal/health"
|
|
"github.com/mixeme/selfpost/internal/inbound"
|
|
"github.com/mixeme/selfpost/internal/postfix"
|
|
"github.com/mixeme/selfpost/internal/store"
|
|
"github.com/mixeme/selfpost/internal/web/auth"
|
|
"github.com/mixeme/selfpost/internal/web/view"
|
|
)
|
|
|
|
// Config holds handler-specific panel configuration.
|
|
type Config struct {
|
|
Hostname string
|
|
SubmissionEnabled bool
|
|
MailLogPath string
|
|
DataDir string
|
|
DBPath string
|
|
DeployRoot string
|
|
Version string
|
|
TLSCertFile string
|
|
TLSServedCertFile string
|
|
TLSReloadInterval time.Duration
|
|
SMTPBanner string
|
|
SignSocket string
|
|
SignLocalSocket string
|
|
JournalSocket string
|
|
SenderSocket string
|
|
// Level-1 Postfix anvil backstop (env RATE_LIMIT_*), shown in the panel
|
|
// and used to cap domain/app level-2 ceilings (guide § Rate limiting).
|
|
RateLimitMessagesPerIP int
|
|
RateLimitWindowSeconds int
|
|
// RetryPolicy is this Postfix's deferred-mail timings, snapshotted once
|
|
// when the HTTP role starts (architecture.md). The Mail queue card and
|
|
// delivery history read it from here; they never call postconf.
|
|
RetryPolicy postfix.RetryPolicy
|
|
// InboundEnabled mirrors INBOUND_RELAY_ENABLE: the inbound panel and
|
|
// routes exist only when this is true.
|
|
InboundEnabled bool
|
|
// InboundAntispamMilter mirrors INBOUND_ANTISPAM_MILTER and
|
|
// InboundAntispamAction INBOUND_ANTISPAM_MILTER_ACTION ("accept",
|
|
// "tempfail", or "" for a value build/postfix-config.sh would refuse). The
|
|
// filter is on only when the inbound relay is on and the milter is set.
|
|
InboundAntispamMilter string
|
|
InboundAntispamAction string
|
|
// DMARCEnabled mirrors DMARC_REPORTS_ENABLE.
|
|
DMARCEnabled bool
|
|
// SendLogRetentionEnvDefault is SEND_LOG_RETENTION_DAYS at panel start; used
|
|
// as bootstrap and fallback when the settings row is missing or invalid.
|
|
SendLogRetentionEnvDefault int
|
|
// InboundSpamLogRetentionEnvDefault is INBOUND_SPAM_LOG_RETENTION_DAYS at
|
|
// panel start, the same bootstrap and fallback for the inbound journal.
|
|
InboundSpamLogRetentionEnvDefault int
|
|
}
|
|
|
|
// Handlers holds dependencies for authenticated panel routes.
|
|
type Handlers struct {
|
|
store *store.Store
|
|
domains *domain.Service
|
|
apps *app.Service
|
|
inbound *inbound.Service
|
|
lists *inbound.Lists
|
|
dmarc *dmarc.Service
|
|
cfg Config
|
|
view *view.Engine
|
|
dns *dnscheck.Checker
|
|
machine *health.MachineSampler
|
|
auth *auth.Module
|
|
}
|
|
|
|
// New builds authenticated panel handlers.
|
|
func New(
|
|
st *store.Store,
|
|
domains *domain.Service,
|
|
apps *app.Service,
|
|
inboundSvc *inbound.Service,
|
|
lists *inbound.Lists,
|
|
dmarcSvc *dmarc.Service,
|
|
cfg Config,
|
|
v *view.Engine,
|
|
dns *dnscheck.Checker,
|
|
machine *health.MachineSampler,
|
|
a *auth.Module,
|
|
) *Handlers {
|
|
return &Handlers{
|
|
store: st,
|
|
domains: domains,
|
|
apps: apps,
|
|
inbound: inboundSvc,
|
|
lists: lists,
|
|
dmarc: dmarcSvc,
|
|
cfg: cfg,
|
|
view: v,
|
|
dns: dns,
|
|
machine: machine,
|
|
auth: a,
|
|
}
|
|
}
|
|
|
|
func logf(format string, args ...any) {
|
|
log.Printf(format, args...)
|
|
}
|