Commit Graph

9 Commits (main)

Author SHA1 Message Date
nexxo 200df30b9d Hetzner-Cloud-DNS, Bereitschaftsseite aus sich heraus behebbar
Die alte Hetzner-DNS-API ist abgeschaltet (301 auf die Weboberflaeche).
Jede Bereitstellung starb in ConfigureDnsAndTls, nachdem der Kunde bezahlt
hatte. HttpHetznerDnsClient und DnsTokenCheck sprechen jetzt die Cloud-API:
Bearer statt Auth-API-Token, RRSets ueber {name}/{typ} statt Record-IDs, der
Zonen-Lookup entfaellt. Gegen das Live-Konto gemessen, nicht geraten.

Drei Fallen, die am Konto gemessen wurden: TXT-Werte muessen in
Anfuehrungszeichen (sonst 422, was als read_only gemeldet worden waere), ein
Name mit Zonensuffix wird STILL angenommen (201), und der Fake gab andere IDs
aus als der echte Client -- zwoelf Tests waren gruen ueber einem Abbau, der im
Betrieb geworfen haette.

Bereitschaftspunkte, die nur eine Shell beheben konnte, sind jetzt bedienbar:
SSH-Schluesselpaar erzeugen (Ed25519 ueber phpseclib, privater Teil direkt in
den Tresor), Stripe-Katalog abgleichen (Warteschlange, Trockenlauf, Ausgabe
wortgleich), Mailzustellung als Schalter statt MAIL_MAILER, Neustart der
Arbeiterprozesse. Stripe hat jetzt alle drei Werte in der Konsole: Secret Key,
Signatur-Secret (Tresor, je Modus getrennt) und Publishable Key (Klartext).

Codex-Review (R15), zwei P1 behoben: der Signaturschluessel faellt nicht mehr
vom Live- in den Testplatz, und eine Record-ID aus der alten API macht einen
Host nicht mehr unloeschbar -- was adressierbar ist, wandelt eine Migration um,
der Rest wird beim Loeschen laut uebergangen statt geworfen.

2109 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 19:50:01 +02:00
nexxo 254674f456 Bound the mail test-send and real send to a timeout instead of hanging
tests / pest (push) Failing after 7m25s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
A blocked outbound port (587 filtered at a firewall, as on the live
server) left both MailboxTester and MailboxTransport blocking until
PHP's default_socket_timeout (60s) or the reverse proxy cut the
request — a 504 with no error the operator ever saw. Mail::build()
honours a 'timeout' config key by calling SocketStream::setTimeout()
(confirmed by reading MailManager::configureSmtpTransport() and by an
end-to-end run against a genuinely unroutable host), so MailboxTester
now passes 'timeout' => 10 through it. MailboxTransport builds its
EsmtpTransport directly rather than through Mail::build(), so it sets
the same call on the transport's stream, at 30s — a queue worker can
afford to wait longer than an operator watching a browser, and a
timed-out send is retried rather than lost.

The test button also now shows a visible "sending" state while the
request is in flight, matching the wire:loading span pattern already
used on admin/host-create's save button — previously only
wire:loading.attr="disabled" fired, leaving a greyed-out button with
no other sign anything was happening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 16:36:14 +02:00
nexxo 2979f008b5 Stop a mismatched-key decrypt from crashing the mailbox test button
tests / pest (push) Failing after 7m15s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
tests / pest (pull_request) Failing after 7m17s Details
tests / assets (pull_request) Successful in 24s Details
tests / release (pull_request) Has been skipped Details
2026-07-28 07:26:42 +02:00
nexxo ebe29f8a5d Stop demanding a cipher, a real port, or a decrypted password a mailbox never uses
MailboxTester required a usable SECRETS_KEY unconditionally, refusing an
unauthenticated relay's test-send on a fresh install with no key yet, even
though real sending never touches the cipher for that mailbox. Gated the
check on authenticates, same as the password-presence check beside it.

Comparison pass across MailboxTester and MailboxTransport turned up two more
of the same shape. MailboxTransport's delegate-cache fingerprint decrypted
$box->password unconditionally, even when unauthenticated and never going to
use it — a mailbox that once authenticated, stored a password, and later had
authenticates unchecked would crash real sending the moment SECRETS_KEY
became unusable. And MailboxTester had no guard against a stored port <1;
Symfony silently reinterprets that as port 25 rather than refusing it, so the
test button could reach whatever happens to listen there and report success
for a configuration MailboxTransport refuses outright.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 06:21:28 +02:00
nexxo 5d306fce31 Let a mailbox send without a password when it never needed one
An SMTP relay with a host and a from address but no username or password
- a trusted local or private-network relay - always used to be seeded as
a placeholder, because the takeover required a username to treat an
account as real, and Mailbox::isConfigured() always required a stored
password. Laravel's own SMTP mailer has supported unauthenticated relays
all along.

Add mailboxes.authenticates (default true, so every existing row keeps
its current behaviour) to tell "does not authenticate" apart from
"authenticates, but nobody has typed the password yet" - the same empty
password column used to mean both. The takeover migration now seeds the
real account off the host/address alone and marks it authenticates=false
only when neither a username nor a password resolved; isConfigured(),
MailboxTransport and MailboxTester all read it before requiring or
sending a password; the edit modal gets a checkbox for it with the
password field's hint updated to match, in both languages.
2026-07-28 05:54:21 +02:00
nexxo 3278f063ea Validate purpose mappings against real mailboxes, and clear stale verification on identity changes
savePurposes() only checked that each key was a nonempty string, so a
stale or tampered mailbox key (reachable by posting straight to
/livewire/update) saved successfully while MailboxResolver silently
returned null for it. Every purpose's key must now name a mailbox that
exists; system's must additionally be active, since every unmapped or
inactive OTHER purpose falls through to it.

EditMailbox::save() also cleared last_verified_at only when the
password changed, but smtpUsername() depends on address and username
too (falling back to address when username is blank) — changing
either while leaving the password untouched kept showing a successful
verification for an identity nothing had actually tested.
2026-07-28 04:35:16 +02:00
nexxo 5de1f45703 Close two ways a mail.manage session could take over outgoing mail
I3: nothing stopped an operator unchecking Aktiv on the mailbox that
mail.purpose.system points at. MailboxResolver::active() filters an
inactive mailbox at BOTH the direct lookup and the system fallback, so
that one checkbox could take mail.purpose.system to null and every
purpose without its own mapping down with it — the existing
system_required guard on savePurposes() reads as if this were covered,
but it only blocks the empty-mapping path, not this one.
EditMailbox::save() now refuses active=false against the mailbox 'system'
currently points at, the same way savePurposes() already refuses to leave
'system' empty.

I4: Admin\Mail::saveServer() writes mail.host — the platform's outbound
relay for every purpose mailbox at once — and CloudReady::toMail() puts a
customer's Nextcloud admin password in cleartext in the message body. So a
signed-in mail.manage session was already a credential-interception
primitive, the same threat Admin\Secrets' second gate exists for. Both
saveServer() and EditMailbox::save() (only when a new password is actually
being typed) now also require passwordRecentlyConfirmed(), following the
ConfirmsPassword pattern Secrets.php and Settings.php's two-factor actions
already use — including their per-action UI shape (an inline confirm form
scoped to the gated action), not a whole-page lock, so address, display
name, username, no-reply, active and the purpose mapping stay reachable
without confirming, per the brief's capability split.

Both gated and ungated paths are covered by tests, and both guards were
mutated away and confirmed to fail before being restored.
2026-07-28 03:50:19 +02:00
nexxo befb67327f Prove a mailbox works by actually sending from it 2026-07-28 02:56:47 +02:00
nexxo 4211f3dfab Give the mailboxes a page, and the support sender its own capability 2026-07-28 01:57:11 +02:00