Clean out existing webauthn logic, implement base logic for base package
This commit is contained in:
parent
0103a0c31e
commit
eaf12aec60
19 changed files with 584 additions and 1541 deletions
74
app/Http/Controllers/Api/Client/HardwareTokenController.php
Normal file
74
app/Http/Controllers/Api/Client/HardwareTokenController.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?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([]);
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Client;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Models\WebauthnKey;
|
||||
use LaravelWebauthn\Facades\Webauthn;
|
||||
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Pterodactyl\Transformers\Api\Client\WebauthnKeyTransformer;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
class WebauthnController extends ClientApiController
|
||||
{
|
||||
private const SESSION_PUBLICKEY_CREATION = 'webauthn.publicKeyCreation';
|
||||
|
||||
/**
|
||||
* ?
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function index(Request $request): array
|
||||
{
|
||||
return $this->fractal->collection(WebauthnKey::query()->where('user_id', '=', $request->user()->id)->get())
|
||||
->transformWith(WebauthnKeyTransformer::class)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
public function register(Request $request): JsonResponse
|
||||
{
|
||||
if (!Webauthn::canRegister($request->user())) {
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'message' => trans('webauthn::errors.cannot_register_new_key'),
|
||||
],
|
||||
], JsonResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$publicKey = Webauthn::getRegisterData($request->user());
|
||||
|
||||
$request->session()->put(self::SESSION_PUBLICKEY_CREATION, $publicKey);
|
||||
$request->session()->save();
|
||||
|
||||
return new JsonResponse([
|
||||
'public_key' => $publicKey,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* ?
|
||||
*
|
||||
* @return array|JsonResponse
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
if (!Webauthn::canRegister($request->user())) {
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'message' => trans('webauthn::errors.cannot_register_new_key'),
|
||||
],
|
||||
], JsonResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
if ($request->input('register') === null) {
|
||||
throw new BadRequestHttpException('Missing register data in request body.');
|
||||
}
|
||||
|
||||
if ($request->input('name') === null) {
|
||||
throw new BadRequestHttpException('Missing name in request body.');
|
||||
}
|
||||
|
||||
try {
|
||||
$publicKey = $request->session()->pull(self::SESSION_PUBLICKEY_CREATION);
|
||||
if (!$publicKey instanceof PublicKeyCredentialCreationOptions) {
|
||||
throw new ModelNotFoundException(trans('webauthn::errors.create_data_not_found'));
|
||||
}
|
||||
|
||||
$webauthnKey = Webauthn::doRegister(
|
||||
$request->user(),
|
||||
$publicKey,
|
||||
$request->input('register'),
|
||||
$request->input('name'),
|
||||
);
|
||||
|
||||
|
||||
|
||||
return $this->fractal->item($webauthnKey)
|
||||
->transformWith(WebauthnKeyTransformer::class)
|
||||
->toArray();
|
||||
} catch (Exception $e) {
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'message' => $e->getMessage(),
|
||||
],
|
||||
], JsonResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
public function deleteKey(Request $request, int $webauthnKeyId): JsonResponse
|
||||
{
|
||||
try {
|
||||
WebauthnKey::query()
|
||||
->where('user_id', $request->user()->getAuthIdentifier())
|
||||
->findOrFail($webauthnKeyId)
|
||||
->delete();
|
||||
|
||||
return new JsonResponse([
|
||||
'deleted' => true,
|
||||
'id' => $webauthnKeyId,
|
||||
]);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'message' => trans('webauthn::errors.object_not_found'),
|
||||
],
|
||||
], JsonResponse::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use LaravelWebauthn\Facades\Webauthn;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||
|
@ -92,30 +91,7 @@ class LoginController extends AbstractLoginController
|
|||
return;
|
||||
}
|
||||
|
||||
$webauthnKeys = $user->webauthnKeys()->get();
|
||||
|
||||
if (count($webauthnKeys) > 0) {
|
||||
$token = Str::random(64);
|
||||
$this->cache->put($token, $user->id, CarbonImmutable::now()->addMinutes(5));
|
||||
|
||||
$publicKey = Webauthn::getAuthenticateData($user);
|
||||
$request->session()->put(self::SESSION_PUBLICKEY_REQUEST, $publicKey);
|
||||
$request->session()->save();
|
||||
|
||||
$methods = [self::METHOD_WEBAUTHN];
|
||||
if ($user->use_totp) {
|
||||
$methods[] = self::METHOD_TOTP;
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'complete' => false,
|
||||
'methods' => $methods,
|
||||
'confirmation_token' => $token,
|
||||
'webauthn' => [
|
||||
'public_key' => $publicKey,
|
||||
],
|
||||
]);
|
||||
} elseif ($user->use_totp) {
|
||||
if ($user->use_totp) {
|
||||
$token = Str::random(64);
|
||||
$this->cache->put($token, $user->id, CarbonImmutable::now()->addMinutes(5));
|
||||
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Auth;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use LaravelWebauthn\Facades\Webauthn;
|
||||
use Webauthn\PublicKeyCredentialRequestOptions;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
|
||||
class WebauthnController extends AbstractLoginController
|
||||
{
|
||||
private const SESSION_PUBLICKEY_REQUEST = 'webauthn.publicKeyRequest';
|
||||
|
||||
private CacheRepository $cache;
|
||||
|
||||
public function __construct(AuthManager $auth, ConfigRepository $config, CacheRepository $cache)
|
||||
{
|
||||
parent::__construct($auth, $config);
|
||||
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JsonResponse|void
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
*/
|
||||
public function auth(Request $request)
|
||||
{
|
||||
if ($this->hasTooManyLoginAttempts($request)) {
|
||||
$this->sendLockoutResponse($request);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $request->input('confirmation_token');
|
||||
try {
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = User::query()->findOrFail($this->cache->get($token, 0));
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
$this->incrementLoginAttempts($request);
|
||||
|
||||
$this->sendFailedLoginResponse(
|
||||
$request,
|
||||
null,
|
||||
'The authentication token provided has expired, please refresh the page and try again.'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->auth->guard()->onceUsingId($user->id);
|
||||
|
||||
try {
|
||||
$publicKey = $request->session()->pull(self::SESSION_PUBLICKEY_REQUEST);
|
||||
if (!$publicKey instanceof PublicKeyCredentialRequestOptions) {
|
||||
throw new ModelNotFoundException(trans('webauthn::errors.auth_data_not_found'));
|
||||
}
|
||||
|
||||
$result = Webauthn::doAuthenticate(
|
||||
$user,
|
||||
$publicKey,
|
||||
$this->input($request, 'data'),
|
||||
);
|
||||
|
||||
if (!$result) {
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'message' => 'Nice attempt, you didn\'t pass the challenge.',
|
||||
],
|
||||
], JsonResponse::HTTP_I_AM_A_TEAPOT);
|
||||
}
|
||||
|
||||
$this->cache->delete($token);
|
||||
|
||||
return $this->sendLoginResponse($user, $request);
|
||||
} catch (Exception $e) {
|
||||
return new JsonResponse([
|
||||
'error' => [
|
||||
'message' => $e->getMessage(),
|
||||
],
|
||||
], JsonResponse::HTTP_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the input with a string result.
|
||||
*/
|
||||
private function input(Request $request, string $name, string $default = ''): string
|
||||
{
|
||||
$result = $request->input($name);
|
||||
|
||||
return is_string($result) ? $result : $default;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Requests\Api\Client\Account;
|
||||
|
||||
use Pterodactyl\Http\Requests\Api\Client\AccountApiRequest;
|
||||
|
||||
class RegisterWebauthnTokenRequest extends AccountApiRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['string', 'required'],
|
||||
'register' => ['string', 'required'],
|
||||
'public_key' => ['string', 'required'],
|
||||
];
|
||||
}
|
||||
}
|
51
app/Models/HardwareSecurityKey.php
Normal file
51
app/Models/HardwareSecurityKey.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
use Webauthn\PublicKeyCredentialDescriptor;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class HardwareSecurityKey extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const RESOURCE_NAME = 'hardware_security_key';
|
||||
|
||||
protected $attributes = [
|
||||
'user_id' => 'int',
|
||||
'transports' => 'array',
|
||||
'trust_path' => 'array',
|
||||
'other_ui' => 'array',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function toCredentialsDescriptor()
|
||||
{
|
||||
return new PublicKeyCredentialDescriptor(
|
||||
$this->type,
|
||||
$this->public_key_id,
|
||||
$this->transports
|
||||
);
|
||||
}
|
||||
|
||||
public function toCredentialSource(): PublicKeyCredentialSource
|
||||
{
|
||||
return PublicKeyCredentialSource::createFromArray([
|
||||
'publicKeyCredentialId' => $this->public_key_id,
|
||||
'type' => $this->type,
|
||||
'transports' => $this->transports,
|
||||
'attestationType' => $this->attestation_type,
|
||||
// 'trustPath' => $key->trustPath->jsonSerialize(),
|
||||
'aaguid' => $this->aaguid,
|
||||
'credentialPublicKey' => $this->public_key,
|
||||
'userHandle' => $this->user_handle,
|
||||
'counter' => $this->counter,
|
||||
'otherUI' => $this->other_ui,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -210,9 +210,9 @@ class User extends Model implements
|
|||
return $this->hasMany(RecoveryToken::class);
|
||||
}
|
||||
|
||||
public function webauthnKeys(): HasMany
|
||||
public function hardwareSecurityKeys(): HasMany
|
||||
{
|
||||
return $this->hasMany(WebauthnKey::class);
|
||||
return $this->hasMany(HardwareSecurityKey::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class WebauthnKey extends \LaravelWebauthn\Models\WebauthnKey
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const RESOURCE_NAME = 'webauthn_key';
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Repositories\Webauthn;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Container\Container;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
use Pterodactyl\Models\HardwareSecurityKey;
|
||||
use Webauthn\PublicKeyCredentialSourceRepository as PublicKeyRepositoryInterface;
|
||||
|
||||
class PublicKeyCredentialSourceRepository implements PublicKeyRepositoryInterface
|
||||
{
|
||||
protected User $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a single hardware security token for a user by uzing the credential ID.
|
||||
*/
|
||||
public function findOneByCredentialId(string $id): ?PublicKeyCredentialSource
|
||||
{
|
||||
/** @var \Pterodactyl\Models\HardwareSecurityKey $key */
|
||||
$key = $this->user->hardwareSecurityKeys()
|
||||
->where('public_key_id', $id)
|
||||
->first();
|
||||
|
||||
return $key ? $key->toCredentialSource() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all of the hardware tokens that exist for the user using the given
|
||||
* entity handle.
|
||||
*/
|
||||
public function findAllForUserEntity(PublicKeyCredentialUserEntity $entity): array
|
||||
{
|
||||
$results = $this->user->hardwareSecurityKeys()
|
||||
->where('user_handle', $entity->getId())
|
||||
->get();
|
||||
|
||||
return $results->map(function (HardwareSecurityKey $key) {
|
||||
return $key->toCredentialSource();
|
||||
})->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a credential to the database and link it with the user.
|
||||
*/
|
||||
public function saveCredentialSource(PublicKeyCredentialSource $source): void
|
||||
{
|
||||
// todo: implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of the repository with the provided user attached.
|
||||
*/
|
||||
public static function factory(User $user): self
|
||||
{
|
||||
return Container::getInstance()->make(static::class, ['user' => $user]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Pterodactyl\Transformers\Api\Client;
|
||||
|
||||
use Pterodactyl\Models\WebauthnKey;
|
||||
use Pterodactyl\Models\HardwareSecurityKey;
|
||||
use Pterodactyl\Transformers\Api\Transformer;
|
||||
|
||||
class WebauthnKeyTransformer extends Transformer
|
||||
|
@ -12,15 +12,13 @@ class WebauthnKeyTransformer extends Transformer
|
|||
*/
|
||||
public function getResourceName(): string
|
||||
{
|
||||
return WebauthnKey::RESOURCE_NAME;
|
||||
return HardwareSecurityKey::RESOURCE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return basic information about the currently logged in user.
|
||||
*
|
||||
* @param \Pterodactyl\Models\WebauthnKey|\LaravelWebauthn\Models\WebauthnKey $model
|
||||
*/
|
||||
public function transform($model): array
|
||||
public function transform(HardwareSecurityKey $model): array
|
||||
{
|
||||
return [
|
||||
'id' => $model->id,
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
"ext-pdo": "*",
|
||||
"ext-pdo_mysql": "*",
|
||||
"ext-zip": "*",
|
||||
"asbiin/laravel-webauthn": "^1.1",
|
||||
"aws/aws-sdk-php": "^3.192",
|
||||
"aws/aws-sdk-php": "^3.186",
|
||||
"doctrine/dbal": "^3.1",
|
||||
"fideloper/proxy": "^4.4",
|
||||
"guzzlehttp/guzzle": "^7.3",
|
||||
|
@ -42,6 +41,7 @@
|
|||
"spatie/laravel-query-builder": "^3.5",
|
||||
"staudenmeir/belongs-to-through": "^2.11",
|
||||
"symfony/yaml": "^5.3",
|
||||
"web-auth/webauthn-lib": "^3.3",
|
||||
"webmozart/assert": "^1.10"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
1294
composer.lock
generated
1294
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,222 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| LaravelWebauthn Master Switch
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option may be used to disable LaravelWebauthn.
|
||||
|
|
||||
*/
|
||||
|
||||
'enable' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Route Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These middleware will be assigned to Webauthn routes, giving you
|
||||
| the chance to add your own middleware to this list or change any of
|
||||
| the existing middleware. Or, you can simply stick with this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'web',
|
||||
'auth',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Prefix path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The uri prefix for all webauthn requests.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => 'webauthn',
|
||||
|
||||
'authenticate' => [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| View to load after middleware login request.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The name of blade template to load whe a user login and it request to validate
|
||||
| the Webauthn 2nd factor.
|
||||
|
|
||||
*/
|
||||
'view' => 'webauthn::authenticate',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redirect with callback url after login.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Save the destination url, then after a successful login, redirect to this
|
||||
| url.
|
||||
|
|
||||
*/
|
||||
'postSuccessCallback' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redirect route
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If postSuccessCallback if false, redirect to this route after login
|
||||
| request is complete.
|
||||
| If empty, send a json response to let the client side redirection.
|
||||
|
|
||||
*/
|
||||
'postSuccessRedirectRoute' => '',
|
||||
],
|
||||
|
||||
'register' => [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| View to load on register request.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The name of blade template to load when a user request a creation of
|
||||
| Webauthn key.
|
||||
|
|
||||
*/
|
||||
'view' => 'webauthn::register',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redirect route
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The route to redirect to after register key request is complete.
|
||||
| If empty, send a json response to let the client side redirection.
|
||||
|
|
||||
*/
|
||||
'postSuccessRedirectRoute' => '',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Name of the session parameter to store the successful login.
|
||||
|
|
||||
*/
|
||||
|
||||
'sessionName' => 'webauthn_auth',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn challenge length
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Length of the random string used in the challenge request.
|
||||
|
|
||||
*/
|
||||
|
||||
'challenge_length' => 32,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn timeout (milliseconds)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Time that the caller is willing to wait for the call to complete.
|
||||
|
|
||||
*/
|
||||
|
||||
'timeout' => 60000,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn extension client input
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Optional authentication extension.
|
||||
| See https://www.w3.org/TR/webauthn/#client-extension-input
|
||||
|
|
||||
*/
|
||||
|
||||
'extensions' => [],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn icon
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Url which resolves to an image associated with the entity.
|
||||
| See https://www.w3.org/TR/webauthn/#dom-publickeycredentialentity-icon
|
||||
|
|
||||
*/
|
||||
|
||||
'icon' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn Attestation Conveyance
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This parameter specify the preference regarding the attestation conveyance
|
||||
| during credential generation.
|
||||
| See https://www.w3.org/TR/webauthn/#attestation-convey
|
||||
|
|
||||
*/
|
||||
|
||||
'attestation_conveyance' => \Webauthn\PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Google Safetynet ApiKey
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Api key to use Google Safetynet.
|
||||
| See https://developer.android.com/training/safetynet/attestation
|
||||
|
|
||||
*/
|
||||
|
||||
'google_safetynet_api_key' => '',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn Public Key Credential Parameters
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| List of allowed Cryptographic Algorithm Identifier.
|
||||
| See https://www.w3.org/TR/webauthn/#alg-identifier
|
||||
|
|
||||
*/
|
||||
|
||||
'public_key_credential_parameters' => [
|
||||
\Cose\Algorithms::COSE_ALGORITHM_ES256,
|
||||
\Cose\Algorithms::COSE_ALGORITHM_RS256,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webauthn Authenticator Selection Criteria
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requirement for the creation operation.
|
||||
| See https://www.w3.org/TR/webauthn/#authenticatorSelection
|
||||
|
|
||||
*/
|
||||
|
||||
'authenticator_selection_criteria' => [
|
||||
/*
|
||||
| See https://www.w3.org/TR/webauthn/#attachment
|
||||
*/
|
||||
'attachment_mode' => \Webauthn\AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE,
|
||||
|
||||
'require_resident_key' => false,
|
||||
|
||||
/*
|
||||
| See https://www.w3.org/TR/webauthn/#userVerificationRequirement
|
||||
*/
|
||||
'user_verification' => \Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED,
|
||||
],
|
||||
];
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Pterodactyl\Models\WebauthnKey;
|
||||
use Pterodactyl\Models\HardwareSecurityKey;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class WebauthnKeyFactory extends Factory
|
||||
|
@ -10,7 +10,7 @@ class WebauthnKeyFactory extends Factory
|
|||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*/
|
||||
protected $model = WebauthnKey::class;
|
||||
protected $model = HardwareSecurityKey::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateHardwareSecurityKeysTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('hardware_security_keys', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->char('uuid', 36);
|
||||
$table->unsignedInteger('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$table->text('public_key_id');
|
||||
$table->text('public_key');
|
||||
$table->char('aaguid', 36);
|
||||
$table->string('type');
|
||||
$table->json('transports');
|
||||
$table->string('attestation_type');
|
||||
$table->json('trust_path');
|
||||
$table->text('user_handle');
|
||||
$table->unsignedInteger('counter');
|
||||
$table->json('other_ui');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('hardware_security_keys');
|
||||
}
|
||||
}
|
|
@ -30,10 +30,10 @@ Route::group(['prefix' => '/account'], function () {
|
|||
Route::post('/api-keys', [Client\ApiKeyController::class, 'store']);
|
||||
Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']);
|
||||
|
||||
Route::get('/webauthn', 'WebauthnController@index')->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
Route::get('/webauthn/register', 'WebauthnController@register')->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
Route::post('/webauthn/register', 'WebauthnController@create')->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
Route::delete('/webauthn/{id}', 'WebauthnController@deleteKey')->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
Route::get('/webauthn', [Client\HardwareTokenController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
Route::get('/webauthn/register', [Client\HardwareTokenController::class, 'create'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
Route::post('/webauthn/register', [Client\HardwareTokenController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
Route::delete('/webauthn/{id}', [Client\HardwareTokenController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class);
|
||||
|
||||
Route::get('/ssh', 'SSHKeyController@index');
|
||||
Route::post('/ssh', 'SSHKeyController@store');
|
||||
|
|
|
@ -20,7 +20,6 @@ Route::group(['middleware' => 'guest'], function () {
|
|||
// Login endpoints.
|
||||
Route::post('/login', 'LoginController@login')->middleware('recaptcha');
|
||||
Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint');
|
||||
Route::post('/login/checkpoint/key', 'WebauthnController@auth');
|
||||
|
||||
// Forgot password route. A post to this endpoint will trigger an
|
||||
// email to be sent containing a reset token.
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware;
|
|||
|
||||
use Mockery as m;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\WebauthnKey;
|
||||
use Pterodactyl\Models\HardwareSecurityKey;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException;
|
||||
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
|
||||
|
@ -66,7 +66,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
|||
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = User::factory()
|
||||
->has(WebauthnKey::factory()->count(1))
|
||||
->has(HardwareSecurityKey::factory()->count(1))
|
||||
->create(['use_totp' => false]);
|
||||
$this->setRequestUserModel($user);
|
||||
|
||||
|
@ -141,7 +141,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
|||
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = User::factory()
|
||||
->has(WebauthnKey::factory()->count(1))
|
||||
->has(HardwareSecurityKey::factory()->count(1))
|
||||
->create(['use_totp' => false]);
|
||||
$this->setRequestUserModel($user);
|
||||
|
||||
|
@ -255,7 +255,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
|||
config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN);
|
||||
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = User::factory()->has(WebauthnKey::factory()->count(1))->create(['use_totp' => false]);
|
||||
$user = User::factory()->has(HardwareSecurityKey::factory()->count(1))->create(['use_totp' => false]);
|
||||
$this->setRequestUserModel($user);
|
||||
|
||||
$this->assertFalse($user->use_totp);
|
||||
|
@ -278,7 +278,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
|
|||
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = User::factory()
|
||||
->has(WebauthnKey::factory()->count(1))
|
||||
->has(HardwareSecurityKey::factory()->count(1))
|
||||
->create(['use_totp' => false, 'root_admin' => true]);
|
||||
$this->setRequestUserModel($user);
|
||||
|
||||
|
|
Loading…
Reference in a new issue