Get basic storage of webauthn tokens working
This commit is contained in:
parent
eaf12aec60
commit
1053b5d605
|
@ -1,74 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Client;
|
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Carbon\CarbonImmutable;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Contracts\Cache\Repository;
|
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Account\RegisterWebauthnTokenRequest;
|
|
||||||
use Pterodactyl\Services\Users\HardwareSecurityKeys\CreatePublicKeyCredentialsService;
|
|
||||||
|
|
||||||
class HardwareTokenController extends ClientApiController
|
|
||||||
{
|
|
||||||
private CreatePublicKeyCredentialsService $createPublicKeyCredentials;
|
|
||||||
|
|
||||||
private Repository $cache;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
Repository $cache,
|
|
||||||
CreatePublicKeyCredentialsService $createPublicKeyCredentials
|
|
||||||
) {
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->cache = $cache;
|
|
||||||
$this->createPublicKeyCredentials = $createPublicKeyCredentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all of the hardware security keys (WebAuthn) that exists for a user.
|
|
||||||
*/
|
|
||||||
public function index(Request $request): array
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data necessary for creating a new hardware security key for the
|
|
||||||
* user.
|
|
||||||
*/
|
|
||||||
public function create(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$tokenId = Str::random(64);
|
|
||||||
$credentials = $this->createPublicKeyCredentials->handle($request->user());
|
|
||||||
|
|
||||||
$this->cache->put("webauthn:$tokenId", [
|
|
||||||
'credentials' => $credentials->jsonSerialize(),
|
|
||||||
'user_entity' => $credentials->getUser()->jsonSerialize(),
|
|
||||||
], CarbonImmutable::now()->addMinutes(10));
|
|
||||||
|
|
||||||
return new JsonResponse([
|
|
||||||
'data' => [
|
|
||||||
'token_id' => $tokenId,
|
|
||||||
'credentials' => $credentials->jsonSerialize(),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores a new key for a user account.
|
|
||||||
*/
|
|
||||||
public function store(RegisterWebauthnTokenRequest $request): JsonResponse
|
|
||||||
{
|
|
||||||
return new JsonResponse([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a WebAuthn key from a user's account.
|
|
||||||
*/
|
|
||||||
public function delete(Request $request, int $webauthnKeyId): JsonResponse
|
|
||||||
{
|
|
||||||
return new JsonResponse([]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Client;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||||
|
use Illuminate\Contracts\Cache\Repository;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
|
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||||
|
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
|
||||||
|
use Pterodactyl\Repositories\SecurityKeys\WebauthnServerRepository;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\Account\RegisterWebauthnTokenRequest;
|
||||||
|
use Pterodactyl\Repositories\SecurityKeys\PublicKeyCredentialSourceRepository;
|
||||||
|
use Pterodactyl\Services\Users\SecurityKeys\CreatePublicKeyCredentialsService;
|
||||||
|
|
||||||
|
class SecurityKeyController extends ClientApiController
|
||||||
|
{
|
||||||
|
protected CreatePublicKeyCredentialsService $createPublicKeyCredentials;
|
||||||
|
|
||||||
|
protected Repository $cache;
|
||||||
|
|
||||||
|
protected WebauthnServerRepository $webauthnServerRepository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Repository $cache,
|
||||||
|
WebauthnServerRepository $webauthnServerRepository,
|
||||||
|
CreatePublicKeyCredentialsService $createPublicKeyCredentials
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->cache = $cache;
|
||||||
|
$this->webauthnServerRepository = $webauthnServerRepository;
|
||||||
|
$this->createPublicKeyCredentials = $createPublicKeyCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all of the hardware security keys (WebAuthn) that exists for a user.
|
||||||
|
*/
|
||||||
|
public function index(Request $request): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data necessary for creating a new hardware security key for the
|
||||||
|
* user.
|
||||||
|
*/
|
||||||
|
public function create(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$tokenId = Str::random(64);
|
||||||
|
$credentials = $this->createPublicKeyCredentials->handle($request->user(), $request->get('display_name'));
|
||||||
|
|
||||||
|
$this->cache->put(
|
||||||
|
"register-security-key:$tokenId",
|
||||||
|
serialize($credentials),
|
||||||
|
CarbonImmutable::now()->addMinutes(10)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => [
|
||||||
|
'token_id' => $tokenId,
|
||||||
|
'credentials' => $credentials->jsonSerialize(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a new key for a user account.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||||
|
*/
|
||||||
|
public function store(RegisterWebauthnTokenRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$stored = $this->cache->pull("register-security-key:{$request->input('token_id')}");
|
||||||
|
|
||||||
|
if (!$stored) {
|
||||||
|
throw new DisplayException('Could not register security key: no data present in session, please try your request again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$credentials = unserialize($stored);
|
||||||
|
if (!$credentials instanceof PublicKeyCredentialCreationOptions) {
|
||||||
|
throw new Exception(sprintf('Unexpected security key data pulled from cache: expected "%s" but got "%s".', PublicKeyCredentialCreationOptions::class, get_class($credentials)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = $this->webauthnServerRepository->getServer($request->user());
|
||||||
|
|
||||||
|
$source = $server->loadAndCheckAttestationResponse(
|
||||||
|
json_encode($request->input('registration')),
|
||||||
|
$credentials,
|
||||||
|
$this->getServerRequest($request),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unfortunately this repository interface doesn't define a response — it is explicitly
|
||||||
|
// void — so we need to just query the database immediately after this to pull the information
|
||||||
|
// we just stored to return to the caller.
|
||||||
|
PublicKeyCredentialSourceRepository::factory($request->user())->saveCredentialSource($source);
|
||||||
|
|
||||||
|
$created = $request->user()->securityKeys()
|
||||||
|
->where('public_key_id', base64_encode($source->getPublicKeyCredentialId()))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$created->update(['name' => $request->input('name')]);
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => $created->toArray(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a WebAuthn key from a user's account.
|
||||||
|
*/
|
||||||
|
public function delete(Request $request, int $webauthnKeyId): JsonResponse
|
||||||
|
{
|
||||||
|
return new JsonResponse([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getServerRequest(Request $request): ServerRequestInterface
|
||||||
|
{
|
||||||
|
$factory = new Psr17Factory();
|
||||||
|
|
||||||
|
$httpFactory = new PsrHttpFactory($factory, $factory, $factory, $factory);
|
||||||
|
|
||||||
|
return $httpFactory->createRequest($request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,8 +10,12 @@ class RegisterWebauthnTokenRequest extends AccountApiRequest
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => ['string', 'required'],
|
'name' => ['string', 'required'],
|
||||||
'register' => ['string', 'required'],
|
'token_id' => ['required', 'string'],
|
||||||
'public_key' => ['string', 'required'],
|
'registration' => ['required', 'array'],
|
||||||
|
'registration.id' => ['required', 'string'],
|
||||||
|
'registration.type' => ['required', 'in:public-key'],
|
||||||
|
'registration.response.attestationObject' => ['required', 'string'],
|
||||||
|
'registration.response.clientDataJSON' => ['required', 'string'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,24 @@ use Webauthn\PublicKeyCredentialSource;
|
||||||
use Webauthn\PublicKeyCredentialDescriptor;
|
use Webauthn\PublicKeyCredentialDescriptor;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
class HardwareSecurityKey extends Model
|
class SecurityKey extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
public const RESOURCE_NAME = 'hardware_security_key';
|
public const RESOURCE_NAME = 'security_key';
|
||||||
|
|
||||||
protected $attributes = [
|
protected $casts = [
|
||||||
'user_id' => 'int',
|
'user_id' => 'int',
|
||||||
'transports' => 'array',
|
'transports' => 'array',
|
||||||
'trust_path' => 'array',
|
'trust_path' => 'array',
|
||||||
'other_ui' => 'array',
|
'other_ui' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $guarded = [
|
||||||
|
'uuid',
|
||||||
|
'user_id',
|
||||||
|
];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
|
@ -210,9 +210,9 @@ class User extends Model implements
|
||||||
return $this->hasMany(RecoveryToken::class);
|
return $this->hasMany(RecoveryToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hardwareSecurityKeys(): HasMany
|
public function securityKeys(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(HardwareSecurityKey::class);
|
return $this->hasMany(SecurityKey::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Pterodactyl\Repositories\Webauthn;
|
namespace Pterodactyl\Repositories\SecurityKeys;
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
use Pterodactyl\Models\User;
|
use Pterodactyl\Models\User;
|
||||||
use Illuminate\Container\Container;
|
use Illuminate\Container\Container;
|
||||||
use Webauthn\PublicKeyCredentialSource;
|
use Webauthn\PublicKeyCredentialSource;
|
||||||
use Webauthn\PublicKeyCredentialUserEntity;
|
use Webauthn\PublicKeyCredentialUserEntity;
|
||||||
use Pterodactyl\Models\HardwareSecurityKey;
|
use Pterodactyl\Models\SecurityKey;
|
||||||
use Webauthn\PublicKeyCredentialSourceRepository as PublicKeyRepositoryInterface;
|
use Webauthn\PublicKeyCredentialSourceRepository as PublicKeyRepositoryInterface;
|
||||||
|
|
||||||
class PublicKeyCredentialSourceRepository implements PublicKeyRepositoryInterface
|
class PublicKeyCredentialSourceRepository implements PublicKeyRepositoryInterface
|
||||||
|
@ -23,8 +24,8 @@ class PublicKeyCredentialSourceRepository implements PublicKeyRepositoryInterfac
|
||||||
*/
|
*/
|
||||||
public function findOneByCredentialId(string $id): ?PublicKeyCredentialSource
|
public function findOneByCredentialId(string $id): ?PublicKeyCredentialSource
|
||||||
{
|
{
|
||||||
/** @var \Pterodactyl\Models\HardwareSecurityKey $key */
|
/** @var \Pterodactyl\Models\SecurityKey $key */
|
||||||
$key = $this->user->hardwareSecurityKeys()
|
$key = $this->user->securityKeys()
|
||||||
->where('public_key_id', $id)
|
->where('public_key_id', $id)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
@ -37,21 +38,36 @@ class PublicKeyCredentialSourceRepository implements PublicKeyRepositoryInterfac
|
||||||
*/
|
*/
|
||||||
public function findAllForUserEntity(PublicKeyCredentialUserEntity $entity): array
|
public function findAllForUserEntity(PublicKeyCredentialUserEntity $entity): array
|
||||||
{
|
{
|
||||||
$results = $this->user->hardwareSecurityKeys()
|
$results = $this->user->securityKeys()
|
||||||
->where('user_handle', $entity->getId())
|
->where('user_handle', $entity->getId())
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return $results->map(function (HardwareSecurityKey $key) {
|
return $results->map(function (SecurityKey $key) {
|
||||||
return $key->toCredentialSource();
|
return $key->toCredentialSource();
|
||||||
})->values()->toArray();
|
})->values()->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a credential to the database and link it with the user.
|
* Save a credential to the database and link it with the user.
|
||||||
|
*
|
||||||
|
* @throws \Throwable
|
||||||
*/
|
*/
|
||||||
public function saveCredentialSource(PublicKeyCredentialSource $source): void
|
public function saveCredentialSource(PublicKeyCredentialSource $source): void
|
||||||
{
|
{
|
||||||
// todo: implement
|
$this->user->securityKeys()->forceCreate([
|
||||||
|
'uuid' => Uuid::uuid4()->toString(),
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'public_key_id' => base64_encode($source->getPublicKeyCredentialId()),
|
||||||
|
'public_key' => base64_encode($source->getCredentialPublicKey()),
|
||||||
|
'aaguid' => $source->getAaguid()->toString(),
|
||||||
|
'type' => $source->getType(),
|
||||||
|
'transports' => $source->getTransports(),
|
||||||
|
'attestation_type' => $source->getAttestationType(),
|
||||||
|
'trust_path' => $source->getTrustPath()->jsonSerialize(),
|
||||||
|
'user_handle' => $source->getUserHandle(),
|
||||||
|
'counter' => $source->getCounter(),
|
||||||
|
'other_ui' => $source->getOtherUI(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Repositories\SecurityKeys;
|
||||||
|
|
||||||
|
use Webauthn\Server;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
use Illuminate\Container\Container;
|
||||||
|
use Webauthn\PublicKeyCredentialRpEntity;
|
||||||
|
|
||||||
|
final class WebauthnServerRepository
|
||||||
|
{
|
||||||
|
private PublicKeyCredentialRpEntity $rpEntity;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$url = str_replace(['http://', 'https://'], '', config('app.url'));
|
||||||
|
|
||||||
|
$this->rpEntity = new PublicKeyCredentialRpEntity(config('app.name'), trim($url, '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getServer(User $user)
|
||||||
|
{
|
||||||
|
return new Server(
|
||||||
|
$this->rpEntity,
|
||||||
|
PublicKeyCredentialSourceRepository::factory($user)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Services\Users\HardwareSecurityKeys;
|
|
||||||
|
|
||||||
use Webauthn\Server;
|
|
||||||
use Ramsey\Uuid\Uuid;
|
|
||||||
use Pterodactyl\Models\User;
|
|
||||||
use Webauthn\PublicKeyCredentialRpEntity;
|
|
||||||
use Pterodactyl\Models\HardwareSecurityKey;
|
|
||||||
use Webauthn\PublicKeyCredentialUserEntity;
|
|
||||||
use Webauthn\PublicKeyCredentialCreationOptions;
|
|
||||||
use Pterodactyl\Repositories\Webauthn\PublicKeyCredentialSourceRepository;
|
|
||||||
|
|
||||||
class CreatePublicKeyCredentialsService
|
|
||||||
{
|
|
||||||
protected PublicKeyCredentialRpEntity $rpEntity;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$url = str_replace(['http://', 'https://'], '', config('app.url'));
|
|
||||||
|
|
||||||
$this->rpEntity = new PublicKeyCredentialRpEntity(config('app.name'), trim($url, '/'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(User $user): PublicKeyCredentialCreationOptions
|
|
||||||
{
|
|
||||||
$id = Uuid::uuid4()->toString();
|
|
||||||
|
|
||||||
$entity = new PublicKeyCredentialUserEntity($user->uuid, $id, $user->email);
|
|
||||||
|
|
||||||
$excluded = $user->hardwareSecurityKeys->map(function (HardwareSecurityKey $key) {
|
|
||||||
return $key->toCredentialsDescriptor();
|
|
||||||
})->values()->toArray();
|
|
||||||
|
|
||||||
return $this->getServerInstance($user)->generatePublicKeyCredentialCreationOptions(
|
|
||||||
$entity,
|
|
||||||
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
|
||||||
$excluded
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getServerInstance(User $user)
|
|
||||||
{
|
|
||||||
return new Server(
|
|
||||||
$this->rpEntity,
|
|
||||||
PublicKeyCredentialSourceRepository::factory($user)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Services\Users\SecurityKeys;
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
use Pterodactyl\Models\SecurityKey;
|
||||||
|
use Webauthn\PublicKeyCredentialUserEntity;
|
||||||
|
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||||
|
use Pterodactyl\Repositories\SecurityKeys\WebauthnServerRepository;
|
||||||
|
|
||||||
|
class CreatePublicKeyCredentialsService
|
||||||
|
{
|
||||||
|
protected WebauthnServerRepository $webauthnServerRepository;
|
||||||
|
|
||||||
|
public function __construct(WebauthnServerRepository $webauthnServerRepository)
|
||||||
|
{
|
||||||
|
$this->webauthnServerRepository = $webauthnServerRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(User $user, ?string $displayName): PublicKeyCredentialCreationOptions
|
||||||
|
{
|
||||||
|
$id = Uuid::uuid4()->toString();
|
||||||
|
|
||||||
|
$entity = new PublicKeyCredentialUserEntity($user->uuid, $id, $name ?? $user->email);
|
||||||
|
|
||||||
|
$excluded = $user->securityKeys->map(function (SecurityKey $key) {
|
||||||
|
return $key->toCredentialsDescriptor();
|
||||||
|
})->values()->toArray();
|
||||||
|
|
||||||
|
$server = $this->webauthnServerRepository->getServer($user);
|
||||||
|
|
||||||
|
return $server->generatePublicKeyCredentialCreationOptions(
|
||||||
|
$entity,
|
||||||
|
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
||||||
|
$excluded
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Transformers\Api\Client;
|
namespace Pterodactyl\Transformers\Api\Client;
|
||||||
|
|
||||||
use Pterodactyl\Models\HardwareSecurityKey;
|
use Pterodactyl\Models\SecurityKey;
|
||||||
use Pterodactyl\Transformers\Api\Transformer;
|
use Pterodactyl\Transformers\Api\Transformer;
|
||||||
|
|
||||||
class WebauthnKeyTransformer extends Transformer
|
class WebauthnKeyTransformer extends Transformer
|
||||||
|
@ -12,13 +12,13 @@ class WebauthnKeyTransformer extends Transformer
|
||||||
*/
|
*/
|
||||||
public function getResourceName(): string
|
public function getResourceName(): string
|
||||||
{
|
{
|
||||||
return HardwareSecurityKey::RESOURCE_NAME;
|
return SecurityKey::RESOURCE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return basic information about the currently logged in user.
|
* Return basic information about the currently logged in user.
|
||||||
*/
|
*/
|
||||||
public function transform(HardwareSecurityKey $model): array
|
public function transform(SecurityKey $model): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => $model->id,
|
'id' => $model->id,
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"league/flysystem-aws-s3-v3": "^1.0",
|
"league/flysystem-aws-s3-v3": "^1.0",
|
||||||
"league/flysystem-memory": "^1.0",
|
"league/flysystem-memory": "^1.0",
|
||||||
"matriphe/iso-639": "^1.2",
|
"matriphe/iso-639": "^1.2",
|
||||||
|
"nyholm/psr7": "^1.4",
|
||||||
"pragmarx/google2fa": "^8.0",
|
"pragmarx/google2fa": "^8.0",
|
||||||
"predis/predis": "^1.1",
|
"predis/predis": "^1.1",
|
||||||
"prologue/alerts": "^0.4",
|
"prologue/alerts": "^0.4",
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
"spatie/laravel-fractal": "^5.8",
|
"spatie/laravel-fractal": "^5.8",
|
||||||
"spatie/laravel-query-builder": "^3.5",
|
"spatie/laravel-query-builder": "^3.5",
|
||||||
"staudenmeir/belongs-to-through": "^2.11",
|
"staudenmeir/belongs-to-through": "^2.11",
|
||||||
|
"symfony/psr-http-message-bridge": "^2.1",
|
||||||
"symfony/yaml": "^5.3",
|
"symfony/yaml": "^5.3",
|
||||||
"web-auth/webauthn-lib": "^3.3",
|
"web-auth/webauthn-lib": "^3.3",
|
||||||
"webmozart/assert": "^1.10"
|
"webmozart/assert": "^1.10"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f6da942b0b0b32416bca002dff11f403",
|
"content-hash": "576d3b9784111373d9df4790023538d8",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-sdk-php",
|
"name": "aws/aws-sdk-php",
|
||||||
|
@ -3314,6 +3314,83 @@
|
||||||
},
|
},
|
||||||
"time": "2021-07-21T10:44:31+00:00"
|
"time": "2021-07-21T10:44:31+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "nyholm/psr7",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Nyholm/psr7.git",
|
||||||
|
"reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/2212385b47153ea71b1c1b1374f8cb5e4f7892ec",
|
||||||
|
"reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1",
|
||||||
|
"php-http/message-factory": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/http-factory-implementation": "1.0",
|
||||||
|
"psr/http-message-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"http-interop/http-factory-tests": "^0.9",
|
||||||
|
"php-http/psr7-integration-tests": "^1.0",
|
||||||
|
"phpunit/phpunit": "^7.5 || 8.5 || 9.4",
|
||||||
|
"symfony/error-handler": "^4.4"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.4-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nyholm\\Psr7\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Martijn van der Ven",
|
||||||
|
"email": "martijn@vanderven.se"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A fast PHP7 implementation of PSR-7",
|
||||||
|
"homepage": "https://tnyholm.se",
|
||||||
|
"keywords": [
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Nyholm/psr7/issues",
|
||||||
|
"source": "https://github.com/Nyholm/psr7/tree/1.4.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Zegnat",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nyholm",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-07-02T08:32:20+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "opis/closure",
|
"name": "opis/closure",
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
|
@ -3446,6 +3523,60 @@
|
||||||
},
|
},
|
||||||
"time": "2020-12-06T15:14:20+00:00"
|
"time": "2020-12-06T15:14:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "php-http/message-factory",
|
||||||
|
"version": "v1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-http/message-factory.git",
|
||||||
|
"reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1",
|
||||||
|
"reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.4",
|
||||||
|
"psr/http-message": "^1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Http\\Message\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Márk Sági-Kazár",
|
||||||
|
"email": "mark.sagikazar@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Factory interfaces for PSR-7 HTTP Message",
|
||||||
|
"homepage": "http://php-http.org",
|
||||||
|
"keywords": [
|
||||||
|
"factory",
|
||||||
|
"http",
|
||||||
|
"message",
|
||||||
|
"stream",
|
||||||
|
"uri"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-http/message-factory/issues",
|
||||||
|
"source": "https://github.com/php-http/message-factory/tree/master"
|
||||||
|
},
|
||||||
|
"time": "2015-12-19T14:08:53+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.7.5",
|
"version": "1.7.5",
|
||||||
|
@ -6587,6 +6718,94 @@
|
||||||
],
|
],
|
||||||
"time": "2021-07-23T15:54:19+00:00"
|
"time": "2021-07-23T15:54:19+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/psr-http-message-bridge",
|
||||||
|
"version": "v2.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/psr-http-message-bridge.git",
|
||||||
|
"reference": "c9012994c4b4fb23e7c57dd86b763a417a04feba"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/c9012994c4b4fb23e7c57dd86b763a417a04feba",
|
||||||
|
"reference": "c9012994c4b4fb23e7c57dd86b763a417a04feba",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1",
|
||||||
|
"psr/http-message": "^1.0",
|
||||||
|
"symfony/http-foundation": "^4.4 || ^5.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"nyholm/psr7": "^1.1",
|
||||||
|
"psr/log": "^1.1 || ^2 || ^3",
|
||||||
|
"symfony/browser-kit": "^4.4 || ^5.0",
|
||||||
|
"symfony/config": "^4.4 || ^5.0",
|
||||||
|
"symfony/event-dispatcher": "^4.4 || ^5.0",
|
||||||
|
"symfony/framework-bundle": "^4.4 || ^5.0",
|
||||||
|
"symfony/http-kernel": "^4.4 || ^5.0",
|
||||||
|
"symfony/phpunit-bridge": "^4.4.19 || ^5.2"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
|
||||||
|
},
|
||||||
|
"type": "symfony-bridge",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "2.1-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Bridge\\PsrHttpMessage\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "http://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PSR HTTP message bridge",
|
||||||
|
"homepage": "http://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-message",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/symfony/psr-http-message-bridge/issues",
|
||||||
|
"source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-07-27T17:25:39+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/routing",
|
"name": "symfony/routing",
|
||||||
"version": "v5.3.4",
|
"version": "v5.3.4",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
use Pterodactyl\Models\HardwareSecurityKey;
|
use Pterodactyl\Models\SecurityKey;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
class WebauthnKeyFactory extends Factory
|
class WebauthnKeyFactory extends Factory
|
||||||
|
@ -10,7 +10,7 @@ class WebauthnKeyFactory extends Factory
|
||||||
/**
|
/**
|
||||||
* The name of the factory's corresponding model.
|
* The name of the factory's corresponding model.
|
||||||
*/
|
*/
|
||||||
protected $model = HardwareSecurityKey::class;
|
protected $model = SecurityKey::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the model's default state.
|
* Define the model's default state.
|
||||||
|
|
|
@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class CreateHardwareSecurityKeysTable extends Migration
|
class CreateSecurityKeysTable extends Migration
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
|
@ -13,10 +13,11 @@ class CreateHardwareSecurityKeysTable extends Migration
|
||||||
*/
|
*/
|
||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
Schema::create('hardware_security_keys', function (Blueprint $table) {
|
Schema::create('security_keys', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->char('uuid', 36);
|
$table->char('uuid', 36)->unique();
|
||||||
$table->unsignedInteger('user_id')->references('id')->on('users')->onDelete('cascade');
|
$table->unsignedInteger('user_id');
|
||||||
|
$table->string('name');
|
||||||
$table->text('public_key_id');
|
$table->text('public_key_id');
|
||||||
$table->text('public_key');
|
$table->text('public_key');
|
||||||
$table->char('aaguid', 36);
|
$table->char('aaguid', 36);
|
||||||
|
@ -26,8 +27,10 @@ class CreateHardwareSecurityKeysTable extends Migration
|
||||||
$table->json('trust_path');
|
$table->json('trust_path');
|
||||||
$table->text('user_handle');
|
$table->text('user_handle');
|
||||||
$table->unsignedInteger('counter');
|
$table->unsignedInteger('counter');
|
||||||
$table->json('other_ui');
|
$table->json('other_ui')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +41,6 @@ class CreateHardwareSecurityKeysTable extends Migration
|
||||||
*/
|
*/
|
||||||
public function down()
|
public function down()
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('hardware_security_keys');
|
Schema::dropIfExists('security_keys');
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ export const rawDataToWebauthnKey = (data: any): WebauthnKey => ({
|
||||||
|
|
||||||
export default (): Promise<WebauthnKey[]> => {
|
export default (): Promise<WebauthnKey[]> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
http.get('/api/client/account/webauthn')
|
http.get('/api/client/account/security-keys')
|
||||||
.then(({ data }) => resolve((data.data || []).map((d: any) => rawDataToWebauthnKey(d.attributes))))
|
.then(({ data }) => resolve((data.data || []).map((d: any) => rawDataToWebauthnKey(d.attributes))))
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import http from '@/api/http';
|
import http from '@/api/http';
|
||||||
import { rawDataToWebauthnKey, WebauthnKey } from '@/api/account/webauthn/getWebauthnKeys';
|
|
||||||
|
|
||||||
export const base64Decode = (input: string): string => {
|
export const base64Decode = (input: string): string => {
|
||||||
input = input.replace(/-/g, '+').replace(/_/g, '/');
|
input = input.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
@ -13,14 +12,10 @@ export const base64Decode = (input: string): string => {
|
||||||
return input;
|
return input;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bufferDecode = (value: string): ArrayBuffer => {
|
export const bufferDecode = (value: string): ArrayBuffer => Uint8Array.from(window.atob(value), c => c.charCodeAt(0));
|
||||||
return Uint8Array.from(window.atob(value), c => c.charCodeAt(0));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const bufferEncode = (value: ArrayBuffer): string => {
|
// @ts-ignore
|
||||||
// @ts-ignore
|
export const bufferEncode = (value: ArrayBuffer): string => window.btoa(String.fromCharCode.apply(null, new Uint8Array(value)));
|
||||||
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(value)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const decodeCredentials = (credentials: PublicKeyCredentialDescriptor[]) => {
|
export const decodeCredentials = (credentials: PublicKeyCredentialDescriptor[]) => {
|
||||||
return credentials.map(c => {
|
return credentials.map(c => {
|
||||||
|
@ -32,42 +27,44 @@ export const decodeCredentials = (credentials: PublicKeyCredentialDescriptor[])
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (name: string): Promise<WebauthnKey> => {
|
const registerCredentialForAccount = async (name: string, tokenId: string, credential: PublicKeyCredential) => {
|
||||||
return new Promise((resolve, reject) => {
|
const { data } = await http.post('/api/client/account/security-keys/register', {
|
||||||
http.get('/api/client/account/webauthn/register').then((res) => {
|
name,
|
||||||
const publicKey = res.data.public_key;
|
token_id: tokenId,
|
||||||
const publicKeyCredential = Object.assign({}, publicKey);
|
registration: {
|
||||||
|
id: credential.id,
|
||||||
publicKeyCredential.user.id = bufferDecode(publicKey.user.id);
|
type: credential.type,
|
||||||
publicKeyCredential.challenge = bufferDecode(base64Decode(publicKey.challenge));
|
rawId: bufferEncode(credential.rawId),
|
||||||
if (publicKey.excludeCredentials) {
|
response: {
|
||||||
publicKeyCredential.excludeCredentials = decodeCredentials(publicKey.excludeCredentials);
|
attestationObject: bufferEncode((credential.response as AuthenticatorAttestationResponse).attestationObject),
|
||||||
}
|
clientDataJSON: bufferEncode(credential.response.clientDataJSON),
|
||||||
|
},
|
||||||
return navigator.credentials.create({
|
},
|
||||||
publicKey: publicKeyCredential,
|
|
||||||
});
|
|
||||||
}).then((c) => {
|
|
||||||
if (c === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const credential = c as PublicKeyCredential;
|
|
||||||
const response = credential.response as AuthenticatorAttestationResponse;
|
|
||||||
|
|
||||||
http.post('/api/client/account/webauthn/register', {
|
|
||||||
name: name,
|
|
||||||
|
|
||||||
register: JSON.stringify({
|
|
||||||
id: credential.id,
|
|
||||||
type: credential.type,
|
|
||||||
rawId: bufferEncode(credential.rawId),
|
|
||||||
|
|
||||||
response: {
|
|
||||||
attestationObject: bufferEncode(response.attestationObject),
|
|
||||||
clientDataJSON: bufferEncode(response.clientDataJSON),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}).then(({ data }) => resolve(rawDataToWebauthnKey(data.attributes))).catch(reject);
|
|
||||||
}).catch(reject);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(data.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const register = async (name: string): Promise<void> => {
|
||||||
|
const { data } = await http.get('/api/client/account/security-keys/register', {
|
||||||
|
params: {
|
||||||
|
display_name: name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const publicKey = data.data.credentials;
|
||||||
|
publicKey.challenge = bufferDecode(base64Decode(publicKey.challenge));
|
||||||
|
publicKey.user.id = bufferDecode(publicKey.user.id);
|
||||||
|
|
||||||
|
if (publicKey.excludeCredentials) {
|
||||||
|
publicKey.excludeCredentials = decodeCredentials(publicKey.excludeCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = await navigator.credentials.create({ publicKey });
|
||||||
|
|
||||||
|
if (!credentials || credentials.type !== 'public-key') {
|
||||||
|
throw new Error(`Unexpected type returned by navigator.credentials.create(): expected "public-key", got "${credentials?.type}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await registerCredentialForAccount(name, data.data.token_id, credentials);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import deleteWebauthnKey from '@/api/account/webauthn/deleteWebauthnKey';
|
import deleteWebauthnKey from '@/api/account/webauthn/deleteWebauthnKey';
|
||||||
import getWebauthnKeys, { WebauthnKey } from '@/api/account/webauthn/getWebauthnKeys';
|
import getWebauthnKeys, { WebauthnKey } from '@/api/account/webauthn/getWebauthnKeys';
|
||||||
import registerWebauthnKey from '@/api/account/webauthn/registerWebauthnKey';
|
import { register } from '@/api/account/webauthn/registerWebauthnKey';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import Button from '@/components/elements/Button';
|
import Button from '@/components/elements/Button';
|
||||||
import ContentBox from '@/components/elements/ContentBox';
|
import ContentBox from '@/components/elements/ContentBox';
|
||||||
|
@ -28,14 +28,13 @@ const AddSecurityKeyForm = ({ onKeyAdded }: { onKeyAdded: (key: WebauthnKey) =>
|
||||||
const submit = ({ name }: Values, { setSubmitting, resetForm }: FormikHelpers<Values>) => {
|
const submit = ({ name }: Values, { setSubmitting, resetForm }: FormikHelpers<Values>) => {
|
||||||
clearFlashes('security_keys');
|
clearFlashes('security_keys');
|
||||||
|
|
||||||
registerWebauthnKey(name)
|
register(name)
|
||||||
.then(key => {
|
.then(() => {
|
||||||
resetForm();
|
resetForm();
|
||||||
onKeyAdded(key);
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(err => {
|
||||||
console.error(error);
|
console.error(err);
|
||||||
clearAndAddHttpError({ key: 'security_keys', error });
|
clearAndAddHttpError({ key: 'security_keys', error: err });
|
||||||
})
|
})
|
||||||
.then(() => setSubmitting(false));
|
.then(() => setSubmitting(false));
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,10 +30,10 @@ Route::group(['prefix' => '/account'], function () {
|
||||||
Route::post('/api-keys', [Client\ApiKeyController::class, 'store']);
|
Route::post('/api-keys', [Client\ApiKeyController::class, 'store']);
|
||||||
Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']);
|
Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']);
|
||||||
|
|
||||||
Route::get('/webauthn', [Client\HardwareTokenController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
Route::get('/security-keys', [Client\SecurityKeyController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||||
Route::get('/webauthn/register', [Client\HardwareTokenController::class, 'create'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
Route::get('/security-keys/register', [Client\SecurityKeyController::class, 'create'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||||
Route::post('/webauthn/register', [Client\HardwareTokenController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
Route::post('/security-keys/register', [Client\SecurityKeyController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||||
Route::delete('/webauthn/{id}', [Client\HardwareTokenController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
Route::delete('/security-keys/{securityKey}', [Client\SecurityKeyController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||||
|
|
||||||
Route::get('/ssh', 'SSHKeyController@index');
|
Route::get('/ssh', 'SSHKeyController@index');
|
||||||
Route::post('/ssh', 'SSHKeyController@store');
|
Route::post('/ssh', 'SSHKeyController@store');
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware;
|
||||||
|
|
||||||
use Mockery as m;
|
use Mockery as m;
|
||||||
use Pterodactyl\Models\User;
|
use Pterodactyl\Models\User;
|
||||||
use Pterodactyl\Models\HardwareSecurityKey;
|
use Pterodactyl\Models\SecurityKey;
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
use Prologue\Alerts\AlertsMessageBag;
|
||||||
use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException;
|
use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException;
|
||||||
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
|
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
|
||||||
|
@ -66,7 +66,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
|
|
||||||
/** @var \Pterodactyl\Models\User $user */
|
/** @var \Pterodactyl\Models\User $user */
|
||||||
$user = User::factory()
|
$user = User::factory()
|
||||||
->has(HardwareSecurityKey::factory()->count(1))
|
->has(SecurityKey::factory()->count(1))
|
||||||
->create(['use_totp' => false]);
|
->create(['use_totp' => false]);
|
||||||
$this->setRequestUserModel($user);
|
$this->setRequestUserModel($user);
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
|
|
||||||
/** @var \Pterodactyl\Models\User $user */
|
/** @var \Pterodactyl\Models\User $user */
|
||||||
$user = User::factory()
|
$user = User::factory()
|
||||||
->has(HardwareSecurityKey::factory()->count(1))
|
->has(SecurityKey::factory()->count(1))
|
||||||
->create(['use_totp' => false]);
|
->create(['use_totp' => false]);
|
||||||
$this->setRequestUserModel($user);
|
$this->setRequestUserModel($user);
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN);
|
config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN);
|
||||||
|
|
||||||
/** @var \Pterodactyl\Models\User $user */
|
/** @var \Pterodactyl\Models\User $user */
|
||||||
$user = User::factory()->has(HardwareSecurityKey::factory()->count(1))->create(['use_totp' => false]);
|
$user = User::factory()->has(SecurityKey::factory()->count(1))->create(['use_totp' => false]);
|
||||||
$this->setRequestUserModel($user);
|
$this->setRequestUserModel($user);
|
||||||
|
|
||||||
$this->assertFalse($user->use_totp);
|
$this->assertFalse($user->use_totp);
|
||||||
|
@ -278,7 +278,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
||||||
|
|
||||||
/** @var \Pterodactyl\Models\User $user */
|
/** @var \Pterodactyl\Models\User $user */
|
||||||
$user = User::factory()
|
$user = User::factory()
|
||||||
->has(HardwareSecurityKey::factory()->count(1))
|
->has(SecurityKey::factory()->count(1))
|
||||||
->create(['use_totp' => false, 'root_admin' => true]);
|
->create(['use_totp' => false, 'root_admin' => true]);
|
||||||
$this->setRequestUserModel($user);
|
$this->setRequestUserModel($user);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue