69 lines
3.3 KiB
PHP
69 lines
3.3 KiB
PHP
<?php
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Contracts\Encryption\DecryptException;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Hash;
|
|
|
|
// Read-only check: never print a password, encrypted value, MFA secret or token.
|
|
require dirname(__DIR__).'/vendor/autoload.php';
|
|
require __DIR__.'/portal-qa-environment.php';
|
|
$target = portalQaEnvironment();
|
|
$app = require dirname(__DIR__).'/bootstrap/app.php';
|
|
$app->make(Kernel::class)->bootstrap();
|
|
$identity = portalQaAssertTarget($target);
|
|
$directory = storage_path('app/private/investmoz-v2/portal-audit');
|
|
$credentials = json_decode(file_get_contents($directory.'/investor-mfa-qa-private.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
if (($credentials['email'] ?? null) !== 'investor-mfa-qa@example.invalid' || (int) ($credentials['actor_id'] ?? 0) !== 18) {
|
|
throw new RuntimeException('Unexpected synthetic QA identity; refused.');
|
|
}
|
|
$connection = DB::connection();
|
|
$connection->statement('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
|
try {
|
|
$matches = User::whereRaw('lower(trim(email)) = ?', [$credentials['email']])->get();
|
|
$actor = $matches->firstWhere('id', 18);
|
|
$passwordMatches = $actor && Hash::check($credentials['password'], $actor->password);
|
|
$mfaDecrypts = false;
|
|
$mfaSecretMatches = false;
|
|
if ($actor && $actor->two_factor_secret) {
|
|
try {
|
|
$secret = app('encrypter')->decrypt($actor->two_factor_secret);
|
|
$mfaDecrypts = is_string($secret);
|
|
$mfaSecretMatches = isset($credentials['totp_secret']) && hash_equals($credentials['totp_secret'], $secret);
|
|
unset($secret);
|
|
} catch (DecryptException $exception) {
|
|
// Record the classification only; do not expose cipher text or exception details.
|
|
}
|
|
}
|
|
$proof = [
|
|
'captured_at' => gmdate('c'),
|
|
'target' => $identity,
|
|
'transaction_read_only' => $connection->selectOne('SHOW transaction_read_only')->transaction_read_only,
|
|
'account_id' => 18,
|
|
'matching_email_rows' => $matches->count(),
|
|
'account_found' => $actor !== null,
|
|
'email_exact' => $actor && $actor->email === $credentials['email'],
|
|
'local_password_matches_database' => (bool) $passwordMatches,
|
|
'local_password_has_surrounding_whitespace' => trim($credentials['password']) !== $credentials['password'],
|
|
'password_rehash_required' => $actor ? Hash::needsRehash($actor->password) : null,
|
|
'mfa_confirmed' => $actor && $actor->two_factor_confirmed_at !== null,
|
|
'mfa_secret_decrypts' => $mfaDecrypts,
|
|
'local_mfa_secret_matches_database' => $mfaSecretMatches,
|
|
'account_updated_at' => $actor?->updated_at?->toIso8601String(),
|
|
'default_guard' => config('auth.defaults.guard'),
|
|
'provider_model' => config('auth.providers.users.model'),
|
|
'configuration_cached' => $app->configurationIsCached(),
|
|
'database_writes' => 0,
|
|
'active_writes' => 0,
|
|
'password_or_mfa_changed' => false,
|
|
];
|
|
} finally {
|
|
$connection->statement('ROLLBACK');
|
|
unset($credentials, $actor, $matches);
|
|
}
|
|
$path = $directory.'/investor-credential-diagnosis-'.gmdate('Ymd_His').'.json';
|
|
file_put_contents($path, json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
chmod($path, 0600);
|
|
echo json_encode($proof, JSON_THROW_ON_ERROR).PHP_EOL;
|