Upgrade to laragear/webauthn v2 - Fixes #255

This commit is contained in:
Bubka 2024-03-29 09:21:00 +01:00
parent 4a8db39ab0
commit ca903b6fc0
13 changed files with 115 additions and 87 deletions

View File

@ -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

8
Dockerfile vendored
View File

@ -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.
#

View File

@ -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.

View File

@ -10,11 +10,10 @@
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 @@ public function login(WebauthnAssertedRequest $request)
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);
}

View File

@ -6,9 +6,9 @@
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;
}

View File

@ -4,7 +4,6 @@
use App\Notifications\WebauthnRecoveryNotification;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
/**
* @see \App\Models\WebAuthnAuthenticatable
@ -12,20 +11,6 @@
*/
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.
*/

View File

@ -6,11 +6,6 @@
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.
*/

View File

@ -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",

109
composer.lock generated
View File

@ -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",
@ -1918,34 +1918,101 @@
"time": "2022-11-17T10:54:53+00:00"
},
{
"name": "laragear/webauthn",
"version": "v1.2.1",
"name": "laragear/meta-model",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/Laragear/WebAuthn.git",
"reference": "e57ac258b0d76eee4ab77c1e1b465f5d8e0c46de"
"url": "https://github.com/Laragear/MetaModel.git",
"reference": "86aa8bbd0e1b9d03467a0257f0cd5815b6836a34"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Laragear/WebAuthn/zipball/e57ac258b0d76eee4ab77c1e1b465f5d8e0c46de",
"reference": "e57ac258b0d76eee4ab77c1e1b465f5d8e0c46de",
"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": "v2.0.3",
"source": {
"type": "git",
"url": "https://github.com/Laragear/WebAuthn.git",
"reference": "15b29db0edb0a12c0fa45c404e57b0d5f1789465"
},
"dist": {
"type": "zip",
"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",

View File

@ -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.
#

View File

@ -8,7 +8,7 @@
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 @@ public function test_too_many_invalid_login_attempts_returns_too_many_request_er
*/
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 @@ public function test_get_options_returns_success()
*/
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 @@ public function test_get_options_for_securelogin_returns_required_userVerificati
*/
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]);

View File

@ -5,10 +5,10 @@
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 @@ public function setUp() : void
*/
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 @@ public function test_uses_attestation_with_fastRegistration_request() : void
*/
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);

View File

@ -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 @@ public function test_admin_scope_returns_only_admin()
*/
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 @@ public function test_promoteToAdministrator_demote_administrator_status()
*/
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 @@ public function test_delete_removes_user_data()
$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 @@ public function test_delete_removes_user_data()
Password::broker()->createToken($user);
$user->delete();
$this->assertDatabaseMissing('twofaccounts', [
'user_id' => $user->id,
]);