feat(webauthn): install web-auth/webauthn-lib + credential storage
Add web-auth/webauthn-lib ^5.3, the webauthn_credentials table (per-user unique credential_id), the WebauthnCredential model and User hasMany relation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>feat/v1-foundation
parent
eef79d1758
commit
78b1017844
|
|
@ -6,6 +6,7 @@ use Database\Factories\UserFactory;
|
|||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
|
@ -95,4 +96,14 @@ class User extends Authenticatable
|
|||
{
|
||||
return ! $this->must_change_password && $this->hasTwoFactorEnabled();
|
||||
}
|
||||
|
||||
public function webauthnCredentials(): HasMany
|
||||
{
|
||||
return $this->hasMany(WebauthnCredential::class);
|
||||
}
|
||||
|
||||
public function hasWebauthnCredentials(): bool
|
||||
{
|
||||
return $this->webauthnCredentials()->exists();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class WebauthnCredential extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'transports' => 'array',
|
||||
'sign_count' => 'integer',
|
||||
'last_used_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
"livewire/livewire": "^3.6",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
"pragmarx/google2fa-qrcode": "^4.0",
|
||||
"web-auth/webauthn-lib": "^5.3",
|
||||
"wire-elements/modal": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('webauthn_credentials', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('credential_id', 512); // base64url of the raw credential id (indexed)
|
||||
$table->text('public_key'); // serialized PublicKeyCredentialSource (JSON)
|
||||
$table->string('aaguid')->nullable();
|
||||
$table->json('transports')->nullable();
|
||||
$table->unsignedBigInteger('sign_count')->default(0);
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'credential_id'], 'webauthn_user_credential_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('webauthn_credentials');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\WebauthnCredential;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class WebauthnCredentialTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_user_has_webauthn_credentials(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->assertFalse($user->hasWebauthnCredentials());
|
||||
|
||||
WebauthnCredential::create([
|
||||
'user_id' => $user->id, 'name' => 'YubiKey 5C', 'credential_id' => 'abc',
|
||||
'public_key' => '{}', 'sign_count' => 0,
|
||||
]);
|
||||
|
||||
$this->assertTrue($user->fresh()->hasWebauthnCredentials());
|
||||
$this->assertCount(1, $user->fresh()->webauthnCredentials);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue