mock(DeploymentService::class); $d->shouldReceive('domain')->andReturn($domain); return app(WebauthnService::class); } public function test_registration_options_use_domain_rpid_and_store_challenge(): void { $user = User::factory()->create(); $opts = $this->service()->registrationOptions($user); $this->assertSame('panel.example.com', data_get($opts, 'rp.id')); $this->assertNotEmpty(data_get($opts, 'challenge')); $this->assertNotNull(session('webauthn.register')); // Target a roaming hardware key (YubiKey) used as a one-touch second factor — not a // platform/password-manager passkey. $this->assertSame('cross-platform', data_get($opts, 'authenticatorSelection.authenticatorAttachment')); $this->assertSame('discouraged', data_get($opts, 'authenticatorSelection.residentKey')); $this->assertSame('discouraged', data_get($opts, 'authenticatorSelection.userVerification')); // WebAuthn L3 hint so passkey managers (Bitwarden) defer to the hardware key. $this->assertSame(['security-key'], data_get($opts, 'hints')); } public function test_assertion_options_require_only_user_presence(): void { $user = User::factory()->create(); $opts = $this->service()->assertionOptions($user); // discouraged = a single touch on login, no PIN/biometric prompt. $this->assertSame('discouraged', data_get($opts, 'userVerification')); $this->assertSame(['security-key'], data_get($opts, 'hints')); } public function test_assertion_options_list_user_credentials(): void { $user = User::factory()->create(); WebauthnCredential::create([ 'user_id' => $user->id, 'name' => 'k', 'credential_id' => rtrim(strtr(base64_encode('rawid'), '+/', '-_'), '='), 'public_key' => '{}', 'sign_count' => 0, ]); $opts = $this->service()->assertionOptions($user->fresh()); $this->assertSame('panel.example.com', data_get($opts, 'rpId')); $this->assertNotEmpty(data_get($opts, 'allowCredentials')); $this->assertNotNull(session('webauthn.login')); } }