diff --git a/CHANGELOG.md b/CHANGELOG.md index b3be6bf7b..d670a3ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,13 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. * Return node configuration from remote API by using `/api/nodes/{id}/config` endpoint. Only accepts SSL connections. * Support for filtering servers within Admin CP to narrow down results by name, email, allocation, or defined fields. * Setup scripts (user, mail, env) now support argument flags for use in containers and other non-terminal environments. +* New API endpoints for individual users to control their servers with at `/api/me/*`. ### Changed * Creating a user, server, or node now returns `HTTP/1.1 200` and a JSON element with the user/server/node's ID. * Environment setting script is much more user friendly and does not require an excessive amount of clicking and typing. * File upload method switched from BinaryJS to Socket.io implementation to fix bugs as well as be a little speedier and allow upload throttling. +* `Server::getbyUUID()` now accepts either the `uuidShort` or full-length `uuid` for server identification. ## v0.5.0-pre.2 (Bodacious Boreopterus) diff --git a/app/Http/Controllers/API/User/InfoController.php b/app/Http/Controllers/API/User/InfoController.php new file mode 100644 index 000000000..2e2e3a03b --- /dev/null +++ b/app/Http/Controllers/API/User/InfoController.php @@ -0,0 +1,58 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +namespace Pterodactyl\Http\Controllers\API\User; + +use Auth; +use Dingo; +use Pterodactyl\Models; +use Illuminate\Http\Request; + +use Pterodactyl\Http\Controllers\API\BaseController; + +class InfoController extends BaseController +{ + public function me(Request $request) + { + $servers = Models\Server::getUserServers(); + $response = []; + + foreach($servers as &$server) { + $response = array_merge($response, [[ + 'id' => $server->uuidShort, + 'uuid' => $server->uuid, + 'name' => $server->name, + 'node' => $server->nodeName, + 'ip' => [ + 'set' => $server->ip, + 'alias' => $server->ip_alias + ], + 'port' => $server->port, + 'service' => $server->a_serviceName, + 'option' => $server->a_serviceOptionName + ]]); + } + + return $response; + } +} diff --git a/app/Http/Controllers/API/User/ServerController.php b/app/Http/Controllers/API/User/ServerController.php new file mode 100644 index 000000000..613f90582 --- /dev/null +++ b/app/Http/Controllers/API/User/ServerController.php @@ -0,0 +1,117 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +namespace Pterodactyl\Http\Controllers\API\User; + +use Auth; +use Log; +use Pterodactyl\Models; +use Illuminate\Http\Request; + +use Pterodactyl\Http\Controllers\API\BaseController; + +class ServerController extends BaseController +{ + + public function info(Request $request, $uuid) + { + $server = Models\Server::getByUUID($uuid); + $node = Models\Node::findOrFail($server->node); + $client = Models\Node::guzzleRequest($node->id); + + try { + $response = $client->request('GET', '/server', [ + 'headers' => [ + 'X-Access-Token' => $server->daemonSecret, + 'X-Access-Server' => $server->uuid + ] + ]); + + $json = json_decode($response->getBody()); + $daemon = [ + 'status' => $json->status, + 'stats' => $json->proc, + 'query' => $json->query + ]; + } catch (\Exception $ex) { + $daemon = [ + 'error' => 'An error was encountered while trying to connect to the daemon to collece information. It might be offline.' + ]; + Log::error($ex); + } + + $allocations = Models\Allocation::select('id', 'ip', 'port', 'ip_alias as alias')->where('assigned_to', $server->id)->get(); + foreach($allocations as &$allocation) { + $allocation->default = ($allocation->id === $server->allocation); + unset($allocation->id); + } + return [ + 'uuidShort' => $server->uuidShort, + 'uuid' => $server->uuid, + 'name' => $server->name, + 'node' => $node->name, + 'limits' => [ + 'memory' => $server->memory, + 'swap' => $server->swap, + 'disk' => $server->disk, + 'io' => $server->io, + 'cpu' => $server->cpu, + 'oom_disabled' => (bool) $server->oom_disabled + ], + 'allocations' => $allocations, + 'sftp' => [ + 'username' => (Auth::user()->can('view-sftp', $server)) ? $server->username : null + ], + 'daemon' => [ + 'token' => ($request->secure()) ? $server->daemonSecret : false, + 'response' => $daemon + ] + ]; + } + + public function power(Request $request, $uuid) + { + $server = Models\Server::getByUUID($uuid); + $node = Models\Node::getByID($server->node); + $client = Models\Node::guzzleRequest($server->node); + + Auth::user()->can('power-' . $request->input('action'), $server); + + $res = $client->request('PUT', '/server/power', [ + 'headers' => [ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->daemonSecret + ], + 'exceptions' => false, + 'json' => [ + 'action' => $request->input('action') + ] + ]); + + if ($res->getStatusCode() !== 204) { + return $this->response->error(json_decode($res->getBody())->error, $res->getStatusCode()); + } + + return $this->response->noContent(); + } +} diff --git a/app/Http/Controllers/Admin/APIController.php b/app/Http/Controllers/Base/APIController.php similarity index 64% rename from app/Http/Controllers/Admin/APIController.php rename to app/Http/Controllers/Base/APIController.php index 0d1684484..da9a20d78 100644 --- a/app/Http/Controllers/Admin/APIController.php +++ b/app/Http/Controllers/Base/APIController.php @@ -2,6 +2,7 @@ /** * Pterodactyl - Panel * Copyright (c) 2015 - 2016 Dane Everitt + * Some Modifications (c) 2015 Dylan Seidt * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,74 +22,62 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -namespace Pterodactyl\Http\Controllers\Admin; +namespace Pterodactyl\Http\Controllers\Base; use Alert; use Log; use Pterodactyl\Models; -use Pterodactyl\Repositories\APIRepository; -use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\APIRepository; use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; use Illuminate\Http\Request; class APIController extends Controller { - - public function __construct() + public function index(Request $request) { - // - } - - public function getIndex(Request $request) - { - $keys = Models\APIKey::all(); + $keys = Models\APIKey::where('user', $request->user()->id)->get(); foreach($keys as &$key) { $key->permissions = Models\APIPermission::where('key_id', $key->id)->get(); } - return view('admin.api.index', [ + return view('base.api.index', [ 'keys' => $keys ]); } - public function getNew(Request $request) + public function new(Request $request) { - return view('admin.api.new'); + return view('base.api.new'); } - public function postNew(Request $request) + public function save(Request $request) { try { - $api = new APIRepository; - $secret = $api->new($request->except(['_token'])); - // Alert::info('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

Secret: ' . $secret . '')->flash(); - Alert::info("")->flash(); - return redirect()->route('admin.api'); + $repo = new APIRepository($request->user()); + $secret = $repo->new($request->except(['_token'])); + 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.

' . $secret . '')->flash(); + return redirect()->route('account.api'); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.api.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('account.api.new')->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); Alert::danger('An unhandled exception occured while attempting to add this API key.')->flash(); } - return redirect()->route('admin.api.new')->withInput(); + return redirect()->route('account.api.new')->withInput(); } - public function deleteRevokeKey(Request $request, $key) + public function revoke(Request $request, $key) { try { - $api = new APIRepository; - $api->revoke($key); + $repo = new APIRepository($request->user()); + $repo->revoke($key); return response('', 204); } catch (\Exception $ex) { return response()->json([ @@ -96,5 +85,4 @@ class APIController extends Controller ], 503); } } - } diff --git a/app/Http/Controllers/Base/AccountController.php b/app/Http/Controllers/Base/AccountController.php new file mode 100644 index 000000000..c79063706 --- /dev/null +++ b/app/Http/Controllers/Base/AccountController.php @@ -0,0 +1,108 @@ + + * Some Modifications (c) 2015 Dylan Seidt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +namespace Pterodactyl\Http\Controllers\Base; + +use Alert; + +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; + +use Illuminate\Http\Request; + +class AccountController extends Controller +{ + /** + * Display base account information page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request) + { + return view('base.account'); + } + + /** + * Update an account email. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function email(Request $request) + { + + $this->validate($request, [ + 'new_email' => 'required|email', + 'password' => 'required' + ]); + + $user = $request->user(); + + if (!password_verify($request->input('password'), $user->password)) { + Alert::danger('The password provided was not valid for this account.')->flash(); + return redirect()->route('account'); + } + + $user->email = $request->input('new_email'); + $user->save(); + + Alert::success('Your email address has successfully been updated.')->flash(); + return redirect()->route('account'); + + } + + /** + * Update an account password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function password(Request $request) + { + + $this->validate($request, [ + 'current_password' => 'required', + 'new_password' => 'required|confirmed|different:current_password|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'new_password_confirmation' => 'required' + ]); + + $user = $request->user(); + + if (!password_verify($request->input('current_password'), $user->password)) { + Alert::danger('The password provided was not valid for this account.')->flash(); + return redirect()->route('account'); + } + + try { + $user->setPassword($request->input('new_password')); + Alert::success('Your password has successfully been updated.')->flash(); + } catch (DisplayException $e) { + Alert::danger($e->getMessage())->flash(); + } + + return redirect()->route('account'); + + } +} diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index d6e1513fd..bed1cb988 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -24,15 +24,9 @@ */ namespace Pterodactyl\Http\Controllers\Base; -use Auth; -use Hash; -use Google2FA; -use Alert; - -use Pterodactyl\Models; -use Pterodactyl\Exceptions\DisplayException; - +use Pterodactyl\Models\Server; use Pterodactyl\Http\Controllers\Controller; + use Illuminate\Http\Request; class IndexController extends Controller @@ -55,7 +49,7 @@ class IndexController extends Controller public function getIndex(Request $request) { return view('base.index', [ - 'servers' => Models\Server::getUserServers(10), + 'servers' => Server::getUserServers(10), ]); } @@ -71,169 +65,4 @@ class IndexController extends Controller return str_random($length); } - /** - * Returns Security Management Page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View - */ - public function getAccountSecurity(Request $request) - { - return view('base.security', [ - 'sessions' => Models\Session::where('user_id', Auth::user()->id)->get() - ]); - } - - /** - * Generates TOTP Secret and returns popup data for user to verify - * that they can generate a valid response. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View - */ - public function putAccountTotp(Request $request) - { - - $user = $request->user(); - - $user->totp_secret = Google2FA::generateSecretKey(); - $user->save(); - - return response()->json([ - 'qrImage' => Google2FA::getQRCodeGoogleUrl( - 'Pterodactyl', - $user->email, - $user->totp_secret - ), - 'secret' => $user->totp_secret - ]); - - } - - /** - * Verifies that 2FA token recieved is valid and will work on the account. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function postAccountTotp(Request $request) - { - - if (!$request->has('token')) { - return response(null, 500); - } - - $user = $request->user(); - if($user->toggleTotp($request->input('token'))) { - return response('true'); - } - - return response('false'); - - } - - /** - * Disables TOTP on an account. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function deleteAccountTotp(Request $request) - { - - if (!$request->has('token')) { - Alert::danger('Missing required `token` field in request.')->flash(); - return redirect()->route('account.totp'); - } - - $user = $request->user(); - if($user->toggleTotp($request->input('token'))) { - return redirect()->route('account.totp'); - } - - Alert::danger('The TOTP token provided was invalid.')->flash(); - return redirect()->route('account.totp'); - - } - - /** - * Display base account information page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View - */ - public function getAccount(Request $request) - { - return view('base.account'); - } - - /** - * Update an account email. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function postAccountEmail(Request $request) - { - - $this->validate($request, [ - 'new_email' => 'required|email', - 'password' => 'required' - ]); - - $user = $request->user(); - - if (!password_verify($request->input('password'), $user->password)) { - Alert::danger('The password provided was not valid for this account.')->flash(); - return redirect()->route('account'); - } - - $user->email = $request->input('new_email'); - $user->save(); - - Alert::success('Your email address has successfully been updated.')->flash(); - return redirect()->route('account'); - - } - - /** - * Update an account password. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function postAccountPassword(Request $request) - { - - $this->validate($request, [ - 'current_password' => 'required', - 'new_password' => 'required|confirmed|different:current_password|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', - 'new_password_confirmation' => 'required' - ]); - - $user = $request->user(); - - if (!password_verify($request->input('current_password'), $user->password)) { - Alert::danger('The password provided was not valid for this account.')->flash(); - return redirect()->route('account'); - } - - try { - $user->setPassword($request->input('new_password')); - Alert::success('Your password has successfully been updated.')->flash(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } - - return redirect()->route('account'); - - } - - public function getRevokeSession(Request $request, $id) - { - $session = Models\Session::where('id', $id)->where('user_id', Auth::user()->id)->firstOrFail(); - $session->delete(); - return redirect()->route('account.security'); - } - } diff --git a/app/Http/Controllers/Base/SecurityController.php b/app/Http/Controllers/Base/SecurityController.php new file mode 100644 index 000000000..dec2f3fd1 --- /dev/null +++ b/app/Http/Controllers/Base/SecurityController.php @@ -0,0 +1,130 @@ + + * Some Modifications (c) 2015 Dylan Seidt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +namespace Pterodactyl\Http\Controllers\Base; + +use Google2FA; +use Alert; + +use Pterodactyl\Models\Session; +use Pterodactyl\Http\Controllers\Controller; + +use Illuminate\Http\Request; + +class SecurityController extends Controller +{ + + /** + * Returns Security Management Page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request) + { + return view('base.security', [ + 'sessions' => Session::where('user_id', $request->user()->id)->get() + ]); + } + + /** + * Generates TOTP Secret and returns popup data for user to verify + * that they can generate a valid response. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function generateTotp(Request $request) + { + + $user = $request->user(); + + $user->totp_secret = Google2FA::generateSecretKey(); + $user->save(); + + return response()->json([ + 'qrImage' => Google2FA::getQRCodeGoogleUrl( + 'Pterodactyl', + $user->email, + $user->totp_secret + ), + 'secret' => $user->totp_secret + ]); + + } + + /** + * Verifies that 2FA token recieved is valid and will work on the account. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function setTotp(Request $request) + { + + if (!$request->has('token')) { + return response(null, 500); + } + + $user = $request->user(); + if($user->toggleTotp($request->input('token'))) { + return response('true'); + } + + return response('false'); + + } + + /** + * Disables TOTP on an account. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function disableTotp(Request $request) + { + + if (!$request->has('token')) { + Alert::danger('Missing required `token` field in request.')->flash(); + return redirect()->route('account.totp'); + } + + $user = $request->user(); + if($user->toggleTotp($request->input('token'))) { + return redirect()->route('account.security'); + } + + Alert::danger('The TOTP token provided was invalid.')->flash(); + return redirect()->route('account.security'); + + } + + public function revoke(Request $request, $id) + { + $session = Session::where('id', $id)->where('user_id', $request->user()->id)->firstOrFail(); + $session->delete(); + return redirect()->route('account.security'); + } + +} diff --git a/app/Http/Middleware/APISecretToken.php b/app/Http/Middleware/APISecretToken.php index a78728b34..fab4d177d 100755 --- a/app/Http/Middleware/APISecretToken.php +++ b/app/Http/Middleware/APISecretToken.php @@ -23,12 +23,15 @@ */ namespace Pterodactyl\Http\Middleware; +use Auth; use Crypt; +use Config; use IPTools\IP; use IPTools\Range; use Pterodactyl\Models\APIKey; use Pterodactyl\Models\APIPermission; +use Pterodactyl\Models\User; use Pterodactyl\Services\APILogService; use Illuminate\Http\Request; @@ -51,7 +54,7 @@ class APISecretToken extends Authorization public function __construct() { - // + Config::set('session.driver', 'array'); } public function getAuthorizationMethod() @@ -90,16 +93,18 @@ class APISecretToken extends Authorization } } - foreach(APIPermission::where('key_id', $key->id)->get() as &$row) { - if ($row->permission === '*' || $row->permission === $request->route()->getName()) { - $this->permissionAllowed = true; - continue; - } + $permission = APIPermission::where('key_id', $key->id)->where('permission', $request->route()->getName()); + + // Suport Wildcards + if (starts_with($request->route()->getName(), 'api.user')) { + $permission->orWhere('permission', 'api.user.*'); + } else if(starts_with($request->route()->getName(), 'api.admin')) { + $permission->orWhere('permission', 'api.admin.*'); } - if (!$this->permissionAllowed) { - APILogService::log($request, 'You do not have permission to access this resource.'); - throw new AccessDeniedHttpException('You do not have permission to access this resource.'); + if (!$permission->first()) { + APILogService::log($request, 'You do not have permission to access this resource. This API Key requires the ' . $request->route()->getName() . ' permission node.'); + throw new AccessDeniedHttpException('You do not have permission to access this resource. This API Key requires the ' . $request->route()->getName() . ' permission node.'); } } @@ -118,7 +123,7 @@ class APISecretToken extends Authorization // Log the Route Access APILogService::log($request, null, true); - return true; + return Auth::loginUsingId($key->user); } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 97aa64965..be7b74e42 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -13,5 +13,6 @@ class VerifyCsrfToken extends BaseVerifier */ protected $except = [ 'remote/*', + 'api/*' ]; } diff --git a/app/Http/Routes/APIRoutes.php b/app/Http/Routes/APIRoutes.php index 15fcab78e..33d3d7177 100755 --- a/app/Http/Routes/APIRoutes.php +++ b/app/Http/Routes/APIRoutes.php @@ -32,33 +32,50 @@ class APIRoutes public function map(Router $router) { $api = app('Dingo\Api\Routing\Router'); - $api->version('v1', ['middleware' => 'api.auth'], function ($api) { + $api->version('v1', ['prefix' => 'api/me', 'middleware' => 'api.auth'], function ($api) { + $api->get('/', [ + 'as' => 'api.user.me', + 'uses' => 'Pterodactyl\Http\Controllers\API\User\InfoController@me' + ]); + + $api->get('/server/{uuid}', [ + 'as' => 'api.user.server', + 'uses' => 'Pterodactyl\Http\Controllers\API\User\ServerController@info' + ]); + + $api->put('/server/{uuid}', [ + 'as' => 'api.user.server.power', + 'uses' => 'Pterodactyl\Http\Controllers\API\User\ServerController@power' + ]); + }); + + $api->version('v1', ['prefix' => 'api', 'middleware' => 'api.auth'], function ($api) { /** * User Routes */ $api->get('users', [ - 'as' => 'api.users.list', + 'as' => 'api.admin.users.list', 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@list' ]); $api->post('users', [ - 'as' => 'api.users.create', + 'as' => 'api.admin.users.create', 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@create' ]); $api->get('users/{id}', [ - 'as' => 'api.users.view', + 'as' => 'api.admin.users.view', 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@view' ]); $api->patch('users/{id}', [ - 'as' => 'api.users.update', + 'as' => 'api.admin.users.update', 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@update' ]); $api->delete('users/{id}', [ - 'as' => 'api.users.delete', + 'as' => 'api.admin.users.delete', 'uses' => 'Pterodactyl\Http\Controllers\API\UserController@delete' ]); @@ -66,42 +83,42 @@ class APIRoutes * Server Routes */ $api->get('servers', [ - 'as' => 'api.servers.list', + 'as' => 'api.admin.servers.list', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@list' ]); $api->post('servers', [ - 'as' => 'api.servers.create', + 'as' => 'api.admin.servers.create', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@create' ]); $api->get('servers/{id}', [ - 'as' => 'api.servers.view', + 'as' => 'api.admin.servers.view', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@view' ]); $api->patch('servers/{id}/config', [ - 'as' => 'api.servers.config', + 'as' => 'api.admin.servers.config', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@config' ]); $api->patch('servers/{id}/build', [ - 'as' => 'api.servers.build', + 'as' => 'api.admin.servers.build', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@build' ]); $api->post('servers/{id}/suspend', [ - 'as' => 'api.servers.suspend', + 'as' => 'api.admin.servers.suspend', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@suspend' ]); $api->post('servers/{id}/unsuspend', [ - 'as' => 'api.servers.unsuspend', + 'as' => 'api.admin.servers.unsuspend', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@unsuspend' ]); $api->delete('servers/{id}/{force?}', [ - 'as' => 'api.servers.delete', + 'as' => 'api.admin.servers.delete', 'uses' => 'Pterodactyl\Http\Controllers\API\ServerController@delete' ]); @@ -109,32 +126,32 @@ class APIRoutes * Node Routes */ $api->get('nodes', [ - 'as' => 'api.nodes.list', + 'as' => 'api.admin.nodes.list', 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@list' ]); $api->post('nodes', [ - 'as' => 'api.nodes.create', + 'as' => 'api.admin.nodes.create', 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@create' ]); $api->get('nodes/allocations', [ - 'as' => 'api.nodes.allocations', + 'as' => 'api.admin.nodes.allocations', 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@allocations' ]); $api->get('nodes/{id}', [ - 'as' => 'api.nodes.view', + 'as' => 'api.admin.nodes.view', 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@view' ]); $api->get('nodes/{id}/config', [ - 'as' => 'api.nodes.view', + 'as' => 'api.admin.nodes.view', 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@config' ]); $api->delete('nodes/{id}', [ - 'as' => 'api.nodes.delete', + 'as' => 'api.admin.nodes.delete', 'uses' => 'Pterodactyl\Http\Controllers\API\NodeController@delete' ]); @@ -142,7 +159,7 @@ class APIRoutes * Location Routes */ $api->get('locations', [ - 'as' => 'api.locations.list', + 'as' => 'api.admin.locations.list', 'uses' => 'Pterodactyl\Http\Controllers\API\LocationController@list' ]); @@ -150,12 +167,12 @@ class APIRoutes * Service Routes */ $api->get('services', [ - 'as' => 'api.services.list', + 'as' => 'api.admin.services.list', 'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@list' ]); $api->get('services/{id}', [ - 'as' => 'api.services.view', + 'as' => 'api.admin.services.view', 'uses' => 'Pterodactyl\Http\Controllers\API\ServiceController@view' ]); diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index a1d1956e0..3f6e73bcb 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -303,32 +303,6 @@ class AdminRoutes { ]); }); - // API Routes - $router->group([ - 'prefix' => 'admin/api', - 'middleware' => [ - 'auth', - 'admin', - 'csrf' - ] - ], function () use ($router) { - $router->get('/', [ - 'as' => 'admin.api', - 'uses' => 'Admin\APIController@getIndex' - ]); - $router->get('/new', [ - 'as' => 'admin.api.new', - 'uses' => 'Admin\APIController@getNew' - ]); - $router->post('/new', [ - 'uses' => 'Admin\APIController@postNew' - ]); - $router->delete('/revoke/{key?}', [ - 'as' => 'admin.api.revoke', - 'uses' => 'Admin\APIController@deleteRevokeKey' - ]); - }); - // Database Routes $router->group([ 'prefix' => 'admin/databases', diff --git a/app/Http/Routes/BaseRoutes.php b/app/Http/Routes/BaseRoutes.php index 26e488ac2..6841ea1b6 100644 --- a/app/Http/Routes/BaseRoutes.php +++ b/app/Http/Routes/BaseRoutes.php @@ -51,21 +51,46 @@ class BaseRoutes { // Account Routes $router->group([ - 'profix' => 'account', + 'prefix' => 'account', 'middleware' => [ 'auth', 'csrf' ] ], function () use ($router) { - $router->get('account', [ + $router->get('/', [ 'as' => 'account', - 'uses' => 'Base\IndexController@getAccount' + 'uses' => 'Base\AccountController@index' ]); - $router->post('/account/password', [ - 'uses' => 'Base\IndexController@postAccountPassword' + $router->post('/password', [ + 'uses' => 'Base\AccountController@password' ]); - $router->post('/account/email', [ - 'uses' => 'Base\IndexController@postAccountEmail' + $router->post('/email', [ + 'uses' => 'Base\AccountController@email' + ]); + }); + + // API Management Routes + $router->group([ + 'prefix' => 'account/api', + 'middleware' => [ + 'auth', + 'csrf' + ] + ], function () use ($router) { + $router->get('/', [ + 'as' => 'account.api', + 'uses' => 'Base\APIController@index' + ]); + $router->get('/new', [ + 'as' => 'account.api.new', + 'uses' => 'Base\APIController@new' + ]); + $router->post('/new', [ + 'uses' => 'Base\APIController@save' + ]); + + $router->delete('/revoke/{key}', [ + 'uses' => 'Base\APIController@revoke' ]); }); @@ -79,20 +104,20 @@ class BaseRoutes { ], function () use ($router) { $router->get('/', [ 'as' => 'account.security', - 'uses' => 'Base\IndexController@getAccountSecurity' + 'uses' => 'Base\SecurityController@index' ]); $router->get('/revoke/{id}', [ 'as' => 'account.security.revoke', - 'uses' => 'Base\IndexController@getRevokeSession' + 'uses' => 'Base\SecurityController@revoke' ]); - $router->put('/', [ - 'uses' => 'Base\IndexController@putAccountTotp' + $router->put('/totp', [ + 'uses' => 'Base\SecurityController@generateTotp' ]); - $router->post('/', [ - 'uses' => 'Base\IndexController@postAccountTotp' + $router->post('/totp', [ + 'uses' => 'Base\SecurityController@setTotp' ]); - $router->delete('/', [ - 'uses' => 'Base\IndexController@deleteAccountTotp' + $router->delete('/totp', [ + 'uses' => 'Base\SecurityController@disableTotp' ]); }); diff --git a/app/Http/Routes/ServerRoutes.php b/app/Http/Routes/ServerRoutes.php index 9624a0d4e..5916287e2 100644 --- a/app/Http/Routes/ServerRoutes.php +++ b/app/Http/Routes/ServerRoutes.php @@ -28,6 +28,7 @@ use Illuminate\Routing\Router; class ServerRoutes { public function map(Router $router) { + $router->group([ 'prefix' => 'server/{server}', 'middleware' => [ @@ -36,6 +37,7 @@ class ServerRoutes { 'csrf' ] ], function ($server) use ($router) { + // Index View for Server $router->get('/', [ 'as' => 'server.index', diff --git a/app/Models/Server.php b/app/Models/Server.php index e0665ee13..d9c2f958a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -24,10 +24,11 @@ namespace Pterodactyl\Models; use Auth; - use Pterodactyl\Models\Subuser; use Illuminate\Database\Eloquent\Model; +use Pterodactyl\Exceptions\DisplayException; + class Server extends Model { @@ -101,7 +102,7 @@ class Server extends Model * @param Illuminate\Database\Eloquent\Model\Server $server * @return string */ - protected static function getUserDaemonSecret(Server $server) + public static function getUserDaemonSecret(Server $server) { if (self::$user->id === $server->owner || self::$user->root_admin === 1) { @@ -133,9 +134,13 @@ class Server extends Model 'locations.short as a_locationShort', 'allocations.ip', 'allocations.ip_alias', - 'allocations.port' + 'allocations.port', + 'services.name as a_serviceName', + 'service_options.name as a_serviceOptionName' )->join('nodes', 'servers.node', '=', 'nodes.id') ->join('locations', 'nodes.location', '=', 'locations.id') + ->join('services', 'servers.service', '=', 'services.id') + ->join('service_options', 'servers.option', '=', 'service_options.id') ->join('allocations', 'servers.allocation', '=', 'allocations.id'); if (self::$user->root_admin !== 1) { @@ -167,7 +172,8 @@ class Server extends Model $query = self::select('servers.*', 'services.file as a_serviceFile') ->join('services', 'services.id', '=', 'servers.service') - ->where('uuidShort', $uuid); + ->where('uuidShort', $uuid) + ->orWhere('uuid', $uuid); if (self::$user->root_admin !== 1) { $query->whereIn('servers.id', Subuser::accessServers()); @@ -175,6 +181,10 @@ class Server extends Model $result = $query->first(); + if (!$result) { + throw new DisplayException('No server was found belonging to this user.'); + } + if(!is_null($result)) { $result->daemonSecret = self::getUserDaemonSecret($result); } diff --git a/app/Repositories/APIRepository.php b/app/Repositories/APIRepository.php index b92a7cab0..c093c86ed 100644 --- a/app/Repositories/APIRepository.php +++ b/app/Repositories/APIRepository.php @@ -23,6 +23,7 @@ */ namespace Pterodactyl\Repositories; +use Auth; use DB; use Crypt; use Validator; @@ -40,38 +41,51 @@ class APIRepository * @var array */ protected $permissions = [ - '*', + 'admin' => [ + '*', - // User Management Routes - 'api.users.list', - 'api.users.create', - 'api.users.view', - 'api.users.update', - 'api.users.delete', + // User Management Routes + 'users.list', + 'users.create', + 'users.view', + 'users.update', + 'users.delete', - // Server Manaement Routes - 'api.servers.list', - 'api.servers.create', - 'api.servers.view', - 'api.servers.config', - 'api.servers.build', - 'api.servers.suspend', - 'api.servers.unsuspend', - 'api.servers.delete', + // Server Manaement Routes + 'servers.list', + 'servers.create', + 'servers.view', + 'servers.config', + 'servers.build', + 'servers.suspend', + 'servers.unsuspend', + 'servers.delete', - // Node Management Routes - 'api.nodes.list', - 'api.nodes.create', - 'api.nodes.list', - 'api.nodes.allocations', - 'api.nodes.delete', + // Node Management Routes + 'nodes.list', + 'nodes.create', + 'nodes.list', + 'nodes.allocations', + 'nodes.delete', - // Service Routes - 'api.services.list', - 'api.services.view', + // Service Routes + 'services.list', + 'services.view', - // Location Routes - 'api.locations.list', + // Location Routes + 'locations.list', + + ], + 'user' => [ + '*', + + // Informational + 'me', + + // Server Control + 'server', + 'server.power', + ], ]; /** @@ -80,12 +94,17 @@ class APIRepository */ protected $allowed = []; + protected $user; + /** * Constructor */ - public function __construct() + public function __construct(Models\User $user = null) { - // + $this->user = is_null($user) ? Auth::user() : $user; + if (is_null($this->user)) { + throw new \Exception('Cannot access API Repository without passing a user to __construct().'); + } } /** @@ -101,7 +120,9 @@ class APIRepository public function new(array $data) { $validator = Validator::make($data, [ - 'permissions' => 'required|array' + 'memo' => 'string|max:500', + 'permissions' => 'sometimes|required|array', + 'adminPermissions' => 'sometimes|required|array' ]); $validator->after(function($validator) use ($data) { @@ -125,31 +146,62 @@ class APIRepository } DB::beginTransaction(); - try { - $secretKey = str_random(16) . '.' . str_random(15); + $secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7); $key = new Models\APIKey; $key->fill([ + 'user' => $this->user->id, 'public' => str_random(16), 'secret' => Crypt::encrypt($secretKey), - 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed) + 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed), + 'memo' => $data['memo'], + 'expires_at' => null ]); $key->save(); - foreach($data['permissions'] as $permission) { - if (in_array($permission, $this->permissions)) { - $model = new Models\APIPermission; - $model->fill([ - 'key_id' => $key->id, - 'permission' => $permission - ]); - $model->save(); + $totalPermissions = 0; + if (isset($data['permissions'])) { + foreach($data['permissions'] as $permNode) { + if (!strpos($permNode, ':')) continue; + + list($toss, $permission) = explode(':', $permNode); + if (in_array($permission, $this->permissions['user'])) { + $totalPermissions++; + $model = new Models\APIPermission; + $model->fill([ + 'key_id' => $key->id, + 'permission' => 'api.user.' . $permission + ]); + $model->save(); + } } } + if ($this->user->root_admin === 1 && isset($data['adminPermissions'])) { + foreach($data['adminPermissions'] as $permNode) { + if (!strpos($permNode, ':')) continue; + + list($toss, $permission) = explode(':', $permNode); + if (in_array($permission, $this->permissions['admin'])) { + $totalPermissions++; + $model = new Models\APIPermission; + $model->fill([ + 'key_id' => $key->id, + 'permission' => 'api.admin.' . $permission + ]); + $model->save(); + } + } + } + + if ($totalPermissions < 1) { + throw new DisplayException('No valid permissions were passed.'); + } + DB::commit(); return $secretKey; } catch (\Exception $ex) { + DB::rollBack(); throw $ex; } @@ -169,7 +221,7 @@ class APIRepository DB::beginTransaction(); try { - $model = Models\APIKey::where('public', $key)->firstOrFail(); + $model = Models\APIKey::where('public', $key)->where('user', $this->user->id)->firstOrFail(); $permissions = Models\APIPermission::where('key_id', $model->id)->delete(); $model->delete(); diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index f90bab484..01cad4269 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -162,7 +162,8 @@ class UserRepository throw new DisplayException('Cannot delete a user with active servers attached to thier account.'); } - if(Auth::user()->id === $id) { + // @TODO: this should probably be checked outside of this method because we won't always have Auth::user() + if(!is_null(Auth::user()) && Auth::user()->id === $id) { throw new DisplayException('Cannot delete your own account.'); } diff --git a/config/app.php b/config/app.php index b28fb0a69..6dea77b4f 100644 --- a/config/app.php +++ b/config/app.php @@ -188,6 +188,8 @@ return [ 'Crypt' => Illuminate\Support\Facades\Crypt::class, 'DB' => Illuminate\Support\Facades\DB::class, 'Debugbar' => Barryvdh\Debugbar\Facade::class, + 'Dingo' => Dingo\Api\Facade\API::class, + 'DingoRoute'=> Dingo\Api\Facade\Route::class, 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'File' => Illuminate\Support\Facades\File::class, diff --git a/config/session.php b/config/session.php index f1b004214..59ad9182f 100644 --- a/config/session.php +++ b/config/session.php @@ -29,9 +29,9 @@ return [ | */ - 'lifetime' => 120, + 'lifetime' => 30, - 'expire_on_close' => false, + 'expire_on_close' => true, /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2016_10_14_164802_update_api_keys.php b/database/migrations/2016_10_14_164802_update_api_keys.php new file mode 100644 index 000000000..80d7f9501 --- /dev/null +++ b/database/migrations/2016_10_14_164802_update_api_keys.php @@ -0,0 +1,36 @@ +unsignedInteger('user')->after('id'); + $table->text('memo')->after('allowed_ips')->nullable(); + $table->timestamp('expires_at')->after('memo')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->dropColumn('user'); + $table->dropColumn('memo'); + $table->dropColumn('expires_at'); + }); + } +} diff --git a/public/themes/default/css/pterodactyl.css b/public/themes/default/css/pterodactyl.css index d3f5d7241..c25130929 100755 --- a/public/themes/default/css/pterodactyl.css +++ b/public/themes/default/css/pterodactyl.css @@ -263,3 +263,11 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default. .left-icon + td { border-left: 0px !important; } + +.fuelux .wizard .step-content > .alert { + margin-bottom: 0 !important; +} + +.fuelux .wizard .steps-container { + background-color: #eee; +} diff --git a/resources/views/admin/api/new.blade.php b/resources/views/admin/api/new.blade.php deleted file mode 100644 index 11f6b22b9..000000000 --- a/resources/views/admin/api/new.blade.php +++ /dev/null @@ -1,213 +0,0 @@ -{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - API Management -@endsection - -@section('content') -
- -

Add New API Key


-
-
-
-
-
-
-
-
-
-

User Management


-
-
-
-
-
-
-
-
-
-
-
-
-

Server Management


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

Node Management


-
-
-
-
-
-
-
-
-
-
-
-
-

Service Management


-
-
-
-
-

Location Management


-
-
-
-
-
-
-
- -
- -

Enter a line delimitated list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.

-
-
-
-
-
- {!! csrf_field() !!} - -
-
-
-
-
- -@endsection diff --git a/resources/views/admin/api/index.blade.php b/resources/views/base/api/index.blade.php similarity index 73% rename from resources/views/admin/api/index.blade.php rename to resources/views/base/api/index.blade.php index 205b6e88f..0fcc69a79 100644 --- a/resources/views/admin/api/index.blade.php +++ b/resources/views/base/api/index.blade.php @@ -17,60 +17,56 @@ {{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} {{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} {{-- SOFTWARE. --}} -@extends('layouts.admin') +@extends('layouts.master') -@section('title') - API Management +@section('title', 'API Access') + +@section('sidebar-server') +@endsection + +@section('scripts') + @parent + {!! Theme::css('css/vendor/sweetalert/sweetalert.min.css') !!} + {!! Theme::js('js/vendor/sweetalert/sweetalert.min.js') !!} @endsection @section('content')
- -

API Key Information


- - - + + + @foreach ($keys as $key) - + - + + - - @endforeach
API Public KeyAllowed IPsPermissionsPublic KeyMemo CreatedExpires
{{ $key->public }} - @if (is_null($key->allowed_ips)) - * + {{ $key->memo }}{{ (new Carbon($key->created_at))->toDayDateTimeString() }} + @if(is_null($key->expires_at)) + Never @else - @foreach(json_decode($key->allowed_ips) as $ip) - {{ $ip }}
- @endforeach + {{ (new Carbon($key->expires_at))->toDayDateTimeString() }} @endif
- @foreach(json_decode($key->permissions) as &$perm) - {{ $perm->permission }}
- @endforeach -
{{ (new Carbon($key->created_at))->toDayDateTimeString() }}
+@endsection diff --git a/resources/views/base/security.blade.php b/resources/views/base/security.blade.php index b0723043a..48b730555 100644 --- a/resources/views/base/security.blade.php +++ b/resources/views/base/security.blade.php @@ -72,7 +72,7 @@

{{ trans('base.account.totp_disable_help') }}


-
+
{{ trans('base.account.totp_token') }} @@ -155,7 +155,7 @@ $(document).ready(function () { $.ajax({ type: 'PUT', - url: '/account/totp', + url: '/account/security/totp', headers: { 'X-CSRF-Token': '{{ csrf_token() }}' } }).done(function (data) { var image = new Image(); @@ -180,7 +180,7 @@ $(document).ready(function () { $.ajax({ type: 'POST', - url:'/account/totp', + url:'/account/security/totp', headers: { 'X-CSRF-Token': '{{ csrf_token() }}' }, data: { token: $('#totp_token').val() diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 72fc70ff2..2ff4b0713 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -60,7 +60,6 @@ @@ -128,7 +127,6 @@ Management Admin Index General Settings - API Management Database Management
diff --git a/resources/views/layouts/master.blade.php b/resources/views/layouts/master.blade.php index 919cd5191..903ec0a4e 100644 --- a/resources/views/layouts/master.blade.php +++ b/resources/views/layouts/master.blade.php @@ -184,6 +184,7 @@ @@ -237,6 +238,7 @@ {{ trans('pagination.sidebar.account_controls') }} {{ trans('pagination.sidebar.account_settings') }} {{ trans('pagination.sidebar.account_security') }} + API Access {{ trans('pagination.sidebar.servers') }}
@section('sidebar-server') @@ -258,29 +260,25 @@
- @section('resp-errors') - @if (count($errors) > 0) -
+ @if (count($errors) > 0) +
+ + {{ trans('strings.whoops') }}! {{ trans('auth.errorencountered') }}

+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + @foreach (Alert::getMessages() as $type => $messages) + @foreach ($messages as $message) + - @endif - @show - @section('resp-alerts') - @foreach (Alert::getMessages() as $type => $messages) - @foreach ($messages as $message) - - @endforeach @endforeach - @show + @endforeach