1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-22 09:02:28 +01:00

cleanup, switch to attributes

This commit is contained in:
Matthew Penner 2023-01-17 16:09:28 -07:00
parent f631ac1946
commit ba7ff571e5
No known key found for this signature in database
9 changed files with 80 additions and 63 deletions

View File

@ -63,7 +63,7 @@ class LoginCheckpointController extends AbstractLoginController
$decrypted = $this->encrypter->decrypt($user->totp_secret); $decrypted = $this->encrypter->decrypt($user->totp_secret);
if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { if ($this->google2FA->verifyKey($decrypted, $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) {
return $this->sendLoginResponse($user, $request); return $this->sendLoginResponse($user, $request);
} }
} }

View File

@ -2,11 +2,12 @@
namespace Pterodactyl\Models; namespace Pterodactyl\Models;
use Ramsey\Uuid\Uuid;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Ramsey\Uuid\UuidInterface; use Symfony\Component\Uid\Uuid;
use Webauthn\TrustPath\TrustPath; use Webauthn\TrustPath\TrustPath;
use Symfony\Component\Uid\NilUuid;
use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Component\Uid\AbstractUid;
use Webauthn\PublicKeyCredentialSource; use Webauthn\PublicKeyCredentialSource;
use Webauthn\TrustPath\TrustPathLoader; use Webauthn\TrustPath\TrustPathLoader;
use Webauthn\PublicKeyCredentialDescriptor; use Webauthn\PublicKeyCredentialDescriptor;
@ -14,7 +15,27 @@ use Psr\Http\Message\ServerRequestInterface;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* @property int $id
* @property string $uuid
* @property int $user_id
* @property string $name
* @property string $public_key_id
* @property string $public_key
* @property AbstractUid $aaguid
* @property string $type
* @property string[] $transports
* @property string $attestation_type
* @property \Webauthn\TrustPath\TrustPath $trust_path
* @property string $user_handle
* @property int $counter
* @property array<string, mixed>|null $other_ui
*
* @property \Carbon\CarbonImmutable $created_at
* @property \Carbon\CarbonImmutable $updated_at
*/
class SecurityKey extends Model class SecurityKey extends Model
{ {
use HasFactory; use HasFactory;
@ -33,57 +54,36 @@ class SecurityKey extends Model
'user_id', 'user_id',
]; ];
public function getPublicKeyAttribute(string $value): string public function publicKey(): Attribute
{ {
return base64_decode($value); return new Attribute(
get: fn (string $value) => base64_decode($value),
set: fn (string $value) => base64_encode($value),
);
} }
public function setPublicKeyAttribute(string $value): void public function publicKeyId(): Attribute
{ {
$this->attributes['public_key'] = base64_encode($value); return new Attribute(
get: fn (string $value) => base64_decode($value),
set: fn (string $value) => base64_encode($value),
);
} }
public function getPublicKeyIdAttribute(string $value): string public function aaguid(): Attribute
{ {
return base64_decode($value); return Attribute::make(
get: fn (string|null $value): AbstractUid => is_null($value) ? new NilUuid() : Uuid::fromString($value),
set: fn (AbstractUid|null $value): string|null => (is_null($value) || $value instanceof NilUuid) ? null : $value->__toString(),
);
} }
public function setPublicKeyIdAttribute(string $value): void public function trustPath(): Attribute
{ {
$this->attributes['public_key_id'] = base64_encode($value); return new Attribute(
} get: fn (mixed $value) => is_null($value) ? null : TrustPathLoader::loadTrustPath(json_decode($value, true)),
set: fn (TrustPath|null $value) => json_encode($value),
public function getTrustPathAttribute(?string $value): ?TrustPath );
{
if (is_null($value)) {
return null;
}
return TrustPathLoader::loadTrustPath(json_decode($value, true));
}
public function setTrustPathAttribute(?TrustPath $value): void
{
$this->attributes['trust_path'] = json_encode($value);
}
/**
* @param \Ramsey\Uuid\UuidInterface|string|null $value
*/
public function setAaguidAttribute($value): void
{
$value = $value instanceof UuidInterface ? $value->__toString() : $value;
$this->attributes['aaguid'] = (is_null($value) || $value === Uuid::NIL) ? null : $value;
}
public function getAaguidAttribute(?string $value): ?UuidInterface
{
if (!is_null($value) && Uuid::isValid($value)) {
return Uuid::fromString($value);
}
return null;
} }
public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor
@ -99,7 +99,7 @@ class SecurityKey extends Model
$this->transports, $this->transports,
$this->attestation_type, $this->attestation_type,
$this->trust_path, $this->trust_path,
$this->aaguid ?? Uuid::fromString(Uuid::NIL), $this->aaguid,
$this->public_key, $this->public_key,
$this->user_handle, $this->user_handle,
$this->counter $this->counter

View File

@ -38,7 +38,9 @@ class DeployServerDatabaseService
throw new NoSuitableDatabaseHostException(); throw new NoSuitableDatabaseHostException();
} }
$databaseHostId = $hosts->random()->id; /** @var \Pterodactyl\Models\DatabaseHost $databaseHost */
$databaseHost = $hosts->random();
$databaseHostId = $databaseHost->id;
} }
return $this->managementService->create($server, [ return $this->managementService->create($server, [

View File

@ -3,8 +3,9 @@
namespace Pterodactyl\Transformers\Api\Client; namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\SecurityKey; use Pterodactyl\Models\SecurityKey;
use Pterodactyl\Transformers\Api\Transformer;
class SecurityKeyTransformer extends BaseClientTransformer class SecurityKeyTransformer extends Transformer
{ {
public function getResourceName(): string public function getResourceName(): string
{ {

View File

@ -1,11 +1,14 @@
import useSWR, { ConfigInterface } from 'swr'; import type { AxiosError } from 'axios';
import { useStoreState } from '@/state/hooks'; import type { SWRConfiguration } from 'swr';
import http, { FractalResponseList } from '@/api/http'; import useSWR from 'swr';
import { SecurityKey, Transformers } from '@definitions/user';
import { AxiosError } from 'axios'; import type { SecurityKey } from '@definitions/user';
import { Transformers } from '@definitions/user';
import { LoginResponse } from '@/api/auth/login';
import type { FractalResponseList } from '@/api/http';
import http from '@/api/http';
import { decodeBase64 } from '@/lib/base64'; import { decodeBase64 } from '@/lib/base64';
import { decodeBuffer, encodeBuffer } from '@/lib/buffer'; import { decodeBuffer, encodeBuffer } from '@/lib/buffer';
import { LoginResponse } from '@/api/auth/login';
import { useUserSWRKey } from '@/plugins/useSWRKey'; import { useUserSWRKey } from '@/plugins/useSWRKey';
function decodeSecurityKeyCredentials(credentials: PublicKeyCredentialDescriptor[]) { function decodeSecurityKeyCredentials(credentials: PublicKeyCredentialDescriptor[]) {
@ -16,8 +19,7 @@ function decodeSecurityKeyCredentials(credentials: PublicKeyCredentialDescriptor
})); }));
} }
function useSecurityKeys(config?: ConfigInterface<SecurityKey[], AxiosError>) { function useSecurityKeys(config?: SWRConfiguration<SecurityKey[], AxiosError>) {
const uuid = useStoreState(state => state.user.data!.uuid);
const key = useUserSWRKey(['account', 'security-keys']); const key = useUserSWRKey(['account', 'security-keys']);
return useSWR<SecurityKey[], AxiosError>( return useSWR<SecurityKey[], AxiosError>(
@ -25,9 +27,9 @@ function useSecurityKeys(config?: ConfigInterface<SecurityKey[], AxiosError>) {
async (): Promise<SecurityKey[]> => { async (): Promise<SecurityKey[]> => {
const { data } = await http.get('/api/client/account/security-keys'); const { data } = await http.get('/api/client/account/security-keys');
return (data as FractalResponseList).data.map((datum) => Transformers.toSecurityKey(datum.attributes)); return (data as FractalResponseList).data.map(datum => Transformers.toSecurityKey(datum.attributes));
}, },
{ revalidateOnMount: false, ...(config || {}) }, { revalidateOnMount: false, ...(config ?? {}) },
); );
} }
@ -35,7 +37,11 @@ async function deleteSecurityKey(uuid: string): Promise<void> {
await http.delete(`/api/client/account/security-keys/${uuid}`); await http.delete(`/api/client/account/security-keys/${uuid}`);
} }
async function registerCredentialForAccount(name: string, tokenId: string, credential: PublicKeyCredential): Promise<SecurityKey> { async function registerCredentialForAccount(
name: string,
tokenId: string,
credential: PublicKeyCredential,
): Promise<SecurityKey> {
const { data } = await http.post('/api/client/account/security-keys/register', { const { data } = await http.post('/api/client/account/security-keys/register', {
name, name,
token_id: tokenId, token_id: tokenId,
@ -44,7 +50,9 @@ async function registerCredentialForAccount(name: string, tokenId: string, crede
type: credential.type, type: credential.type,
rawId: encodeBuffer(credential.rawId), rawId: encodeBuffer(credential.rawId),
response: { response: {
attestationObject: encodeBuffer((credential.response as AuthenticatorAttestationResponse).attestationObject), attestationObject: encodeBuffer(
(credential.response as AuthenticatorAttestationResponse).attestationObject,
),
clientDataJSON: encodeBuffer(credential.response.clientDataJSON), clientDataJSON: encodeBuffer(credential.response.clientDataJSON),
}, },
}, },
@ -66,7 +74,9 @@ async function registerSecurityKey(name: string): Promise<SecurityKey> {
const credentials = await navigator.credentials.create({ publicKey }); const credentials = await navigator.credentials.create({ publicKey });
if (!credentials || credentials.type !== 'public-key') { if (!credentials || credentials.type !== 'public-key') {
throw new Error(`Unexpected type returned by navigator.credentials.create(): expected "public-key", got "${credentials?.type}"`); throw new Error(
`Unexpected type returned by navigator.credentials.create(): expected "public-key", got "${credentials?.type}"`,
);
} }
return await registerCredentialForAccount(name, data.data.token_id, credentials as PublicKeyCredential); return await registerCredentialForAccount(name, data.data.token_id, credentials as PublicKeyCredential);

View File

@ -22,7 +22,7 @@ export default class Transformers {
}; };
}; };
static toSecurityKey (data: Record<string, any>): Models.SecurityKey { static toSecurityKey(data: Record<string, any>): Models.SecurityKey {
return { return {
uuid: data.uuid, uuid: data.uuid,
name: data.name, name: data.name,

View File

@ -1,3 +1,5 @@
import { describe, expect, it } from 'vitest';
import { decodeBase64 } from '@/lib/base64'; import { decodeBase64 } from '@/lib/base64';
describe('@/lib/base64.ts', function () { describe('@/lib/base64.ts', function () {

View File

@ -1,4 +1,4 @@
function decodeBase64 (input: string): string { function decodeBase64(input: string): string {
input = input.replace(/-/g, '+').replace(/_/g, '/'); input = input.replace(/-/g, '+').replace(/_/g, '/');
const pad = input.length % 4; const pad = input.length % 4;
@ -13,4 +13,4 @@ function decodeBase64 (input: string): string {
return input; return input;
} }
export { decodeBase64 } export { decodeBase64 };

View File

@ -1,3 +1,5 @@
import { describe, expect, it } from 'vitest';
import { decodeBuffer, encodeBuffer } from '@/lib/buffer'; import { decodeBuffer, encodeBuffer } from '@/lib/buffer';
describe('@/lib/buffer.ts', function () { describe('@/lib/buffer.ts', function () {