Admin can now hopefully add a passkey to their account

This commit is contained in:
Jonny Barnes 2023-09-25 18:31:38 +01:00
parent cadd58187a
commit 2fb8339d91
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
16 changed files with 351 additions and 40 deletions

View file

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Passkey;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class PasskeysController extends Controller
{
public function index(): View
{
$user = auth()->user();
$passkeys = $user->passkey;
return view('admin.passkeys.index', compact('passkeys'));
}
public function save(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'id' => 'required|string|unique:App\Models\Passkey,passkey_id',
'public_key' => 'required|file',
'transports' => 'required|json',
'challenge' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Passkey could not be saved (validation failed)',
]);
}
$validated = $validator->validated();
if (
!session()->has('challenge') ||
$validated['challenge'] !== session('challenge')
) {
return response()->json([
'success' => false,
'message' => 'Passkey could not be saved (challenge failed)',
]);
}
$passkey = new Passkey();
$passkey->passkey_id = $validated['id'];
$passkey->passkey = $validated['public_key']->get();
$passkey->transports = json_decode($validated['transports'], true, 512, JSON_THROW_ON_ERROR);
$passkey->user_id = auth()->user()->id;
$passkey->save();
return response()->json([
'success' => true,
'message' => 'Passkey saved successfully',
]);
}
public function init(): JsonResponse
{
/** @var User $user */
$user = auth()->user();
$passkeys = $user->passkey()->get();
$existing = $passkeys->map(function (Passkey $passkey) {
return [
'id' => $passkey->passkey_id,
'transports' => $passkey->transports,
'type' => 'public-key',
];
})->all();
$challenge = Hash::make(random_bytes(32));
session(['challenge' => $challenge]);
return response()->json([
'challenge' => $challenge,
'userId' => $user->name,
'existing' => $existing,
]);
}
}

42
app/Models/Passkey.php Normal file
View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Passkey extends Model
{
use HasFactory;
/**
* Save and access the passkey appropriately.
*/
protected function passkey(): Attribute
{
return Attribute::make(
get: static fn ($value) => stream_get_contents($value),
set: static fn ($value) => pg_escape_bytea($value),
);
}
/**
* Save and access the transports appropriately.
*/
protected function transports(): Attribute
{
return Attribute::make(
get: static fn ($value) => json_decode($value, true, 512, JSON_THROW_ON_ERROR),
set: static fn ($value) => json_encode($value, JSON_THROW_ON_ERROR),
);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@ -24,4 +25,9 @@ class User extends Authenticatable
'password',
'remember_token',
];
public function passkey(): HasMany
{
return $this->hasMany(Passkey::class);
}
}