Skip to content

Commit 53ec2c5

Browse files
committed
Add front-end support for adding and deleting API keys.
1 parent dfeed01 commit 53ec2c5

File tree

8 files changed

+383
-229
lines changed

8 files changed

+383
-229
lines changed

app/Http/Controllers/Base/APIController.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
namespace Pterodactyl\Http\Controllers\Base;
2626

2727
use Alert;
28+
use Log;
2829

2930
use Pterodactyl\Models;
3031

32+
use Pterodactyl\Repositories\APIRepository;
33+
use Pterodactyl\Exceptions\DisplayValidationException;
3134
use Pterodactyl\Exceptions\DisplayException;
3235
use Pterodactyl\Http\Controllers\Controller;
3336

@@ -45,7 +48,6 @@ public function index(Request $request)
4548
return view('base.api.index', [
4649
'keys' => $keys
4750
]);
48-
4951
}
5052

5153
public function new(Request $request)
@@ -55,6 +57,32 @@ public function new(Request $request)
5557

5658
public function save(Request $request)
5759
{
60+
try {
61+
$repo = new APIRepository($request->user());
62+
$secret = $repo->new($request->except(['_token']));
63+
Alert::success('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.<br /><br /><code>' . $secret . '</code>')->flash();
64+
return redirect()->route('account.api');
65+
} catch (DisplayValidationException $ex) {
66+
return redirect()->route('account.api.new')->withErrors(json_decode($ex->getMessage()))->withInput();
67+
} catch (DisplayException $ex) {
68+
Alert::danger($ex->getMessage())->flash();
69+
} catch (\Exception $ex) {
70+
Log::error($ex);
71+
Alert::danger('An unhandled exception occured while attempting to add this API key.')->flash();
72+
}
73+
return redirect()->route('account.api.new')->withInput();
74+
}
5875

76+
public function revoke(Request $request, $key)
77+
{
78+
try {
79+
$repo = new APIRepository($request->user());
80+
$repo->revoke($key);
81+
return response('', 204);
82+
} catch (\Exception $ex) {
83+
return response()->json([
84+
'error' => 'An error occured while attempting to remove this key.'
85+
], 503);
86+
}
5987
}
6088
}

app/Http/Routes/APIRoutes.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function map(Router $router) {
3434
$api = app('Dingo\Api\Routing\Router');
3535
$api->version('v1', ['prefix' => 'api/me', 'middleware' => 'api.auth'], function ($api) {
3636
$api->get('/', [
37-
'as' => 'api.user',
37+
'as' => 'api.user.me',
3838
'uses' => 'Pterodactyl\Http\Controllers\API\User\InfoController@me'
3939
]);
4040

app/Http/Routes/BaseRoutes.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ public function map(Router $router) {
8888
$router->post('/new', [
8989
'uses' => 'Base\APIController@save'
9090
]);
91+
92+
$router->delete('/revoke/{key}', [
93+
'uses' => 'Base\APIController@revoke'
94+
]);
9195
});
9296

9397
// TOTP Routes

app/Repositories/APIRepository.php

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
namespace Pterodactyl\Repositories;
2525

26+
use Auth;
2627
use DB;
2728
use Crypt;
2829
use Validator;
@@ -40,38 +41,51 @@ class APIRepository
4041
* @var array
4142
*/
4243
protected $permissions = [
43-
'*',
44-
45-
// User Management Routes
46-
'api.users.list',
47-
'api.users.create',
48-
'api.users.view',
49-
'api.users.update',
50-
'api.users.delete',
51-
52-
// Server Manaement Routes
53-
'api.servers.list',
54-
'api.servers.create',
55-
'api.servers.view',
56-
'api.servers.config',
57-
'api.servers.build',
58-
'api.servers.suspend',
59-
'api.servers.unsuspend',
60-
'api.servers.delete',
61-
62-
// Node Management Routes
63-
'api.nodes.list',
64-
'api.nodes.create',
65-
'api.nodes.list',
66-
'api.nodes.allocations',
67-
'api.nodes.delete',
68-
69-
// Service Routes
70-
'api.services.list',
71-
'api.services.view',
72-
73-
// Location Routes
74-
'api.locations.list',
44+
'admin' => [
45+
'*',
46+
47+
// User Management Routes
48+
'users.list',
49+
'users.create',
50+
'users.view',
51+
'users.update',
52+
'users.delete',
53+
54+
// Server Manaement Routes
55+
'servers.list',
56+
'servers.create',
57+
'servers.view',
58+
'servers.config',
59+
'servers.build',
60+
'servers.suspend',
61+
'servers.unsuspend',
62+
'servers.delete',
63+
64+
// Node Management Routes
65+
'nodes.list',
66+
'nodes.create',
67+
'nodes.list',
68+
'nodes.allocations',
69+
'nodes.delete',
70+
71+
// Service Routes
72+
'services.list',
73+
'services.view',
74+
75+
// Location Routes
76+
'locations.list',
77+
78+
],
79+
'user' => [
80+
'*',
81+
82+
// Informational
83+
'me',
84+
85+
// Server Control
86+
'server',
87+
'server.power',
88+
],
7589
];
7690

7791
/**
@@ -80,12 +94,17 @@ class APIRepository
8094
*/
8195
protected $allowed = [];
8296

97+
protected $user;
98+
8399
/**
84100
* Constructor
85101
*/
86-
public function __construct()
102+
public function __construct(Models\User $user = null)
87103
{
88-
//
104+
$this->user = is_null($user) ? Auth::user() : $user;
105+
if (is_null($this->user)) {
106+
throw new \Exception('Cannot access API Repository without passing a user to __construct().');
107+
}
89108
}
90109

91110
/**
@@ -101,7 +120,9 @@ public function __construct()
101120
public function new(array $data)
102121
{
103122
$validator = Validator::make($data, [
104-
'permissions' => 'required|array'
123+
'memo' => 'string|max:500',
124+
'permissions' => 'sometimes|required|array',
125+
'adminPermissions' => 'sometimes|required|array'
105126
]);
106127

107128
$validator->after(function($validator) use ($data) {
@@ -125,31 +146,53 @@ public function new(array $data)
125146
}
126147

127148
DB::beginTransaction();
128-
129149
try {
130-
$secretKey = str_random(16) . '.' . str_random(15);
150+
$secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7);
131151
$key = new Models\APIKey;
132152
$key->fill([
153+
'user' => $this->user->id,
133154
'public' => str_random(16),
134155
'secret' => Crypt::encrypt($secretKey),
135-
'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed)
156+
'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed),
157+
'memo' => $data['memo'],
158+
'expires_at' => null
136159
]);
137160
$key->save();
138161

139-
foreach($data['permissions'] as $permission) {
140-
if (in_array($permission, $this->permissions)) {
162+
foreach($data['permissions'] as $permNode) {
163+
if (!strpos($permNode, ':')) continue;
164+
165+
list($toss, $permission) = explode(':', $permNode);
166+
if (in_array('api.user.' . $permission, $this->permissions['user'])) {
141167
$model = new Models\APIPermission;
142168
$model->fill([
143169
'key_id' => $key->id,
144-
'permission' => $permission
170+
'permission' => 'api.user.' . $permission
145171
]);
146172
$model->save();
147173
}
148174
}
149175

176+
if ($this->user->root_admin === 1) {
177+
foreach($data['permissions'] as $permNode) {
178+
if (!strpos($permNode, ':')) continue;
179+
180+
list($toss, $permission) = explode(':', $permNode);
181+
if (in_array('api.admin.' . $permission, $this->permissions['admin'])) {
182+
$model = new Models\APIPermission;
183+
$model->fill([
184+
'key_id' => $key->id,
185+
'permission' => 'api.admin.' . $permission
186+
]);
187+
$model->save();
188+
}
189+
}
190+
}
191+
150192
DB::commit();
151193
return $secretKey;
152194
} catch (\Exception $ex) {
195+
DB::rollBack();
153196
throw $ex;
154197
}
155198

@@ -169,7 +212,7 @@ public function revoke(string $key)
169212
DB::beginTransaction();
170213

171214
try {
172-
$model = Models\APIKey::where('public', $key)->firstOrFail();
215+
$model = Models\APIKey::where('public', $key)->where('user', $this->user->id)->firstOrFail();
173216
$permissions = Models\APIPermission::where('key_id', $model->id)->delete();
174217
$model->delete();
175218

public/themes/default/css/pterodactyl.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,7 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default.
267267
.fuelux .wizard .step-content > .alert {
268268
margin-bottom: 0 !important;
269269
}
270+
271+
.fuelux .wizard .steps-container {
272+
background-color: #eee;
273+
}

resources/views/base/api/index.blade.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
@section('sidebar-server')
2525
@endsection
2626

27+
@section('scripts')
28+
@parent
29+
{!! Theme::css('css/vendor/sweetalert/sweetalert.min.css') !!}
30+
{!! Theme::js('js/vendor/sweetalert/sweetalert.min.js') !!}
31+
@endsection
32+
2733
@section('content')
2834
<div class="col-md-12">
2935
<table class="table table-bordered table-hover">
@@ -61,6 +67,43 @@
6167
<script>
6268
$(document).ready(function () {
6369
$('#sidebar_links').find('a[href="/account/api"]').addClass('active');
70+
$('[data-action="delete"]').click(function (event) {
71+
var self = $(this);
72+
event.preventDefault();
73+
swal({
74+
type: 'error',
75+
title: 'Revoke API Key',
76+
text: 'Once this API key is revoked any applications currently using it will stop working.',
77+
showCancelButton: true,
78+
allowOutsideClick: true,
79+
closeOnConfirm: false,
80+
confirmButtonText: 'Revoke',
81+
confirmButtonColor: '#d9534f',
82+
showLoaderOnConfirm: true
83+
}, function () {
84+
$.ajax({
85+
method: 'DELETE',
86+
url: '{{ route('account.api') }}/revoke/' + self.data('attr'),
87+
headers: {
88+
'X-CSRF-TOKEN': '{{ csrf_token() }}'
89+
}
90+
}).done(function (data) {
91+
swal({
92+
type: 'success',
93+
title: '',
94+
text: 'API Key has been revoked.'
95+
});
96+
self.parent().parent().slideUp();
97+
}).fail(function (jqXHR) {
98+
console.error(jqXHR);
99+
swal({
100+
type: 'error',
101+
title: 'Whoops!',
102+
text: 'An error occured while attempting to revoke this key.'
103+
});
104+
});
105+
});
106+
});
64107
});
65108
</script>
66109
@endsection

0 commit comments

Comments
 (0)