Merge pull request #768 from Pterodactyl/feature/auth-controller-cleanup

Push updates to login page, mostly UI enhancements.
This commit is contained in:
Dane Everitt 2017-11-18 18:00:01 -05:00 committed by GitHub
commit 3e8514b966
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 420 additions and 433 deletions

View file

@ -19,6 +19,8 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
### Changed
* Moved Docker image setting to be on the startup management page for a server rather than the details page. This value changes based on the Nest and Egg that are selected.
* Two-Factor authentication tokens are now 32 bytes in length, and are stored encrypted at rest in the database.
* Login page UI has been improved to be more sleek and welcoming to users.
* Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials.
### Added
* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.

View file

@ -1,15 +1,9 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Password;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Events\Auth\FailedPasswordReset;
@ -17,27 +11,8 @@ use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get the response for a failed password reset link.
*
@ -45,12 +20,12 @@ class ForgotPasswordController extends Controller
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
protected function sendResetLinkFailedResponse(Request $request, $response): RedirectResponse
{
// As noted in #358 we will return success even if it failed
// to avoid pointing out that an account does or does not
// exist on the system.
event(new FailedPasswordReset($request->ip(), $request->only('email')));
event(new FailedPasswordReset($request->ip(), $request->input('email')));
return $this->sendResetLinkResponse(Password::RESET_LINK_SENT);
}

View file

@ -1,53 +1,57 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
* Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com>.
*
* 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\Auth;
use Auth;
use Cache;
use Crypt;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Illuminate\Auth\AuthManager;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Auth\Events\Failed;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
const USER_INPUT_FIELD = 'user';
/**
* @var \Illuminate\Auth\AuthManager
*/
private $auth;
/**
* @var \Illuminate\Contracts\Cache\Repository
*/
private $cache;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $config;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/
private $repository;
/**
* @var \PragmaRX\Google2FA\Google2FA
*/
private $google2FA;
/**
* Where to redirect users after login / registration.
*
@ -60,54 +64,54 @@ class LoginController extends Controller
*
* @var int
*/
protected $lockoutTime = 120;
protected $lockoutTime;
/**
* After how many attempts should logins be throttled and locked.
*
* @var int
*/
protected $maxLoginAttempts = 3;
protected $maxLoginAttempts;
/**
* Create a new controller instance.
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'logout']);
}
/**
* Get the failed login response instance.
* LoginController constructor.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
* @param \Illuminate\Auth\AuthManager $auth
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \PragmaRX\Google2FA\Google2FA $google2FA
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
*/
protected function sendFailedLoginResponse(Request $request)
{
$this->incrementLoginAttempts($request);
public function __construct(
AuthManager $auth,
CacheRepository $cache,
ConfigRepository $config,
Encrypter $encrypter,
Google2FA $google2FA,
UserRepositoryInterface $repository
) {
$this->auth = $auth;
$this->cache = $cache;
$this->config = $config;
$this->encrypter = $encrypter;
$this->google2FA = $google2FA;
$this->repository = $repository;
$errors = [$this->username() => trans('auth.failed')];
if ($request->expectsJson()) {
return response()->json($errors, 422);
}
return redirect()->route('auth.login')
->withInput($request->only($this->username(), 'remember'))
->withErrors($errors);
$this->lockoutTime = $this->config->get('auth.lockout.time');
$this->maxLoginAttempts = $this->config->get('auth.lockout.attempts');
}
/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Response\RedirectResponse
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
*/
public function login(Request $request)
{
// Check wether the user identifier is an email address or a username
$checkField = str_contains($request->input('user'), '@') ? 'email' : 'username';
$username = $request->input(self::USER_INPUT_FIELD);
$useColumn = $this->getField($username);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
@ -115,40 +119,27 @@ class LoginController extends Controller
return $this->sendLockoutResponse($request);
}
// Determine if the user even exists.
$user = User::where($checkField, $request->input($this->username()))->first();
if (! $user) {
try {
$user = $this->repository->findFirstWhere([[$useColumn, '=', $username]]);
} catch (RecordNotFoundException $exception) {
return $this->sendFailedLoginResponse($request);
}
// If user uses 2FA, redirect to that page.
$validCredentials = password_verify($request->input('password'), $user->password);
if ($user->use_totp) {
$token = str_random(64);
Cache::put($token, [
'user_id' => $user->id,
'credentials' => Crypt::encrypt(serialize([
$checkField => $request->input($this->username()),
'password' => $request->input('password'),
])),
], 5);
$this->cache->put($token, ['user_id' => $user->id, 'valid_credentials' => $validCredentials], 5);
return redirect()->route('auth.totp')
->with('authentication_token', $token)
->with('remember', $request->has('remember'));
return redirect()->route('auth.totp')->with('authentication_token', $token);
}
$attempt = Auth::attempt([
$checkField => $request->input($this->username()),
'password' => $request->input('password'),
'use_totp' => 0,
], $request->has('remember'));
if ($validCredentials) {
$this->auth->guard()->login($user, true);
if ($attempt) {
return $this->sendLoginResponse($request);
}
// Login failed, send response.
return $this->sendFailedLoginResponse($request);
return $this->sendFailedLoginResponse($request, $user);
}
/**
@ -160,71 +151,96 @@ class LoginController extends Controller
public function totp(Request $request)
{
$token = $request->session()->get('authentication_token');
if (is_null($token) || Auth::user()) {
if (is_null($token) || $this->auth->guard()->user()) {
return redirect()->route('auth.login');
}
return view('auth.totp', [
'verify_key' => $token,
'remember' => $request->session()->get('remember'),
]);
return view('auth.totp', ['verify_key' => $token]);
}
/**
* Handle a TOTP input.
* Handle a login where the user is required to provide a TOTP authentication
* token. In order to add additional layers of security, users are not
* informed of an incorrect password until this stage, forcing them to
* provide a token on each login attempt.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
*/
public function totpCheckpoint(Request $request)
public function loginUsingTotp(Request $request)
{
$G2FA = new Google2FA();
if (is_null($request->input('verify_token'))) {
return $this->sendFailedLoginResponse($request);
}
$cache = Cache::pull($request->input('verify_token'));
$user = User::where('id', $cache['user_id'])->first();
if (! $user || ! $cache) {
$this->sendFailedLoginResponse($request);
}
if (is_null($request->input('2fa_token'))) {
return $this->sendFailedLoginResponse($request);
}
try {
$credentials = unserialize(Crypt::decrypt($cache['credentials']));
} catch (\Illuminate\Contracts\Encryption\DecryptException $ex) {
$cache = $this->cache->pull($request->input('verify_token'), []);
$user = $this->repository->find(array_get($cache, 'user_id', 0));
} catch (RecordNotFoundException $exception) {
return $this->sendFailedLoginResponse($request);
}
if (! $G2FA->verifyKey(Crypt::decrypt($user->totp_secret), $request->input('2fa_token'), 2)) {
event(new \Illuminate\Auth\Events\Failed($user, $credentials));
return $this->sendFailedLoginResponse($request);
if (is_null($request->input('2fa_token')) || ! array_get($cache, 'valid_credentials')) {
return $this->sendFailedLoginResponse($request, $user);
}
$attempt = Auth::attempt($credentials, $request->has('remember'));
if (! $this->google2FA->verifyKey(
$this->encrypter->decrypt($user->totp_secret),
$request->input('2fa_token'),
$this->config->get('pterodactyl.auth.2fa.window')
)) {
return $this->sendFailedLoginResponse($request, $user);
}
$this->auth->guard()->login($user, true);
if ($attempt) {
return $this->sendLoginResponse($request);
}
// Login failed, send response.
return $this->sendFailedLoginResponse($request);
/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null): RedirectResponse
{
$this->incrementLoginAttempts($request);
$this->fireFailedLoginEvent($user, [
$this->getField($request->input(self::USER_INPUT_FIELD)) => $request->input(self::USER_INPUT_FIELD),
]);
$errors = [self::USER_INPUT_FIELD => trans('auth.failed')];
if ($request->expectsJson()) {
return response()->json($errors, 422);
}
return redirect()->route('auth.login')
->withInput($request->only(self::USER_INPUT_FIELD))
->withErrors($errors);
}
/**
* Get the login username to be used by the controller.
* Determine if the user is logging in using an email or username,.
*
* @param string $input
* @return string
*/
public function username()
private function getField(string $input = null): string
{
return 'user';
return str_contains($input, '@') ? 'email' : 'username';
}
/**
* Fire a failed login event.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
*/
private function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = [])
{
event(new Failed($user, $credentials));
}
}

View file

@ -1,69 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Validator;
use Pterodactyl\User;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}

View file

@ -2,23 +2,11 @@
namespace Pterodactyl\Http\Controllers\Auth;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
@ -28,25 +16,17 @@ class ResetPasswordController extends Controller
*/
public $redirectTo = '/';
/**
* Create a new controller instance.
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Return the rules used when validating password reset.
*
* @return array
*/
protected function rules()
protected function rules(): array
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|' . User::PASSWORD_RULES,
'password' => 'required|confirmed|min:8',
];
}
}

View file

@ -1,6 +1,21 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Lockout Configuration
|--------------------------------------------------------------------------
|
| These options are Pterodactyl specific and allow you to configure how
| long a user should be locked out for if they input a username or
| password incorrectly.
|
*/
'lockout' => [
'time' => 120,
'attempts' => 3,
],
/*
|--------------------------------------------------------------------------
| Authentication Defaults

View file

@ -26,42 +26,74 @@
background: #10529f;
}
#login-position-elements {
margin: 25% auto;
}
.login-logo {
color: white;
font-weight: bold;
color: #fff;
font-weight: 400;
}
.login-copyright {
color: white;
color: rgba(255, 255, 255, 0.3);
}
.login-copyright a, .login-copyright a:hover {
color: white;
font-weight: bold;
.login-copyright > a {
color: rgba(255, 255, 255, 0.6);
}
.particles-js-canvas-el {
position: absolute;
}
.login-box, .register-box {
position: absolute;
margin: -180px 0 0 -180px;
left: 50%;
top: 50%;
height: 360px;
width: 360px;
z-index: 100;
}
@media (max-width:768px) {
.login-box {
width: 90%;
margin-top: 20px;
margin: 5%;
left: 0;
width: 100%;
height: 100%;
top: 0;
z-index: -1;
}
.pterodactyl-login-box {
background: rgba(0, 0, 0, 0.25);
border-radius: 3px;
padding: 20px;
}
.pterodactyl-login-input > input {
background: rgba(0, 0, 0, 0.4);
border: 1px solid #000;
border-radius: 2px;
color: #fff;
}
.pterodactyl-login-input > .form-control-feedback {
color: #fff;
}
.pterodactyl-login-button--main {
background: rgba(0, 0, 0, 0.4);
border: 1px solid #000;
border-radius: 2px;
color: #fff;
}
.pterodactyl-login-button--main:hover {
background: rgba(0, 0, 0, 0.7);
border: 1px solid #000;
border-radius: 2px;
color: #fff;
}
.pterodactyl-login-button--left {
background: rgba(255, 255, 255, 0.4);
border: 1px solid rgba(255, 255, 255, 0.6);
border-radius: 2px;
color: #fff;
}
.pterodactyl-login-button--left:hover {
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.8);
border-radius: 2px;
color: #fff;
}
.weight-100 {

View file

@ -10,9 +10,10 @@
@endsection
@section('content')
<div class="login-box-body">
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10">
@if (count($errors) > 0)
<div class="callout callout-danger">
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@lang('auth.auth_error')<br><br>
<ul>
@ -30,29 +31,34 @@
</div>
@endforeach
@endforeach
<p class="login-box-msg">@lang('auth.authentication_required')</p>
<form id="loginForm" action="{{ route('auth.login') }}" method="POST">
<div class="form-group has-feedback">
<input name="user" class="form-control" value="{{ old('user') }}" placeholder="@lang('strings.user_identifier')">
<span class="fa fa-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input type="password" name="password" class="form-control" placeholder="@lang('strings.password')">
<span class="fa fa-lock form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-8">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10 pterodactyl-login-box">
<form id="loginForm" action="{{ route('auth.login') }}" method="POST">
<div class="form-group has-feedback">
<input type="checkbox" name="remember" id="remember" /> <label for="remember" class="weight-300">@lang('auth.remember_me')</label>
<div class="pterodactyl-login-input">
<input type="text" name="user" class="form-control input-lg" value="{{ old('user') }}" required placeholder="@lang('strings.user_identifier')" autofocus>
<span class="fa fa-envelope form-control-feedback fa-lg"></span>
</div>
</div>
<div class="form-group has-feedback">
<div class="pterodactyl-login-input">
<input type="password" name="password" class="form-control input-lg" required placeholder="@lang('strings.password')">
<span class="fa fa-lock form-control-feedback fa-lg"></span>
</div>
</div>
<div class="row">
<div class="col-xs-4">
<a href="{{ route('auth.password') }}"><button type="button" class="btn pterodactyl-login-button--left"><i class="fa fa-life-ring"></i></button></a>
</div>
<div class="col-xs-offset-4 col-xs-4">
{!! csrf_field() !!}
<button type="submit" class="btn btn-primary btn-block btn-flat g-recaptcha" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.sign_in')</button>
<button type="submit" class="btn btn-block g-recaptcha pterodactyl-login-button--main" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.sign_in')</button>
</div>
</div>
</form>
<a href="{{ route('auth.password') }}">@lang('auth.forgot_password')</a><br>
</div>
</div>
@endsection

View file

@ -10,9 +10,10 @@
@endsection
@section('content')
<div class="login-box-body">
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10">
@if (count($errors) > 0)
<div class="callout callout-danger">
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@lang('auth.auth_error')<br><br>
<ul>
@ -23,32 +24,38 @@
</div>
@endif
@if (session('status'))
<div class="callout callout-success">
<div class="alert alert-success">
@lang('auth.email_sent')
</div>
@endif
<p class="login-box-msg">@lang('auth.request_reset_text')</p>
</div>
</div>
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10 pterodactyl-login-box">
<form id="resetForm" action="{{ route('auth.password') }}" method="POST">
<div class="form-group has-feedback">
<input type="email" name="email" class="form-control" value="{{ old('email') }}" autofocus placeholder="@lang('strings.email')">
<span class="fa fa-envelope form-control-feedback"></span>
<div class="pterodactyl-login-input">
<input type="email" name="email" class="form-control input-lg" value="{{ old('email') }}" required placeholder="@lang('strings.email')" autofocus>
<span class="fa fa-envelope form-control-feedback fa-lg"></span>
@if ($errors->has('email'))
<span class="help-block text-red small">
{{ $errors->first('email') }}
</span>
@endif
</div>
</div>
<div class="row">
<div class="col-xs-4">
<a href="{{ route('auth.login') }}"><button type="button" class="btn btn-clear btn-block btn-flat">@lang('strings.login')</button></a>
<a href="{{ route('auth.login') }}"><button type="button" class="btn pterodactyl-login-button--left"><i class="fa fa-user-circle"></i></button></a>
</div>
<div class="col-xs-8">
<div class="col-xs-offset-4 col-xs-4">
{!! csrf_field() !!}
<button type="submit" class="btn btn-primary btn-block btn-flat g-recaptcha" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.request_reset')</button>
<button type="submit" class="btn btn-block g-recaptcha pterodactyl-login-button--main" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.request_reset')</button>
</div>
</div>
</form>
</div>
</div>
@endsection
@section('scripts')

View file

@ -10,9 +10,10 @@
@endsection
@section('content')
<div class="login-box-body">
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10">
@if (count($errors) > 0)
<div class="callout callout-danger">
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@lang('auth.auth_error')<br><br>
<ul>
@ -22,12 +23,15 @@
</ul>
</div>
@endif
<p class="login-box-msg">@lang('auth.reset_password_text')</p>
</div>
</div>
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10 pterodactyl-login-box">
<form id="resetForm" action="{{ route('auth.reset.post') }}" method="POST">
<div class="form-group">
<label for="email" class="control-label">@lang('strings.email')</label>
<div>
<input type="text" class="form-control" name="email" id="email" value="{{ $email or old('email') }}" required autofocus placeholder="@lang('strings.email')" />
<div class="form-group has-feedback">
<div class="pterodactyl-login-input">
<input type="email" name="email" class="form-control input-lg" value="{{ $email or old('email') }}" required autofocus placeholder="@lang('strings.email')">
<span class="fa fa-envelope form-control-feedback fa-lg"></span>
@if ($errors->has('email'))
<span class="help-block text-red small">
{{ $errors->first('email') }}
@ -35,22 +39,22 @@
@endif
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">@lang('strings.password')</label>
<div>
<input type="password" class="form-control" name="password" id="password" required placeholder="@lang('strings.password')" />
<div class="form-group has-feedback">
<div class="pterodactyl-login-input">
<input type="password" name="password" class="form-control input-lg" id="password" required placeholder="@lang('strings.password')">
<span class="fa fa-lock form-control-feedback fa-lg"></span>
@if ($errors->has('password'))
<span class="help-block text-red small">
{{ $errors->first('password') }}
</span>
@endif
<p class="text-muted"><small>@lang('auth.password_requirements')</small></p>
<p class="small" style="color: #fff;">@lang('auth.password_requirements')</p>
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">@lang('strings.confirm_password')</label>
<div>
<input type="password" class="form-control" name="password_confirmation" id="password_confirmation" required placeholder="@lang('strings.confirm_password')" />
<div class="form-group has-feedback">
<div class="pterodactyl-login-input">
<input type="password" name="password_confirmation" class="form-control input-lg" id="password_confirmation" required placeholder="@lang('strings.confirm_password')">
<span class="fa fa-lock form-control-feedback fa-lg"></span>
@if ($errors->has('password_confirmation'))
<span class="help-block text-red small">
{{ $errors->first('password_confirmation') }}
@ -59,14 +63,17 @@
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-4">
<a href="{{ route('auth.login') }}"><button type="button" class="btn pterodactyl-login-button--left"><i class="fa fa-user-circle"></i></button></a>
</div>
<div class="col-xs-offset-1 col-xs-7">
{!! csrf_field() !!}
<input type="hidden" name="token" value="{{ $token }}">
<button type="submit" class="btn btn-primary btn-block btn-flat g-recaptcha" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.reset_password')</button>
<button type="submit" class="btn btn-block g-recaptcha pterodactyl-login-button--main" @if(config('recaptcha.enabled')) data-sitekey="{{ config('recaptcha.website_key') }}" data-callback='onSubmit' @endif>@lang('auth.reset_password')</button>
</div>
</div>
</form>
</div>
</div>
@endsection
@section('scripts')

View file

@ -20,22 +20,23 @@
@endsection
@section('content')
<div class="login-box-body">
<form action="{{ route('auth.totp') }}" method="POST">
<div class="row">
<div class="col-sm-offset-3 col-xs-offset-1 col-sm-6 col-xs-10 pterodactyl-login-box">
<form id="totpForm" action="{{ route('auth.totp') }}" method="POST">
<div class="form-group has-feedback">
<input type="number" name="2fa_token" class="form-control input-lg text-center" placeholder="@lang('strings.2fa_token')" autofocus>
<span class="fa fa-shield form-control-feedback"></span>
<div class="pterodactyl-login-input">
<input type="number" name="2fa_token" class="form-control input-lg" required placeholder="@lang('strings.2fa_token')" autofocus>
<span class="fa fa-shield form-control-feedback fa-lg"></span>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-offset-8 col-xs-4">
{!! csrf_field() !!}
<input type="hidden" name="verify_token" value="{{ $verify_key }}" />
@if($remember)
<input type="checkbox" name="remember" checked style="display:none;"/>
@endif
<button type="submit" class="btn btn-primary btn-block btn-flat">@lang('strings.submit')</button>
<button type="submit" class="btn btn-primary btn-block btn-flat pterodactyl-login-button--main">@lang('strings.submit')</button>
</div>
</div>
</form>
</div>
</div>
@endsection

View file

@ -34,7 +34,8 @@
@show
</head>
<body id="particles-js" class="hold-transition login-page">
<div class="login-box">
<div class="container">
<div id="login-position-elements">
<div class="login-logo">
{{ Settings::get('company', 'Pterodactyl') }}
</div>
@ -43,6 +44,7 @@
Copyright &copy; 2015 - {{ date('Y') }} <a href="https://pterodactyl.io/" target="_blank">Pterodactyl Software</a>.<br />
</p>
</div>
</div>
<div class="login-corner-info small">
<strong><i class="fa fa-fw {{ $appIsGit ? 'fa-git-square' : 'fa-code-fork' }}"></i></strong> {{ $appVersion }}<br />
<strong><i class="fa fa-fw fa-clock-o"></i></strong> {{ round(microtime(true) - LARAVEL_START, 3) }}s

View file

@ -1,19 +1,32 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
/*
|--------------------------------------------------------------------------
| Authentication Routes
|--------------------------------------------------------------------------
|
| Endpoint: /auth
|
*/
Route::get('/logout', 'LoginController@logout')->name('auth.logout')->middleware('auth');
Route::group(['middleware' => 'guest'], function () {
Route::get('/login', 'LoginController@showLoginForm')->name('auth.login');
Route::get('/login/totp', 'LoginController@totp')->name('auth.totp');
Route::get('/password', 'ForgotPasswordController@showLinkRequestForm')->name('auth.password');
Route::get('/password/reset/{token}', 'ResetPasswordController@showResetForm')->name('auth.reset');
Route::post('/login', 'LoginController@login')->middleware('recaptcha');
Route::post('/login/totp', 'LoginController@totpCheckpoint');
Route::post('/login/totp', 'LoginController@loginUsingTotp');
Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha');
Route::post('/password/reset', 'ResetPasswordController@reset')->name('auth.reset.post')->middleware('recaptcha');
Route::post('/password/reset/{token}', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha');
});
/*
|--------------------------------------------------------------------------
| Routes Accessable only when logged in
|--------------------------------------------------------------------------
|
| Endpoint: /auth
|
*/
Route::get('/logout', 'LoginController@logout')->name('auth.logout')->middleware('auth');