Переглянути джерело

Upgrade to laragear/webauthn v2 - Fixes #255

Bubka 1 рік тому
батько
коміт
ca903b6fc0

+ 2 - 1
.env.example

@@ -200,7 +200,8 @@ PROXY_LOGOUT_URL=null
 WEBAUTHN_NAME=2FAuth
 
 
-# Relying Party ID. If null, the device will fill it internally.
+# Relying Party ID, should equal the site domain (i.e 2fauth.example.com).
+# If null, the device will fill it internally (recommended)
 # See https://webauthn-doc.spomky-labs.com/prerequisites/the-relying-party#how-to-determine-the-relying-party-id
 
 WEBAUTHN_ID=null

+ 3 - 5
Dockerfile

@@ -194,14 +194,12 @@ ENV \
     # Custom logout URL to open when using an auth proxy.
     PROXY_LOGOUT_URL=null \
     # WebAuthn settings
-    # Relying Party name, aka the name of the application. If null, defaults to APP_NAME
+    # Relying Party name, aka the name of the application. If blank, defaults to APP_NAME. Do not set to null.
     WEBAUTHN_NAME=2FAuth \
-    # Relying Party ID. If null, the device will fill it internally.
+    # Relying Party ID, should equal the site domain (i.e 2fauth.example.com).
+    # If null, the device will fill it internally (recommended)
     # See https://webauthn-doc.spomky-labs.com/prerequisites/the-relying-party#how-to-determine-the-relying-party-id
     WEBAUTHN_ID=null \
-    # Optional image data in BASE64 (128 bytes maximum) or an image url
-    # See https://webauthn-doc.spomky-labs.com/prerequisites/the-relying-party#relying-party-icon
-    WEBAUTHN_ICON=null \
     # Use this setting to control how user verification behave during the
     # WebAuthn authentication flow.
     #

+ 1 - 1
app/Extensions/WebauthnTwoFAuthUserProvider.php

@@ -16,7 +16,7 @@ class WebauthnTwoFAuthUserProvider extends WebAuthnUserProvider
     public function validateCredentials($user, array $credentials) : bool
     {
         if ($user instanceof WebAuthnAuthenticatable && $this->isSignedChallenge($credentials)) {
-            return $this->validateWebAuthn();
+            return $this->validateWebAuthn($user);
         }
 
         // If the user disabled the fallback, we will validate the credential password.

+ 3 - 15
app/Http/Controllers/Auth/WebAuthnLoginController.php

@@ -10,11 +10,10 @@ use Illuminate\Contracts\Support\Responsable;
 use Illuminate\Foundation\Auth\AuthenticatesUsers;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Response;
-use Illuminate\Support\Arr;
 use Illuminate\Support\Facades\Lang;
 use Illuminate\Support\Facades\Log;
+use Laragear\WebAuthn\Enums\UserVerification;
 use Laragear\WebAuthn\Http\Requests\AssertionRequest;
-use Laragear\WebAuthn\WebAuthn;
 
 class WebAuthnLoginController extends Controller
 {
@@ -44,10 +43,10 @@ class WebAuthnLoginController extends Controller
     public function options(AssertionRequest $request) : Responsable|JsonResponse
     {
         switch (config('webauthn.user_verification')) {
-            case WebAuthn::USER_VERIFICATION_DISCOURAGED:
+            case UserVerification::DISCOURAGED:
                 $request = $request->fastLogin();    // Makes the authenticator to only check for user presence on registration
                 break;
-            case WebAuthn::USER_VERIFICATION_REQUIRED:
+            case UserVerification::REQUIRED:
                 $request = $request->secureLogin();  // Makes the authenticator to always verify the user thoroughly on registration
                 break;
         }
@@ -88,17 +87,6 @@ class WebAuthnLoginController extends Controller
             return $this->sendLockoutResponse($request);
         }
 
-        if ($request->has('response')) {
-            $response = $request->response;
-
-            // Some authenticators do not send a userHandle so we hack the response to be compliant
-            // with Laragear\WebAuthn implementation that waits for a userHandle
-            if (! Arr::exists($response, 'userHandle') || blank($response['userHandle'])) {
-                $response['userHandle'] = User::getFromCredentialId($request->id)?->userHandle();
-                $request->merge(['response' => $response]);
-            }
-        }
-
         if ($this->attemptLogin($request)) {
             return $this->sendLoginResponse($request);
         }

+ 3 - 3
app/Http/Controllers/Auth/WebAuthnRegisterController.php

@@ -6,9 +6,9 @@ use App\Http\Controllers\Controller;
 use Illuminate\Contracts\Support\Responsable;
 use Illuminate\Http\Response;
 use Illuminate\Support\Facades\Log;
+use Laragear\WebAuthn\Enums\UserVerification;
 use Laragear\WebAuthn\Http\Requests\AttestationRequest;
 use Laragear\WebAuthn\Http\Requests\AttestedRequest;
-use Laragear\WebAuthn\WebAuthn;
 
 class WebAuthnRegisterController extends Controller
 {
@@ -18,10 +18,10 @@ class WebAuthnRegisterController extends Controller
     public function options(AttestationRequest $request) : Responsable
     {
         switch (config('webauthn.user_verification')) {
-            case WebAuthn::USER_VERIFICATION_DISCOURAGED:
+            case UserVerification::DISCOURAGED:
                 $request = $request->fastRegistration();    // Makes the authenticator to only check for user presence on registration
                 break;
-            case WebAuthn::USER_VERIFICATION_REQUIRED:
+            case UserVerification::REQUIRED:
                 $request = $request->secureRegistration();  // Makes the authenticator to always verify the user thoroughly on registration
                 break;
         }

+ 0 - 15
app/Models/Traits/WebAuthnManageCredentials.php

@@ -4,7 +4,6 @@ namespace App\Models\Traits;
 
 use App\Notifications\WebauthnRecoveryNotification;
 use Illuminate\Database\Eloquent\Collection;
-use Illuminate\Support\Str;
 
 /**
  * @see \App\Models\WebAuthnAuthenticatable
@@ -12,20 +11,6 @@ use Illuminate\Support\Str;
  */
 trait WebAuthnManageCredentials
 {
-    /**
-     * Return the handle used to identify his credentials.
-     */
-    public function userHandle() : string
-    {
-        // Laragear\WebAuthn uses Ramsey\Uuid\Uuid::fromString()->getHex()->toString()
-        // to obtain a UUID v4 with dashes removed and uses it as user_id (aka userHandle)
-        // see https://github.com/ramsey/uuid/blob/4.x/src/Uuid.php#L379
-        // and Laragear\WebAuthn\Assertion\Validator\Pipes\CheckCredentialIsForUser::validateId()
-
-        return $this->webAuthnCredentials()->value('user_id')
-            ?? str_replace('-', '', Str::uuid()->toString());
-    }
-
     /**
      * Saves a new alias for a given WebAuthn credential.
      */

+ 0 - 5
app/Models/WebAuthnAuthenticatable.php

@@ -6,11 +6,6 @@ use Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable as Authenticatable;
 
 interface WebAuthnAuthenticatable extends Authenticatable
 {
-    /**
-     * Return the handle used to identify his credentials.
-     */
-    public function userHandle() : string;
-
     /**
      * Saves a new alias for a given WebAuthn credential.
      */

+ 1 - 1
composer.json

@@ -27,7 +27,7 @@
         "guzzlehttp/guzzle": "^7.2",
         "jackiedo/dotenv-editor": "^2.1",
         "khanamiryan/qrcode-detector-decoder": "^2.0.2",
-        "laragear/webauthn": "^1.2.0",
+        "laragear/webauthn": "^2.0",
         "laravel/framework": "^10.10",
         "laravel/passport": "^11.2",
         "laravel/socialite": "^5.10",

+ 86 - 19
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "15022c60c2ef59e0821ae0064f477e50",
+    "content-hash": "7c27f612b0b319b88f2b8b3f1d52a51d",
     "packages": [
         {
             "name": "brick/math",
@@ -1917,35 +1917,102 @@
             },
             "time": "2022-11-17T10:54:53+00:00"
         },
+        {
+            "name": "laragear/meta-model",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Laragear/MetaModel.git",
+                "reference": "86aa8bbd0e1b9d03467a0257f0cd5815b6836a34"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Laragear/MetaModel/zipball/86aa8bbd0e1b9d03467a0257f0cd5815b6836a34",
+                "reference": "86aa8bbd0e1b9d03467a0257f0cd5815b6836a34",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/database": "10.*|11.*",
+                "php": "^8.1"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.6",
+                "phpunit/phpunit": "^10.5|11.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Laragear\\MetaModel\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Italo Israel Baeza Cabrera",
+                    "email": "DarkGhostHunter@Gmail.com",
+                    "homepage": "https://github.com/sponsors/DarkGhostHunter",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Let other developers customize your package model and migrations",
+            "keywords": [
+                "database",
+                "eloquent",
+                "laravel",
+                "model"
+            ],
+            "support": {
+                "issues": "https://github.com/Laragear/MetaModel/issues",
+                "source": "https://github.com/Laragear/MetaModel"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/DarkGhostHunter",
+                    "type": "Github Sponsorship"
+                },
+                {
+                    "url": "https://paypal.me/darkghosthunter",
+                    "type": "Paypal"
+                }
+            ],
+            "time": "2024-03-15T23:27:56+00:00"
+        },
         {
             "name": "laragear/webauthn",
-            "version": "v1.2.1",
+            "version": "v2.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Laragear/WebAuthn.git",
-                "reference": "e57ac258b0d76eee4ab77c1e1b465f5d8e0c46de"
+                "reference": "15b29db0edb0a12c0fa45c404e57b0d5f1789465"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Laragear/WebAuthn/zipball/e57ac258b0d76eee4ab77c1e1b465f5d8e0c46de",
-                "reference": "e57ac258b0d76eee4ab77c1e1b465f5d8e0c46de",
+                "url": "https://api.github.com/repos/Laragear/WebAuthn/zipball/15b29db0edb0a12c0fa45c404e57b0d5f1789465",
+                "reference": "15b29db0edb0a12c0fa45c404e57b0d5f1789465",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
                 "ext-openssl": "*",
-                "illuminate/auth": "9.*|10.*",
-                "illuminate/config": "9.*|10.*",
-                "illuminate/database": "9.*|10.*",
-                "illuminate/encryption": "9.*|10.*",
-                "illuminate/http": "9.*|10.*",
-                "illuminate/session": "9.*|10.*",
-                "illuminate/support": "9.*|10.*",
-                "php": "8.*"
+                "illuminate/auth": "10.*|11.*",
+                "illuminate/config": "10.*|11.*",
+                "illuminate/database": "10.*|11.*",
+                "illuminate/encryption": "10.*|11.*",
+                "illuminate/http": "10.*|11.*",
+                "illuminate/session": "10.*|11.*",
+                "illuminate/support": "10.*|11.*",
+                "laragear/meta-model": "^1.1",
+                "php": "^8.1"
             },
             "require-dev": {
-                "jetbrains/phpstorm-attributes": "*",
-                "orchestra/testbench": "^7.22|8.*"
+                "ext-sodium": "*",
+                "orchestra/testbench": "8.*|9.*"
+            },
+            "suggest": {
+                "paragonie/sodium_compat": "To enable EdDSA 25519 keys from authenticators, if `ext-sodium` is unavailable."
             },
             "type": "library",
             "extra": {
@@ -1976,7 +2043,7 @@
                     "role": "Developer"
                 }
             ],
-            "description": "Authenticate your users with biometric data, devices or USB keys.",
+            "description": "Authenticate users with Passkeys: fingerprints, patterns and biometric data.",
             "homepage": "https://github.com/laragear/webauthn",
             "keywords": [
                 "Authentication",
@@ -1988,8 +2055,8 @@
                 "windows hello"
             ],
             "support": {
-                "issues": "https://github.com/Laragear/TwoFactor/issues",
-                "source": "https://github.com/Laragear/TwoFactor"
+                "issues": "https://github.com/Laragear/WebAuthn/issues",
+                "source": "https://github.com/Laragear/WebAuthn"
             },
             "funding": [
                 {
@@ -2001,7 +2068,7 @@
                     "type": "Paypal"
                 }
             ],
-            "time": "2023-03-09T18:38:16+00:00"
+            "time": "2024-03-18T22:38:29+00:00"
         },
         {
             "name": "laravel/framework",

+ 3 - 5
docker/docker-compose.yml

@@ -91,14 +91,12 @@ services:
       # Custom logout URL to open when using an auth proxy.
       - PROXY_LOGOUT_URL=null
       # WebAuthn settings
-      # Relying Party name, aka the name of the application. If null, defaults to APP_NAME
+      # Relying Party name, aka the name of the application. If blank, defaults to APP_NAME. Do not set to null.
       - WEBAUTHN_NAME=2FAuth
-      # Relying Party ID. If null, the device will fill it internally.
+      # Relying Party ID, should equal the site domain (i.e 2fauth.example.com).
+      # If null, the device will fill it internally (recommended)
       # See https://webauthn-doc.spomky-labs.com/prerequisites/the-relying-party#how-to-determine-the-relying-party-id
       - WEBAUTHN_ID=null
-      # Optional image data in BASE64 (128 bytes maximum) or an image url
-      # See https://webauthn-doc.spomky-labs.com/prerequisites/the-relying-party#relying-party-icon
-      - WEBAUTHN_ICON=null
       # Use this setting to control how user verification behave during the
       # WebAuthn authentication flow.
       #

+ 4 - 4
tests/Feature/Http/Auth/WebAuthnLoginControllerTest.php

@@ -8,7 +8,7 @@ use App\Models\User;
 use Illuminate\Support\Facades\Config;
 use Illuminate\Support\Facades\DB;
 use Laragear\WebAuthn\Assertion\Validator\AssertionValidator;
-use Laragear\WebAuthn\WebAuthn;
+use Laragear\WebAuthn\Enums\UserVerification;
 use PHPUnit\Framework\Attributes\CoversClass;
 use Tests\FeatureTestCase;
 
@@ -369,7 +369,7 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
      */
     public function test_get_options_returns_success()
     {
-        Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_PREFERRED);
+        Config::set('webauthn.user_verification', UserVerification::PREFERRED);
 
         $this->user = User::factory()->create(['email' => self::EMAIL]);
 
@@ -409,7 +409,7 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
      */
     public function test_get_options_for_securelogin_returns_required_userVerification()
     {
-        Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_REQUIRED);
+        Config::set('webauthn.user_verification', UserVerification::REQUIRED);
 
         $this->user = User::factory()->create(['email' => self::EMAIL]);
 
@@ -451,7 +451,7 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
      */
     public function test_get_options_for_fastlogin_returns_discouraged_userVerification()
     {
-        Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_DISCOURAGED);
+        Config::set('webauthn.user_verification', UserVerification::DISCOURAGED);
 
         $this->user = User::factory()->create(['email' => self::EMAIL]);
 

+ 3 - 3
tests/Feature/Http/Auth/WebAuthnRegisterControllerTest.php

@@ -5,10 +5,10 @@ namespace Tests\Feature\Http\Auth;
 use App\Http\Controllers\Auth\WebAuthnRegisterController;
 use App\Models\User;
 use Illuminate\Support\Facades\Config;
+use Laragear\WebAuthn\Enums\UserVerification;
 use Laragear\WebAuthn\Http\Requests\AttestationRequest;
 use Laragear\WebAuthn\Http\Requests\AttestedRequest;
 use Laragear\WebAuthn\JsonTransport;
-use Laragear\WebAuthn\WebAuthn;
 use PHPUnit\Framework\Attributes\CoversClass;
 use Tests\FeatureTestCase;
 
@@ -38,7 +38,7 @@ class WebAuthnRegisterControllerTest extends FeatureTestCase
      */
     public function test_uses_attestation_with_fastRegistration_request() : void
     {
-        Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_DISCOURAGED);
+        Config::set('webauthn.user_verification', UserVerification::DISCOURAGED);
 
         $request = $this->mock(AttestationRequest::class);
 
@@ -55,7 +55,7 @@ class WebAuthnRegisterControllerTest extends FeatureTestCase
      */
     public function test_uses_attestation_with_secureRegistration_request() : void
     {
-        Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_REQUIRED);
+        Config::set('webauthn.user_verification', UserVerification::REQUIRED);
 
         $request = $this->mock(AttestationRequest::class);
 

+ 4 - 8
tests/Feature/Models/UserModelTest.php

@@ -2,14 +2,10 @@
 
 namespace Tests\Feature\Models;
 
-use App\Extensions\WebauthnCredentialBroker;
 use App\Models\Group;
 use App\Models\TwoFAccount;
 use App\Models\User;
-use Database\Factories\UserFactory;
 use Illuminate\Auth\Events\PasswordReset;
-use Illuminate\Http\Testing\FileFactory;
-use Illuminate\Support\Carbon;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Event;
 use Illuminate\Support\Facades\Password;
@@ -52,7 +48,7 @@ class UserModelTest extends FeatureTestCase
      */
     public function test_isAdministrator_returns_correct_state()
     {
-        $user = User::factory()->create();
+        $user  = User::factory()->create();
         $admin = User::factory()->administrator()->create();
 
         $this->assertEquals($user->isAdministrator(), false);
@@ -88,7 +84,7 @@ class UserModelTest extends FeatureTestCase
      */
     public function test_resetPassword_resets_password_with_success()
     {
-        $user = User::factory()->create();
+        $user        = User::factory()->create();
         $oldPassword = $user->password;
 
         $user->resetPassword();
@@ -118,7 +114,7 @@ class UserModelTest extends FeatureTestCase
         $user = User::factory()->create();
         TwoFAccount::factory()->for($user)->create();
         Group::factory()->for($user)->create();
-        
+
         DB::table('webauthn_credentials')->insert([
             'id'                   => '-VOLFKPY-_FuMI_sJ7gMllK76L3VoRUINj6lL_Z3qDg',
             'authenticatable_type' => \App\Models\User::class,
@@ -139,7 +135,7 @@ class UserModelTest extends FeatureTestCase
         Password::broker()->createToken($user);
 
         $user->delete();
-        
+
         $this->assertDatabaseMissing('twofaccounts', [
             'user_id' => $user->id,
         ]);