Skip to content

WALL #2 6b: native queue/trigger (remote-cron runner endpoint)#83

Closed
eilandert wants to merge 5 commits into
wall2-6b3-native-emailsettingsfrom
wall2-6b4-native-queue-trigger
Closed

WALL #2 6b: native queue/trigger (remote-cron runner endpoint)#83
eilandert wants to merge 5 commits into
wall2-6b3-native-emailsettingsfrom
wall2-6b4-native-queue-trigger

Conversation

@eilandert
Copy link
Copy Markdown
Owner

Stacked on #82. Native port of the ZF1 QueueController::triggerAction — the
last web route still served by the ZF1 fallback.

Unauthenticated machine endpoint: Bearer-key (SHA-256, constant-time) + source-IP
allowlist gated, then spawns a background runner via the framework-free
ViMbAdmin_QueueRunner::triggerCheck and returns JSON. Empty queue.runner.key
⇒ 404 (disabled). No NATIVE_CONTROLLERS change (queue already native), but this
makes canHandle('/queue/trigger') true so the native stack serves it.

Image validation (vimbadmin:dev)

  • empty key → 404 {"error":"queue trigger disabled"}
  • no bearer → 401 {"error":"missing bearer"}
  • bad key → 403 {"error":"bad key"}
  • correct Bearer + allowed IP → 200 {"triggered":false}

After this, the only remaining ZF1-served routes are the cli-* entrypoints
(cli-run, cli-reset-totp, cli-delete-pending, cli-schema-update) — invoked
as CLI (vimbtool/php public/index.php args), NOT web-dispatched. Those need a
design call (native CLI bootstrap vs standalone shim) and do not block the web
tier being fully native. Then 6c drops the ZF1 fallback + de-Zends library/OSS.

eilandert added 5 commits June 6, 2026 16:48
Foundation for porting the mailer-dependent tail (auth lost-password /
reset-password, mailbox email-settings) off the ZF1 Zend_Mail path. Inert:
nothing calls it yet, mirroring how each WALL #2 resource builder landed.

- composer: require symfony/mailer ^8.0 (matches the locked symfony 8.1
  band; pulls symfony/mime + event-dispatcher + egulias/email-validator).
- src/Kernel/Mail/Mailer.php: framework-free sender. Turns the
  resources.mail.transport.* ini block (type smtp|sendmail, host, port,
  ssl tls|ssl|none, username/password, verify_peer[_name]) into a configured
  symfony transport and sends a Mime\Email. Transport-building decision is a
  pure, testable resolveConfig(); buildTransport() makes the real transport.
  ssl=ssl -> implicit TLS (465); ssl=tls/starttls -> opportunistic STARTTLS
  (587); omitted -> plaintext. verify_peer/_name routed through
  FILTER_VALIDATE_BOOLEAN so the ini "0" string disables verification.
- Container::mailer(): lazily builds the Mailer from
  options[resources][mail][transport]. No Bootstrap change (reads options()).
- tests/test-kernel-mailer.php (26 asserts) + regression.yml step.

src/ stays free of the legacy framework prefix (guard green).
Ports OSS_Controller_Trait_Auth::lostPasswordAction/resetPasswordAction onto
the native kernel, sending through the slice-6b-1 Mailer instead of Zend_Mail.
The last two browser auth routes leave ZF1.

- AbstractController: mailer() (native getMailer) + renderEmail() (body-only
  Smarty render, no page chrome) for the auth/email/* templates.
- AuthController::lostPasswordAction: anti-enumeration (identical response
  whether or not the username exists); 40-char token stored as an expiring
  (2h, max 5) tokens.password_reset indexed preference; reset link mailed via
  the html email template. Captcha honoured when
  resources.auth.oss.lost_password.use_captcha is on — fresh OSS_Captcha_Image
  per render, typed text validated (as a field rule) against the SUBMITTED
  captcha id; image-click requests a new one (requestnewimage) and
  short-circuits before validation. The captcha PNG route (auth/captcha-image)
  stays ZF1 via the dispatcher fallback (de-Zended in 6c).
- AuthController::resetPasswordAction: GET (from the emailed link) prefills
  username + token from the path; valid POST checks the token against the live
  reset-token preferences, sets the new hash, clears all tokens, zeroes any
  failed-login counter, mails a confirmation, redirects to login. All failure
  paths share one generic message.
- sendAuthEmail() honours resources.auth.oss.email_format (html|plaintext|both)
  and sets From/To from identity.mailer.* + the admin email/formatted-name,
  mirroring resolveTemplate() + the legacy actions.
- New native views auth/native-{lost,reset}-password.phtml (FormRenderer +
  captcha image/refresh; no Zend_Form JS validator).

No NATIVE_CONTROLLERS / Kernel change (auth already native; the dispatcher
invokes the new action methods). src/ stays free of the legacy prefix.
Honouring resources.auth.oss.lost_password.use_captcha on the native
lost-password page surfaced two native-bootstrap gaps in the reused ZF1 captcha
(OSS_Captcha_Image extends Zend_Captcha_Image). Both fixed in the ZF1-zone entry
point / the unscanned OSS lib; slice 6c de-Zends the captcha and removes these.

- OSS_Utils::getIniOption(): null-guard the ZF1 front-controller bootstrap param
  (null under the native bootstrap) and fall back to the merged options the
  native entry publishes in Zend_Registry. Without it OSS_Captcha_Image's ctor
  fatals (getTempDir -> getIniOption -> getApplication() on null).
- public/index.php (native zone): bridge Zend_Session to the already-open native
  PHP session — set _sessionStarted/_readable/_writable so a ZF1
  Zend_Session_Namespace (the captcha word store) attaches to the live session
  instead of fataling in Zend_Session::start()/Namespace ("already started" /
  "not marked as readable").

Image-validated (vimbadmin:dev + a mailpit SMTP sink): captcha-on GET renders the
image (auth/captcha-image 200 image/png), the RIGHT word sends the reset mail,
the WRONG word shows the inline "does not match that of the image" error and
sends nothing; captcha-off lost-password + the full reset round-trip also send +
verify (new password logs in, the spent token is rejected).
Ports the ZF1 emailSettingsAction + _sendSettingsEmail onto the native kernel —
the last mailer-dependent browser action. Sends through the slice-6b-1 Mailer.

- MailboxController::emailSettingsAction (GET|POST): the ajax "send settings"
  modal. GET returns the chrome-less modal (type select username/alt_email/other
  + an "other" free-text box, CSRF-guarded); POST send=1 resolves the
  recipient(s) and mails the per-user settings, returning the literal ok/error
  the modal JS expects, or re-rendering the modal with an inline error (the JS
  detects the <div class="modal-header"> prefix and swaps it in). "other" splits
  on comma and validates each address (ZF1 parity).
- sendSettingsEmail(): From = server.email.*; body = mailbox/email/settings.phtml
  rendered with the per-user-substituted server.* display settings
  (Entities\Mailbox::substitute). The mailbox/sendSettingsEmail/preSetBody notify
  hook has no listeners, so it is not replicated; the welcome-email variant + cc
  are dropped (consistent with the other native mailbox actions, no mailer there).
- AbstractController: renderEmail() generalised to renderPartial() (chrome-less
  template render for both email bodies AND ajax partials like this modal);
  AuthController updated to match.
- New native view mailbox/native-email-settings.phtml (modal + type-toggle/ajax JS).

No NATIVE_CONTROLLERS / Kernel change (mailbox already native; the dispatcher
invokes the new action). Image-validated (vimbadmin:dev + mailpit): username ->
mail to the mailbox; other with a bad address -> modal re-render, no send; other
with two valid addresses -> mail to both; CSRF enforced. src/ stays free of the
legacy prefix.
Native port of the ZF1 QueueController::triggerAction — the last WEB route still
served by the ZF1 fallback. Unauthenticated machine endpoint: Bearer-key
(SHA-256, constant-time) + source-IP-allowlist gated, then spawns a background
runner via the framework-free ViMbAdmin_QueueRunner::triggerCheck and returns
JSON. Empty queue.runner.key => 404 (disabled). No NATIVE_CONTROLLERS change
(queue already native; the dispatcher invokes the new action) — but this makes
canHandle('/queue/trigger') true, so the native stack now serves it instead of
falling through to ZF1.

Image-validated (vimbadmin:dev): empty key -> 404; no bearer -> 401; bad key ->
403; correct Bearer + allowed IP -> 200 {"triggered":false}. src/ stays free of
the legacy prefix (calls \ViMbAdmin_Net + \ViMbAdmin_QueueRunner, both
framework-free).
@eilandert eilandert force-pushed the wall2-6b3-native-emailsettings branch from 473018d to 35824c3 Compare June 6, 2026 17:39
@eilandert eilandert deleted the branch wall2-6b3-native-emailsettings June 6, 2026 17:39
@eilandert eilandert closed this Jun 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant