fix(engine-b): always scrub credentials once delivered; idempotent monitoring lookup

- CompleteProvisioning scrubs admin_password whenever credentials_sent exists,
  so a crash between recording and scrubbing can't leave the password in context.
- HttpMonitoringClient looks up an existing monitor by URL before creating one,
  so a retry after a crashed POST doesn't create a duplicate external monitor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
nexxo 2026-07-25 12:46:34 +02:00
parent 661b52dbe0
commit 778bb7f117
2 changed files with 19 additions and 4 deletions

View File

@ -32,13 +32,17 @@ class CompleteProvisioning extends CustomerStep
$encrypted = $run->context('admin_password');
if ($encrypted !== null && ! $this->hasResource($run, 'credentials_sent')) {
// Pass the ENCRYPTED password — CloudReady is queued, so plaintext
// would land in the queue payload; it decrypts only when building the mail.
// Pass the ENCRYPTED password (decrypted only when building the mail).
Notification::route('mail', $order->customer->email)->notify(
new CloudReady($instance, (string) $instance->nc_admin_ref, $encrypted),
);
// Record delivery so a retry after a crash doesn't re-send.
$this->recordResource($run, $instance->host, 'credentials_sent', (string) $instance->nc_admin_ref);
}
// Always scrub once delivery is recorded — even if a crash interrupted a
// previous attempt between recording and scrubbing.
if ($this->hasResource($run, 'credentials_sent')) {
$run->forgetContext('admin_password');
}

View File

@ -18,8 +18,19 @@ class HttpMonitoringClient implements MonitoringClient
return 'mon-'.substr(sha1($url), 0, 12);
}
return (string) Http::withToken((string) config('services.monitoring.token'))
->post(rtrim($endpoint, '/').'/monitors', ['friendly_name' => $name, 'url' => $url, 'type' => 'https'])
$base = rtrim($endpoint, '/');
$token = (string) config('services.monitoring.token');
// Idempotent: reuse an existing monitor for this URL rather than creating
// a duplicate when a crash retries after a prior successful POST.
$existing = collect(Http::withToken($token)->get($base.'/monitors')->throw()->json('monitors', []))
->first(fn ($monitor) => ($monitor['url'] ?? null) === $url);
if ($existing !== null) {
return (string) $existing['id'];
}
return (string) Http::withToken($token)
->post($base.'/monitors', ['friendly_name' => $name, 'url' => $url, 'type' => 'https'])
->throw()->json('monitor.id');
}