CluPilotCloud/app/Console/Commands/VerifyCustomDomains.php

80 lines
2.8 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Instance;
use App\Services\Domains\DomainVerifier;
use Illuminate\Console\Command;
/**
* Re-reads every custom domain's proof, nightly.
*
* Asked for in exactly these words: the token must be checked again and again,
* so that nobody puts it in, passes the check, and takes it straight back out.
* At night, because a domain that is withdrawn is a domain that stops being
* served, and that is not a thing to do to somebody at eleven in the morning.
*
* Three consecutive misses before a verified domain is withdrawn. One failed
* lookup is a nameserver having a bad minute; taking a working Nextcloud
* offline over a hiccup would be far worse than the hole it closes.
*/
class VerifyCustomDomains extends Command
{
protected $signature = 'clupilot:verify-domains {--instance= : one instance uuid, for checking on demand}';
protected $description = 'Re-read the DNS proof for every custom domain and withdraw the ones that lost it';
public function handle(DomainVerifier $verifier): int
{
$query = Instance::query()->whereNotNull('custom_domain')->whereNotNull('domain_token');
if ($uuid = $this->option('instance')) {
$query->where('uuid', $uuid);
}
$checked = 0;
$withdrawn = 0;
foreach ($query->cursor() as $instance) {
$checked++;
$present = $verifier->proofPresent($instance);
if ($present) {
$instance->forceFill([
'domain_verified_at' => $instance->domain_verified_at ?? now(),
'domain_checked_at' => now(),
'domain_error' => null,
'domain_failures' => 0,
])->save();
continue;
}
$failures = $instance->domain_failures + 1;
// Withdrawal clears verified_at, which is the single flag every
// consumer reads. Nothing else is deleted: the domain and its token
// stay on the row, so the customer sees what is wrong and can put
// the record back rather than starting over.
$withdraw = $instance->domain_verified_at !== null
&& $failures >= DomainVerifier::FAILURES_BEFORE_WITHDRAWAL;
$instance->forceFill([
'domain_checked_at' => now(),
'domain_error' => 'missing_txt',
'domain_failures' => $failures,
'domain_verified_at' => $withdraw ? null : $instance->domain_verified_at,
])->save();
if ($withdraw) {
$withdrawn++;
$this->warn("Withdrew {$instance->custom_domain}: proof missing on {$failures} consecutive checks.");
}
}
$this->info("Checked {$checked} domain(s), withdrew {$withdrawn}.");
return self::SUCCESS;
}
}