Skip to content

Commit 933a473

Browse files
committed
Add base support for creating a new API key for an account
1 parent 32f2517 commit 933a473

File tree

17 files changed

+371
-13
lines changed

17 files changed

+371
-13
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Controllers\Api\Client;
4+
5+
use Pterodactyl\Models\ApiKey;
6+
use Pterodactyl\Exceptions\DisplayException;
7+
use Illuminate\Contracts\Encryption\Encrypter;
8+
use Pterodactyl\Services\Api\KeyCreationService;
9+
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
10+
use Pterodactyl\Transformers\Api\Client\ApiKeyTransformer;
11+
use Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest;
12+
13+
class ApiKeyController extends ClientApiController
14+
{
15+
/**
16+
* @var \Pterodactyl\Services\Api\KeyCreationService
17+
*/
18+
private $keyCreationService;
19+
20+
/**
21+
* @var \Illuminate\Contracts\Encryption\Encrypter
22+
*/
23+
private $encrypter;
24+
25+
/**
26+
* ApiKeyController constructor.
27+
*
28+
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
29+
* @param \Pterodactyl\Services\Api\KeyCreationService $keyCreationService
30+
*/
31+
public function __construct(Encrypter $encrypter, KeyCreationService $keyCreationService)
32+
{
33+
parent::__construct();
34+
35+
$this->encrypter = $encrypter;
36+
$this->keyCreationService = $keyCreationService;
37+
}
38+
39+
/**
40+
* Returns all of the API keys that exist for the given client.
41+
*
42+
* @param \Pterodactyl\Http\Requests\Api\Client\ClientApiRequest $request
43+
* @return array
44+
*/
45+
public function index(ClientApiRequest $request)
46+
{
47+
return $this->fractal->collection($request->user()->apiKeys)
48+
->transformWith($this->getTransformer(ApiKeyTransformer::class))
49+
->toArray();
50+
}
51+
52+
/**
53+
* Store a new API key for a user's account.
54+
*
55+
* @param \Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest $request
56+
* @return array
57+
*
58+
* @throws \Pterodactyl\Exceptions\DisplayException
59+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
60+
*/
61+
public function store(StoreApiKeyRequest $request)
62+
{
63+
if ($request->user()->apiKeys->count() >= 5) {
64+
throw new DisplayException(
65+
'You have reached the account limit for number of API keys.'
66+
);
67+
}
68+
69+
$key = $this->keyCreationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
70+
'user_id' => $request->user()->id,
71+
'memo' => $request->input('description'),
72+
'allowed_ips' => $request->input('allowed_ips') ?? [],
73+
]);
74+
75+
return $this->fractal->item($key)
76+
->transformWith($this->getTransformer(ApiKeyTransformer::class))
77+
->addMeta([
78+
'secret_token' => $this->encrypter->decrypt($key->token),
79+
])
80+
->toArray();
81+
}
82+
83+
public function delete()
84+
{
85+
}
86+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Api\Client\Account;
4+
5+
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
6+
7+
class StoreApiKeyRequest extends ClientApiRequest
8+
{
9+
/**
10+
* @return array
11+
*/
12+
public function rules(): array
13+
{
14+
return [
15+
'description' => 'required|string|min:4',
16+
'allowed_ips' => 'array',
17+
'allowed_ips.*' => 'ip',
18+
];
19+
}
20+
}

app/Models/ApiKey.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,22 @@
44

55
use Pterodactyl\Services\Acl\Api\AdminAcl;
66

7+
/**
8+
* @property int $id
9+
* @property int $user_id
10+
* @property int $key_type
11+
* @property string $identifier
12+
* @property string $token
13+
* @property array $allowed_ips
14+
* @property string $memo
15+
* @property \Carbon\Carbon|null $last_used_at
16+
* @property \Carbon\Carbon $created_at
17+
* @property \Carbon\Carbon $updated_at
18+
*/
719
class ApiKey extends Validable
820
{
21+
const RESOURCE_NAME = 'api_key';
22+
923
/**
1024
* Different API keys that can exist on the system.
1125
*/

app/Models/User.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* @property \Carbon\Carbon $updated_at
3737
*
3838
* @property string $name
39+
* @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys
3940
* @property \Pterodactyl\Models\Permission[]|\Illuminate\Database\Eloquent\Collection $permissions
4041
* @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
4142
* @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subuserOf
@@ -258,4 +259,13 @@ public function keys()
258259
{
259260
return $this->hasMany(DaemonKey::class);
260261
}
262+
263+
/**
264+
* @return \Illuminate\Database\Eloquent\Relations\HasMany
265+
*/
266+
public function apiKeys()
267+
{
268+
return $this->hasMany(ApiKey::class)
269+
->where('key_type', ApiKey::TYPE_ACCOUNT);
270+
}
261271
}

app/Services/Api/KeyCreationService.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ public function handle(array $data, array $permissions = []): ApiKey
7272
$data = array_merge($data, $permissions);
7373
}
7474

75-
$instance = $this->repository->create($data, true, true);
76-
77-
return $instance;
75+
return $this->repository->create($data, true, true);
7876
}
7977
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Pterodactyl\Transformers\Api\Client;
4+
5+
use Pterodactyl\Models\ApiKey;
6+
7+
class ApiKeyTransformer extends BaseClientTransformer
8+
{
9+
/**
10+
* {@inheritdoc}
11+
*/
12+
public function getResourceName(): string
13+
{
14+
return ApiKey::RESOURCE_NAME;
15+
}
16+
17+
/**
18+
* Transform this model into a representation that can be consumed by a client.
19+
*
20+
* @param \Pterodactyl\Models\ApiKey $model
21+
* @return array
22+
*/
23+
public function transform(ApiKey $model)
24+
{
25+
return [
26+
'identifier' => $model->identifier,
27+
'description' => $model->memo,
28+
'allowed_ips' => $model->allowed_ips,
29+
'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null,
30+
'created_at' => $model->created_at->toIso8601String(),
31+
];
32+
}
33+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"devDependencies": {
4444
"@babel/core": "^7.7.5",
4545
"@babel/plugin-proposal-class-properties": "^7.7.4",
46+
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
4647
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
4748
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
4849
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import http from '@/api/http';
2+
import { ApiKey, rawDataToApiKey } from '@/api/account/getApiKeys';
3+
4+
export default (description: string, allowedIps: string): Promise<ApiKey & { secretToken: string }> => {
5+
return new Promise((resolve, reject) => {
6+
http.post(`/api/client/account/api-keys`, {
7+
description,
8+
// eslint-disable-next-line @typescript-eslint/camelcase
9+
allowed_ips: allowedIps.length > 0 ? allowedIps.split('\n') : [],
10+
})
11+
.then(({ data }) => resolve({
12+
...rawDataToApiKey(data.attributes),
13+
secretToken: data.meta?.secret_token ?? '',
14+
}))
15+
.catch(reject);
16+
});
17+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import http from '@/api/http';
2+
3+
export interface ApiKey {
4+
identifier: string;
5+
description: string;
6+
allowedIps: string[];
7+
createdAt: Date | null;
8+
lastUsedAt: Date | null;
9+
}
10+
11+
export const rawDataToApiKey = (data: any): ApiKey => ({
12+
identifier: data.identifier,
13+
description: data.description,
14+
allowedIps: data.allowed_ips,
15+
createdAt: data.created_at ? new Date(data.created_at) : null,
16+
lastUsedAt: data.last_used_at ? new Date(data.last_used_at) : null,
17+
});
18+
19+
export default (): Promise<ApiKey[]> => {
20+
return new Promise((resolve, reject) => {
21+
http.get('/api/client/account/api-keys')
22+
.then(({ data }) => resolve((data.data || []).map(rawDataToApiKey)))
23+
.catch(reject);
24+
});
25+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import ContentBox from '@/components/elements/ContentBox';
3+
import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm';
4+
5+
export default () => {
6+
return (
7+
<div className={'my-10 flex'}>
8+
<ContentBox title={'Create API Key'} className={'flex-1'} showFlashes={'account'}>
9+
<CreateApiKeyForm/>
10+
</ContentBox>
11+
<ContentBox title={'API Keys'} className={'ml-10 flex-1'}>
12+
<p>Testing</p>
13+
</ContentBox>
14+
</div>
15+
);
16+
};

0 commit comments

Comments
 (0)