Update underlying model representation for PATs

This commit is contained in:
Dane Everitt 2021-07-28 20:53:54 -07:00
parent d60e8a193b
commit 1a3451fb0d
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
12 changed files with 135 additions and 24002 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -63,12 +63,12 @@ class ApiKeyController extends ClientApiController
// TODO: this should accept an array of different scopes to apply as permissions
// for the token. Right now it allows any account level permission.
$token = $request->user()->createToken($request->input('description'));
[$token, $plaintext] = $request->user()->createToken($request->input('description'));
return $this->fractal->item($token->accessToken)
return $this->fractal->item($token)
->transformWith($this->getTransformer(PersonalAccessTokenTransformer::class))
->addMeta([
'secret_token' => $token->plainTextToken,
'secret_token' => $plaintext,
])
->toArray();
}
@ -76,9 +76,9 @@ class ApiKeyController extends ClientApiController
/**
* Deletes a given API key.
*/
public function delete(ClientApiRequest $request, string $identifier): Response
public function delete(ClientApiRequest $request, string $id): Response
{
$request->user()->tokens()->where('id', $identifier)->delete();
$request->user()->tokens()->where('token_id', $id)->delete();
return $this->returnNoContent();
}

View file

@ -2,9 +2,78 @@
namespace Pterodactyl\Models;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
use Illuminate\Support\Str;
use Laravel\Sanctum\Contracts\HasAbilities;
class PersonalAccessToken extends SanctumPersonalAccessToken
class PersonalAccessToken extends Model implements HasAbilities
{
public const RESOURCE_NAME = 'personal_access_token';
/**
* @var string[]
*/
protected $casts = [
'user_id' => 'int',
'abilities' => 'json',
'last_used_at' => 'datetime',
];
/**
* @var string[]
*/
protected $fillable = [
'description',
'token',
'token_id',
'abilities',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Determine if the token has a given ability.
*
* @param string $ability
* @return bool
*/
public function can($ability)
{
return in_array('*', $this->abilities) ||
array_key_exists($ability, array_flip($this->abilities));
}
/**
* Determine if the token is missing a given ability.
*
* @param string $ability
* @return bool
*/
public function cant($ability)
{
return !$this->can($ability);
}
/**
* Find the token instance matching the given token.
*
* @param string $token
* @return \Pterodactyl\Models\PersonalAccessToken|null
*/
public static function findToken($token)
{
if (strpos($token, '_') === false) {
return null;
}
$id = Str::substr($token, 0, 16);
$token = Str::substr($token, strlen($id));
return static::where('token_id', $id)->where('token', hash('sha256', $token))->first();
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Pterodactyl\Models\Traits;
use Illuminate\Support\Str;
use Laravel\Sanctum\HasApiTokens;
use Pterodactyl\Models\PersonalAccessToken;
/**
* @mixin \Pterodactyl\Models\Model
*/
trait HasAccessTokens
{
use HasApiTokens;
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tokens()
{
return $this->hasMany(PersonalAccessToken::class);
}
/**
* Creates a new personal access token for the user. The token will be returned
* as the first element of the array, and the plain-text token will be the second.
*
* @param string $description
* @param string[] $abilities
* @return array
*/
public function createToken(string $description, array $abilities = ['*']): array
{
/** @var \Pterodactyl\Models\PersonalAccessToken $token */
$token = $this->tokens()->create([
'user_id' => $this->id,
'description' => $description,
'token' => hash('sha256', $plain = Str::random(36)),
'token_id' => 'ptdl_' . Str::random(11),
'abilities' => $abilities,
]);
return [$token, $token->token_id . $plain];
}
}

View file

@ -3,11 +3,11 @@
namespace Pterodactyl\Models;
use Pterodactyl\Rules\Username;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Support\Collection;
use Illuminate\Auth\Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Builder;
use Pterodactyl\Models\Traits\HasAccessTokens;
use Illuminate\Auth\Passwords\CanResetPassword;
use Pterodactyl\Traits\Helpers\AvailableLanguages;
use Illuminate\Database\Eloquent\Relations\HasOne;
@ -28,7 +28,7 @@ class User extends Model implements
use Authorizable;
use AvailableLanguages;
use CanResetPassword;
use HasApiTokens;
use HasAccessTokens;
use HasFactory;
use Notifiable;

View file

@ -37,6 +37,8 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
Sanctum::ignoreMigrations();
// Only load the settings service provider if the environment
// is configured to allow it.
if (! config('pterodactyl.load_environment_only', false) && $this->app->environment() !== 'testing') {

View file

@ -21,8 +21,8 @@ class PersonalAccessTokenTransformer extends BaseClientTransformer
public function transform(PersonalAccessToken $model): array
{
return [
'id' => $model->tokenable_id,
'name' => $model->name,
'token_id' => $model->token_id,
'description' => $model->description,
'abilities' => $model->abilities ?? [],
'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null,
'created_at' => $model->created_at->toIso8601String(),

View file

@ -15,12 +15,15 @@ class CreatePersonalAccessTokensTable extends Migration
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->bigIncrements('id');
$table->morphs('tokenable');
$table->string('name');
$table->unsignedInteger('user_id');
$table->char('token_id', 16)->unique();
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->json('abilities')->nullable();
$table->string('description');
$table->timestamp('last_used_at')->nullable();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
});
}

View file

@ -3,15 +3,13 @@ import http from '@/api/http';
export interface ApiKey {
identifier: string;
description: string;
allowedIps: string[];
createdAt: Date | null;
lastUsedAt: Date | null;
}
export const rawDataToApiKey = (data: any): ApiKey => ({
identifier: data.identifier,
identifier: data.token_id,
description: data.description,
allowedIps: data.allowed_ips,
createdAt: data.created_at ? new Date(data.created_at) : null,
lastUsedAt: data.last_used_at ? new Date(data.last_used_at) : null,
});

View file

@ -30,7 +30,7 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => {
.then(({ secretToken, ...key }) => {
resetForm();
setSubmitting(false);
setApiKey(`${key.identifier}${secretToken}`);
setApiKey(secretToken);
onKeyCreated(key);
})
.catch(error => {