17 Commits

Author SHA1 Message Date
Italo
652df193f1 Merge pull request #39 from Laragear/fix/key-length
[1.x] Fixes id length limitation #38
2023-03-09 14:42:04 -03:00
Italo Israel Baeza Cabrera
cd40888eb9 Fixes id length limitation #38 2023-03-09 14:39:27 -03:00
Italo
42558d9787 Merge pull request #34 from Laragear/feat/php-8.2
[1.x] Adds PHP 8.2, Laravel 10 Support
2023-02-22 02:07:52 -03:00
Italo Israel Baeza Cabrera
07ebd2b337 Should have fixed not found models 2023-02-22 02:05:39 -03:00
Italo Israel Baeza Cabrera
f9eee331f9 Fixes PHPUnit config version. 2023-02-22 01:57:15 -03:00
Italo Israel Baeza Cabrera
639ca1aa28 Fixes test for migration publishing. 2023-02-22 01:56:23 -03:00
Italo Israel Baeza Cabrera
0ea8f8d82b Initial PHP 8.2, Laravel 10 support. 2023-02-22 01:47:28 -03:00
Italo
2e420ba518 Merge pull request #30 from deibertf/1.x
Update webauthn.js to prevent wrong request urls
2023-02-15 22:18:36 -03:00
Italo
73502cea4e Use const, minor line break fix. 2023-02-15 22:16:02 -03:00
Felix Deibert
0b381551e0 Update webauthn.js 2023-02-16 00:34:39 +01:00
Felix Deibert
b0aa1974de Update webauthn.js 2023-02-16 00:10:01 +01:00
Italo
3291c57a3a Clarified hypothetical controllers 2023-01-19 11:55:21 -03:00
Italo
2ed7cdeff3 Minor clarification to WEBAUTHN_ID 2023-01-19 11:48:49 -03:00
Italo
e2af6a8395 Merge pull request #24 from Laragear/feat/trait_tests
[1.x] Adds tests for trait, fixing some methods.
2022-11-11 18:30:09 -03:00
Italo Israel Baeza Cabrera
89f15373bc Adds tests for trait, fixing some methods. 2022-11-11 18:26:07 -03:00
Italo
afa5b62107 Merge pull request #23 from Bubka/1.x
[1.1.3] FIX: Query error when disabling all credentials
2022-11-11 17:34:45 -03:00
Bubka
ef3ad38a16 Fixes query error when disabling all credentials 2022-11-11 18:05:43 +01:00
12 changed files with 345 additions and 75 deletions

View File

@@ -52,11 +52,16 @@ jobs:
php-version: php-version:
- "8.0" - "8.0"
- "8.1" - "8.1"
- "8.2"
laravel-constrain: laravel-constrain:
- "9.*" - "9.*"
- "10.*"
dependencies: dependencies:
- "lowest" - "lowest"
- "highest" - "highest"
exclude:
- laravel-constrain: "10.*"
php-version: "8.0"
steps: steps:
- name: "Set up PHP" - name: "Set up PHP"
uses: "shivammathur/setup-php@v2" uses: "shivammathur/setup-php@v2"
@@ -77,7 +82,7 @@ jobs:
run: "composer run-script test" run: "composer run-script test"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
uses: "codecov/codecov-action@v2" uses: "codecov/codecov-action@v3"
static_analysis: static_analysis:
name: "3⃣ Static Analysis" name: "3⃣ Static Analysis"
@@ -91,6 +96,7 @@ jobs:
with: with:
tools: "phpstan" tools: "phpstan"
php-version: "latest" php-version: "latest"
coverage: "none"
- name: "Checkout code" - name: "Checkout code"
uses: "actions/checkout@v3" uses: "actions/checkout@v3"

1
.gitignore vendored
View File

@@ -4,5 +4,6 @@
/.vscode /.vscode
.php-cs-fixer.cache .php-cs-fixer.cache
.phpunit.result.cache .phpunit.result.cache
.phpunit.cache
composer.lock composer.lock
phpunit.xml.bak phpunit.xml.bak

View File

@@ -258,6 +258,8 @@ const webAuthn = new WebAuthn({}, {
Attestation is the _ceremony_ to create WebAuthn Credentials. To create an Attestable Response that the user device can understand, use the `AttestationRequest::toCreate()` form request. Attestation is the _ceremony_ to create WebAuthn Credentials. To create an Attestable Response that the user device can understand, use the `AttestationRequest::toCreate()` form request.
For example, we can create our own `AttestationController` to create it.
```php ```php
// app\Http\Controllers\WebAuthn\AttestationController.php // app\Http\Controllers\WebAuthn\AttestationController.php
use Laragear\WebAuthn\Http\Requests\AttestationRequest; use Laragear\WebAuthn\Http\Requests\AttestationRequest;
@@ -354,6 +356,8 @@ The Assertion procedure also follows a two-step procedure: the user will input i
First, use the `AssertionRequest::toVerify()` form request. It will automatically create an assertion for the user that matches the credentials, or a blank one in case you're using [userless login](#userlessone-touchtypeless-login). Otherwise, you may set stricter validation rules to always ask for credentials. First, use the `AssertionRequest::toVerify()` form request. It will automatically create an assertion for the user that matches the credentials, or a blank one in case you're using [userless login](#userlessone-touchtypeless-login). Otherwise, you may set stricter validation rules to always ask for credentials.
For example, we can use our own `AssertionController` to handle it.
```php ```php
// app\Http\Controllers\WebAuthn\AssertionController.php // app\Http\Controllers\WebAuthn\AssertionController.php
use Laragear\WebAuthn\Http\Requests\AssertionRequest; use Laragear\WebAuthn\Http\Requests\AssertionRequest;
@@ -573,10 +577,17 @@ return [
The _Relying Party_ is just a way to uniquely identify your application in the user device: The _Relying Party_ is just a way to uniquely identify your application in the user device:
* `name`: The name of the application. Defaults to the application name. * `name`: The name of the application. Defaults to the application name.
* `id`: An unique ID the application, like the site domain. If `null`, the device may fill it internally, usually as the full domain. * `id`: An unique ID the application, like the site URL. If `null`, the device _may_ fill it internally, usually as the full domain.
> WebAuthn authentication only work on the top domain it was registered. > WebAuthn authentication only work on the top domain it was registered.
Instead of modifying the config file, you should use the environment variables to set the name and ID for WebAuthn.
```dotenv
WEBAUTHN_NAME=SecureBank
WEBAUTHN_ID=https://auth.securebank.com
```
### Challenge configuration ### Challenge configuration
```php ```php

View File

@@ -24,7 +24,7 @@
"name": "Italo Israel Baeza Cabrera", "name": "Italo Israel Baeza Cabrera",
"email": "DarkGhostHunter@Gmail.com", "email": "DarkGhostHunter@Gmail.com",
"role": "Developer", "role": "Developer",
"homepage": "https://patreon.com/packagesforlaravel" "homepage": "https://github.com/sponsors/DarkGhostHunter"
} }
], ],
"support": { "support": {
@@ -32,22 +32,20 @@
"issues": "https://github.com/Laragear/TwoFactor/issues" "issues": "https://github.com/Laragear/TwoFactor/issues"
}, },
"require": { "require": {
"php": ">=8.0.2", "php": "8.*",
"ext-openssl": "*", "ext-openssl": "*",
"ext-json": "*", "ext-json": "*",
"illuminate/auth": "9.*", "illuminate/auth": "9.*|10.*",
"illuminate/http": "9.*", "illuminate/http": "9.*|10.*",
"illuminate/session": "9.*", "illuminate/session": "9.*|10.*",
"illuminate/support": "9.*", "illuminate/support": "9.*|10.*",
"illuminate/config": "9.*", "illuminate/config": "9.*|10.*",
"illuminate/database": "9.*", "illuminate/database": "9.*|10.*",
"illuminate/encryption": "9.*" "illuminate/encryption": "9.*|10.*"
}, },
"require-dev": { "require-dev": {
"orchestra/testbench": "7.*", "orchestra/testbench": "^7.22|8.*",
"phpunit/phpunit": "^9.5", "jetbrains/phpstorm-attributes": "*"
"mockery/mockery": "^1.5",
"jetbrains/phpstorm-attributes": "^1.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@@ -76,16 +74,8 @@
}, },
"funding": [ "funding": [
{ {
"type": "Patreon", "type": "Github Sponsorship",
"url": "https://patreon.com/PackagesForLaravel" "url": "https://github.com/sponsors/DarkGhostHunter"
},
{
"type": "Ko-Fi",
"url": "https://ko-fi.com/DarkGhostHunter"
},
{
"type": "Buy me a cofee",
"url": "https://www.buymeacoffee.com/darkghosthunter"
}, },
{ {
"type": "Paypal", "type": "Paypal",

View File

@@ -43,7 +43,7 @@ return new class extends Migration {
*/ */
protected static function defaultBlueprint(Blueprint $table): void protected static function defaultBlueprint(Blueprint $table): void
{ {
$table->string('id')->primary(); $table->string('id', 510)->primary();
$table->morphs('authenticatable', 'webauthn_user_index'); $table->morphs('authenticatable', 'webauthn_user_index');

View File

@@ -1,30 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache">
backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" <coverage>
convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" <include>
stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"> <directory suffix=".php">src/</directory>
<coverage> <directory suffix=".php">stubs/controllers</directory>
<include> </include>
<directory suffix=".php">src/</directory> <report>
</include> <clover outputFile="build/logs/clover.xml"/>
<report> </report>
<clover outputFile="build/logs/clover.xml"/> </coverage>
</report> <testsuites>
</coverage> <testsuite name="Test Suite">
<testsuites> <directory>tests</directory>
<testsuite name="Test Suite"> </testsuite>
<directory>tests</directory> </testsuites>
<file>stubs/controllers/WebAuthnLoginController.php</file> <php>
<file>stubs/controllers/WebAuthnRegisterController.php</file> <includePath>stubs/controllers</includePath>
</testsuite> <env name="APP_ENV" value="testing"/>
</testsuites> <env name="APP_DEBUG" value="true"/>
<logging> <env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<junit outputFile="build/report.junit.xml"/> <env name="DB_CONNECTION" value="testing"/>
</logging> </php>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_DEBUG" value="true"/>
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
<env name="DB_CONNECTION" value="testing"/>
</php>
</phpunit> </phpunit>

View File

@@ -156,7 +156,9 @@ class WebAuthn {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
#fetch(data, route, headers = {}) { #fetch(data, route, headers = {}) {
return fetch(route, { const url = new URL(route, window.location.origin).href;
return fetch(url, {
method: "POST", method: "POST",
credentials: this.#includeCredentials ? "include" : "same-origin", credentials: this.#includeCredentials ? "include" : "same-origin",
redirect: "error", redirect: "error",
@@ -313,6 +315,7 @@ class WebAuthn {
const publicKeyCredential = this.#parseOutgoingCredentials(credentials); const publicKeyCredential = this.#parseOutgoingCredentials(credentials);
Object.assign(publicKeyCredential, response); Object.assign(publicKeyCredential, response);
Object.assign(publicKeyCredential, request);
return await this.#fetch(publicKeyCredential, this.#routes.register).then(WebAuthn.#handleResponse); return await this.#fetch(publicKeyCredential, this.#routes.register).then(WebAuthn.#handleResponse);
} }

View File

@@ -95,7 +95,7 @@ class WebAuthnCredential extends Model
* *
* @var array<int, string> * @var array<int, string>
*/ */
protected $visible = ['id', 'origin', 'alias', 'aaguid', 'attestation_format', 'disabled_at', 'is_enabled']; protected $visible = ['id', 'origin', 'alias', 'aaguid', 'attestation_format', 'disabled_at'];
/** /**
* @phpstan-ignore-next-line * @phpstan-ignore-next-line

View File

@@ -4,9 +4,9 @@ namespace Laragear\WebAuthn;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\Date;
use JetBrains\PhpStorm\ArrayShape; use JetBrains\PhpStorm\ArrayShape;
use Laragear\WebAuthn\Models\WebAuthnCredential; use Laragear\WebAuthn\Models\WebAuthnCredential;
use function in_array;
/** /**
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Laragear\WebAuthn\Models\WebAuthnCredential> $webAuthnCredentials * @property-read \Illuminate\Database\Eloquent\Collection<int, \Laragear\WebAuthn\Models\WebAuthnCredential> $webAuthnCredentials
@@ -38,20 +38,17 @@ trait WebAuthnAuthentication
*/ */
public function flushCredentials(string ...$except): void public function flushCredentials(string ...$except): void
{ {
if ($this->relationLoaded('webAuthnCredentials') && $this->webAuthnCredentials instanceof Collection) { if (! $this->relationLoaded('webAuthnCredentials')) {
$partitioned = $this->webAuthnCredentials $this->webAuthnCredentials()->whereKeyNot($except)->delete();
->partition(static function (WebAuthnCredential $credential) use ($except): bool {
return in_array($credential->getKey(), $except, true);
});
$partitioned->first()->each->delete();
$this->setRelation('webAuthnCredentials', $partitioned->last());
return; return;
} }
$this->webAuthnCredentials()->whereKeyNot($except)->delete(); if ($this->webAuthnCredentials instanceof Collection && $this->webAuthnCredentials->isNotEmpty()) {
$this->webAuthnCredentials->whereNotIn('id', $except)->each->delete();
$this->setRelation('webAuthnCredentials', $this->webAuthnCredentials->whereIn('id', $except));
}
} }
/** /**
@@ -64,13 +61,14 @@ trait WebAuthnAuthentication
{ {
if ($this->relationLoaded('webAuthnCredentials') && $this->webAuthnCredentials instanceof Collection) { if ($this->relationLoaded('webAuthnCredentials') && $this->webAuthnCredentials instanceof Collection) {
$this->webAuthnCredentials $this->webAuthnCredentials
->each(static function (WebAuthnCredential $credential) use ($except): bool { ->when($except)->whereNotIn('id', $except)
if ($credential->isEnabled() && in_array($credential->getKey(), $except, true)) { ->each(static function (WebAuthnCredential $credential): void {
if ($credential->isEnabled()) {
$credential->disable(); $credential->disable();
} }
}); });
} else { } else {
$this->webAuthnCredentials()->whereKeyNot($except)->update(['is_enabled' => false]); $this->webAuthnCredentials()->whereKeyNot($except)->whereEnabled()->update(['disabled_at' => Date::now()]);
} }
} }

View File

@@ -6,8 +6,8 @@ use JetBrains\PhpStorm\ArrayShape;
class FakeAuthenticator class FakeAuthenticator
{ {
public const CREDENTIAL_ID = '-VOLFKPY-_FuMI_sJ7gMllK76L3VoRUINj6lL_Z3qDg'; public const CREDENTIAL_ID = 'owBYu_waGLhAOCg4EFzi6Lr55x51G2dR5yhJi8q2C3tgZQQL2aEi-nK3I54J6ILj70pJzR_6QxvA5XER17d7NA9EFe2QH3VoJYQGpO8G5yDoFQvsdkxNhioyMyhyQHNrAgTMGyfigIMCfhjk9te7LNYl9K5GbWRc4TGeQl1vROjBtTNm3GdpEOqp9RijWd-ShQZ95eHoc8SA_-8vzCyfmy-wI_K4ZqlQNNl85Fzg2GIBcC2zvcJhLYy1A2kw6JoBTAmz1ZCCgkTKWhzUvAJQpMpu40M67FqE0WkGZfSJ9A';
public const CREDENTIAL_ID_RAW = '+VOLFKPY+/FuMI/sJ7gMllK76L3VoRUINj6lL/Z3qDg='; public const CREDENTIAL_ID_RAW = 'owBYu/waGLhAOCg4EFzi6Lr55x51G2dR5yhJi8q2C3tgZQQL2aEi+nK3I54J6ILj70pJzR/6QxvA5XER17d7NA9EFe2QH3VoJYQGpO8G5yDoFQvsdkxNhioyMyhyQHNrAgTMGyfigIMCfhjk9te7LNYl9K5GbWRc4TGeQl1vROjBtTNm3GdpEOqp9RijWd+ShQZ95eHoc8SA/+8vzCyfmy+wI/K4ZqlQNNl85Fzg2GIBcC2zvcJhLYy1A2kw6JoBTAmz1ZCCgkTKWhzUvAJQpMpu40M67FqE0WkGZfSJ9A=';
public const ATTESTATION_USER = [ public const ATTESTATION_USER = [
'id' => 'e8af6f703f8042aa91c30cf72289aa07', 'id' => 'e8af6f703f8042aa91c30cf72289aa07',

View File

@@ -3,6 +3,7 @@
namespace Tests; namespace Tests;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Fluent; use Illuminate\Support\Fluent;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@@ -28,19 +29,25 @@ class ServiceProviderTest extends TestCase
); );
} }
/**
* @define-env usesCustomTestTime
*/
public function test_publishes_migrations(): void public function test_publishes_migrations(): void
{ {
$format = now()->format('Y_m_d_His');
static::assertSame( static::assertSame(
[ [
realpath(WebAuthnServiceProvider::MIGRATIONS . '/2022_07_01_000000_create_webauthn_credentials.php') => realpath(WebAuthnServiceProvider::MIGRATIONS . '/2022_07_01_000000_create_webauthn_credentials.php') =>
$this->app->databasePath("migrations/{$format}_create_webauthn_credentials.php"), $this->app->databasePath("migrations/2020_01_01_163025_create_webauthn_credentials.php"),
], ],
ServiceProvider::pathsToPublish(WebAuthnServiceProvider::class, 'migrations') ServiceProvider::pathsToPublish(WebAuthnServiceProvider::class, 'migrations')
); );
} }
protected function usesCustomTestTime()
{
$this->travelTo(Carbon::create(2020, 01, 01, 16, 30, 25));
}
public function test_bounds_user(): void public function test_bounds_user(): void
{ {
static::assertNull($this->app->make(WebAuthnAuthenticatable::class)); static::assertNull($this->app->make(WebAuthnAuthenticatable::class));

View File

@@ -0,0 +1,260 @@
<?php
namespace Tests;
use Illuminate\Support\Carbon;
use Laragear\WebAuthn\Models\WebAuthnCredential;
use Ramsey\Uuid\Uuid;
use function now;
class WebAuthnAuthenticationTest extends TestCase
{
protected Stubs\WebAuthnAuthenticatableUser $user;
protected function afterRefreshingDatabase(): void
{
$this->user = Stubs\WebAuthnAuthenticatableUser::forceCreate([
'name' => FakeAuthenticator::ATTESTATION_USER['displayName'],
'email' => FakeAuthenticator::ATTESTATION_USER['name'],
'password' => 'test_password',
]);
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id',
'user_id' => Uuid::NIL,
'counter' => 0,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
])->save();
}
public function test_shows_webauthn_data(): void
{
static::assertSame([
'name' => FakeAuthenticator::ATTESTATION_USER['name'],
'displayName' => FakeAuthenticator::ATTESTATION_USER['displayName'],
], $this->user->webAuthnData());
}
public function test_flushes_all_credentials(): void
{
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
'disabled_at' => now()
])->save();
$this->user->flushCredentials();
$this->assertDatabaseCount(WebAuthnCredential::class, 0);
}
public function test_flushes_all_credentials_using_loaded_relation(): void
{
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
'disabled_at' => now()
])->save();
$this->user->load('webAuthnCredentials');
static::assertCount(2, $this->user->webAuthnCredentials);
$this->user->flushCredentials();
static::assertEmpty($this->user->webAuthnCredentials);
$this->assertDatabaseCount(WebAuthnCredential::class, 0);
}
public function test_flushes_all_credentials_except_given_id(): void
{
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
'disabled_at' => now()
])->save();
$this->user->flushCredentials('test_id_2');
$this->assertDatabaseCount(WebAuthnCredential::class, 1);
$this->assertDatabaseMissing(WebAuthnCredential::class, [
'id' => 'test_id'
]);
}
public function test_flushes_all_credentials_using_loaded_relation_except_given_id(): void
{
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
'disabled_at' => now()
])->save();
$this->user->load('webAuthnCredentials');
static::assertCount(2, $this->user->webAuthnCredentials);
$this->user->flushCredentials('test_id_2');
static::assertCount(1, $this->user->webAuthnCredentials);
static::assertTrue($this->user->webAuthnCredentials->contains('id', 'test_id_2'));
$this->assertDatabaseCount(WebAuthnCredential::class, 1);
$this->assertDatabaseMissing(WebAuthnCredential::class, [
'id' => 'test_id'
]);
}
public function test_disables_all_credentials(): void
{
$this->travelTo(Carbon::now()->startOfSecond());
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
'disabled_at' => now()->subMinute()
])->save();
$this->user->disableAllCredentials();
$this->assertDatabaseCount(WebAuthnCredential::class, 2);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id',
'disabled_at' => now()->toDateTimeString(),
]);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id_2',
'disabled_at' => now()->subMinute()->toDateTimeString(),
]);
}
public function test_disables_all_credentials_with_loaded_relation(): void
{
$this->travelTo(Carbon::now()->startOfSecond());
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
'disabled_at' => now()->subMinute()
])->save();
$this->user->load('webAuthnCredentials');
$this->user->disableAllCredentials();
static::assertTrue($this->user->webAuthnCredentials->firstWhere('id', 'test_id')->isDisabled());
static::assertTrue($this->user->webAuthnCredentials->firstWhere('id', 'test_id_2')->isDisabled());
$this->assertDatabaseCount(WebAuthnCredential::class, 2);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id',
'disabled_at' => now()->toDateTimeString(),
]);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id_2',
'disabled_at' => now()->subMinute()->toDateTimeString(),
]);
}
public function test_disables_all_credentials_except_one(): void
{
$this->travelTo(Carbon::now()->startOfSecond());
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
])->save();
$this->user->disableAllCredentials('test_id');
$this->assertDatabaseCount(WebAuthnCredential::class, 2);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id',
'disabled_at' => null,
]);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id_2',
'disabled_at' => now()->toDateTimeString(),
]);
}
public function test_disables_all_credentials_with_loaded_relation_except_one(): void
{
$this->travelTo(Carbon::now()->startOfSecond());
$this->user->webAuthnCredentials()->make()->forceFill([
'id' => 'test_id_2',
'user_id' => Uuid::NIL,
'counter' => 10,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost:8000',
'aaguid' => Uuid::NIL,
'public_key' => 'test_key',
'attestation_format' => 'none',
])->save();
$this->user->load('webAuthnCredentials');
$this->user->disableAllCredentials('test_id_2');
static::assertTrue($this->user->webAuthnCredentials->firstWhere('id', 'test_id')->isDisabled());
static::assertFalse($this->user->webAuthnCredentials->firstWhere('id', 'test_id_2')->isDisabled());
$this->assertDatabaseCount(WebAuthnCredential::class, 2);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id',
'disabled_at' => now()->toDateTimeString(),
]);
$this->assertDatabaseHas(WebAuthnCredential::class, [
'id' => 'test_id_2',
'disabled_at' => null,
]);
}
}