big auth refactor

This commit is contained in:
flash 2016-12-08 00:34:59 +01:00
parent ac310dce99
commit 13090423d2
14 changed files with 150 additions and 159 deletions

View file

@ -49,8 +49,7 @@ class AuthController extends Controller
// Destroy the active session // Destroy the active session
CurrentSession::stop(); CurrentSession::stop();
// Return true indicating a successful logout return redirect(route('main.index'));
return redirect(route('auth.login'));
} }
/** /**
@ -60,16 +59,12 @@ class AuthController extends Controller
public function login(): string public function login(): string
{ {
if (!session_check()) { if (!session_check()) {
return view('auth/login'); return $this->json(['error' => 'Your session expired! Please refresh and try again.', 'a' => $_POST]);
} }
// Preliminarily set login to failed
$redirect = route('auth.login');
// Get request variables // Get request variables
$username = $_REQUEST['username'] ?? null; $username = $_POST['username'] ?? null;
$password = $_REQUEST['password'] ?? null; $password = $_POST['password'] ?? null;
$remember = isset($_REQUEST['remember']);
// Check if we haven't hit the rate limit // Check if we haven't hit the rate limit
$rates = DB::table('login_attempts') $rates = DB::table('login_attempts')
@ -79,38 +74,30 @@ class AuthController extends Controller
->count(); ->count();
if ($rates > 4) { if ($rates > 4) {
$message = 'Your have hit the login rate limit, try again later.'; return $this->json(['error' => 'Your have hit the login rate limit, try again later.']);
return view('global/information', compact('message', 'redirect'));
} }
// Get account data
$user = User::construct(clean_string($username, true, true)); $user = User::construct(clean_string($username, true, true));
// Check if the user that's trying to log in actually exists // Check if the user that's trying to log in actually exists
if ($user->id === 0) { if ($user->id === 0) {
$this->touchRateLimit($user->id); $this->touchRateLimit($user->id);
$message = 'The user you tried to log into does not exist.'; return $this->json(['error' => 'The user you tried to log into does not exist.']);
return view('global/information', compact('message', 'redirect'));
} }
if ($user->passwordExpired()) { if ($user->passwordExpired()) {
$message = 'Your password expired.'; return $this->json(['error' => 'Your password expired.']);
$redirect = route('auth.resetpassword');
return view('global/information', compact('message', 'redirect'));
} }
if (!$user->verifyPassword($password)) { if (!$user->verifyPassword($password)) {
$this->touchRateLimit($user->id); $this->touchRateLimit($user->id);
$message = 'The password you entered was invalid.'; return $this->json(['error' => 'The password you entered was invalid.']);
return view('global/information', compact('message', 'redirect'));
} }
// Check if the user has the required privs to log in // Check if the user has the required privs to log in
if (!$user->activated) { if (!$user->activated) {
$this->touchRateLimit($user->id); $this->touchRateLimit($user->id);
$message = 'Your account is deactivated, activate it first!'; return $this->json(['error' => "Your account isn't activated, check your e-mail!"]);
$redirect = route('auth.reactivate');
return view('global/information', compact('message', 'redirect'));
} }
// Generate a session key // Generate a session key
@ -118,33 +105,22 @@ class AuthController extends Controller
$user->id, $user->id,
Net::ip(), Net::ip(),
get_country_code(), get_country_code(),
clean_string($_SERVER['HTTP_USER_AGENT'] ?? ''), clean_string($_SERVER['HTTP_USER_AGENT'] ?? '')
$remember
); );
$cookiePrefix = config('cookie.prefix'); $cookiePrefix = config('cookie.prefix');
setcookie("{$cookiePrefix}id", $user->id, time() + 604800);
// User ID cookie setcookie("{$cookiePrefix}session", $session->key, time() + 604800);
setcookie(
"{$cookiePrefix}id",
$user->id,
time() + 604800
);
// Session ID cookie
setcookie(
"{$cookiePrefix}session",
$session->key,
time() + 604800
);
$this->touchRateLimit($user->id, true); $this->touchRateLimit($user->id, true);
$redirect = $user->lastOnline ? ($_REQUEST['redirect'] ?? route('main.index')) : route('info.welcome'); $msg = ['error' => null];
$message = 'Welcome' . ($user->lastOnline ? ' back' : '') . '!'; if (!$user->lastOnline) {
$msg['go'] = route('info.welcome');
}
return view('global/information', compact('message', 'redirect')); return $this->json($msg);
} }
/** /**
@ -247,7 +223,7 @@ class AuthController extends Controller
} }
// Return true with a specific message if needed // Return true with a specific message if needed
$redirect = route('auth.login'); $redirect = route('main.index');
$message = 'Your registration went through!'; $message = 'Your registration went through!';
$message .= $requireActive ? ' An activation e-mail has been sent.' : ' Welcome to ' . config('general.name') . '!'; $message .= $requireActive ? ' An activation e-mail has been sent.' : ' Welcome to ' . config('general.name') . '!';
@ -294,7 +270,7 @@ class AuthController extends Controller
->where('user_id', $userId) ->where('user_id', $userId)
->update(['user_activated' => 1]); ->update(['user_activated' => 1]);
$redirect = route('auth.login'); $redirect = route('main.index');
$message = "Your account is activated, welcome to " . config('general.name') . "!"; $message = "Your account is activated, welcome to " . config('general.name') . "!";
return view('global/information', compact('message', 'redirect')); return view('global/information', compact('message', 'redirect'));
} }
@ -341,7 +317,7 @@ class AuthController extends Controller
// Send activation e-mail to user // Send activation e-mail to user
$this->sendActivationMail($user); $this->sendActivationMail($user);
$redirect = route('auth.login'); $redirect = route('main.index');
$message = "Sent the e-mail! Make sure to check your spam folder as well!"; $message = "Sent the e-mail! Make sure to check your spam folder as well!";
return view('global/information', compact('message', 'redirect')); return view('global/information', compact('message', 'redirect'));
} }
@ -400,7 +376,7 @@ class AuthController extends Controller
$user->setPassword($password); $user->setPassword($password);
$message = "Changed your password! You may now log in."; $message = "Changed your password! You may now log in.";
$redirect = route('auth.login'); $redirect = route('main.index');
} else { } else {
// Send the e-mail // Send the e-mail
$this->sendPasswordMail($user); $this->sendPasswordMail($user);

View file

@ -74,8 +74,8 @@ class CurrentSession
* @param int $length * @param int $length
* @return Session * @return Session
*/ */
public static function create(int $user, string $ip, string $country, string $agent = null, bool $remember = false, int $length = 604800) public static function create(int $user, string $ip, string $country, string $agent = null, int $length = 604800)
{ {
return Session::create($user, $ip, $country, $agent, $remember, $length); return Session::create($user, $ip, $country, $agent, $length);
} }
} }

View file

@ -61,12 +61,6 @@ class Session
*/ */
public $expire = 0; public $expire = 0;
/**
* Whether to extend the session's lifetime.
* @var bool
*/
public $remember = false;
/** /**
* Constructor, $id can be a number or the secret key. * Constructor, $id can be a number or the secret key.
* @param mixed $id * @param mixed $id
@ -92,7 +86,6 @@ class Session
$this->key = $data->session_key; $this->key = $data->session_key;
$this->start = intval($data->session_start); $this->start = intval($data->session_start);
$this->expire = intval($data->session_expire); $this->expire = intval($data->session_expire);
$this->remember = boolval($data->session_remember);
} }
} }
@ -102,11 +95,10 @@ class Session
* @param string $ip * @param string $ip
* @param string $country * @param string $country
* @param string $agent * @param string $agent
* @param bool $remember
* @param int $length * @param int $length
* @return Session * @return Session
*/ */
public static function create(int $user, string $ip, string $country, string $agent = null, bool $remember = false, int $length = 604800) public static function create(int $user, string $ip, string $country, string $agent = null, int $length = 604800)
{ {
$start = time(); $start = time();
$key = bin2hex(random_bytes(64)); $key = bin2hex(random_bytes(64));
@ -119,7 +111,6 @@ class Session
'session_key' => $key, 'session_key' => $key,
'session_start' => $start, 'session_start' => $start,
'session_expire' => $start + $length, 'session_expire' => $start + $length,
'session_remember' => $remember ? 1 : 0,
'session_country' => $country, 'session_country' => $country,
]); ]);
@ -168,14 +159,10 @@ class Session
good thing is i can probably do CIDR based checking */ good thing is i can probably do CIDR based checking */
} }
// If the remember flag is set extend the session time
if ($session->session_remember) {
DB::table('sessions') DB::table('sessions')
->where('session_id', $session->session_id) ->where('session_id', $session->session_id)
->update(['session_expire' => time() + 604800]); ->update(['session_expire' => time() + 604800]);
}
// Return 2 if the remember flag is set and return 1 if not
return true; return true;
} }

View file

@ -347,10 +347,6 @@ class BaseTables extends Migration
$table->integer('session_expire') $table->integer('session_expire')
->unsigned(); ->unsigned();
$table->tinyInteger('session_remember')
->unsigned()
->default(0);
}); });
$schema->create('topics', function (Blueprint $table) { $schema->create('topics', function (Blueprint $table) {

View file

@ -1,12 +1,17 @@
.header-login { .header-login-container {
background: rgba(211, 191, 255, .8); background: #A586C3;
border: 1px solid #9475B2;
box-shadow: 0 0 3px #8364A1; box-shadow: 0 0 3px #8364A1;
text-align: center; }
.header-login {
max-width: 1024px; max-width: 1024px;
margin: 10px auto 0; padding: 6px 4px;
padding: 6px 3px; margin: 0 auto;
border-radius: 3px; text-align: left;
display: flex;
justify-content: space-between;
align-items: center;
&__text { &__text {
width: auto !important; width: auto !important;
@ -16,17 +21,43 @@
&__button { &__button {
margin: 0 !important; margin: 0 !important;
padding: 2px 8px !important; padding: 2px 8px !important;
&--small {
font-size: .9em !important;
padding: 1px 8px !important;
}
} }
&__label { &__sub--buttons {
font-family: @cute-font; text-align: right;
font-weight: 100;
font-size: 15px;
} }
@media (max-width: 640px) { @media (max-width: 1064px) {
&__label { align-items: flex-start;
display: block;
&__text {
margin-bottom: 2px;
}
&__button--small {
margin-bottom: 2px !important;
display: flex !important;
justify-content: space-between;
align-items: center;
}
&__sub {
display: flex;
flex-direction: column;
padding: 0 1px;
&--form {
flex-grow: 1;
}
&--buttons {
width: 150px;
}
} }
} }
} }

View file

@ -17,10 +17,12 @@
display: flex; display: flex;
align-items: center; align-items: center;
&:not(:first-child) {
// hackjob // hackjob
pointer-events: none; pointer-events: none;
position: relative; position: relative;
top: -100%; top: -100%;
}
> * { > * {
pointer-events: auto; pointer-events: auto;

View file

@ -7,6 +7,7 @@
font-size: 3em; font-size: 3em;
line-height: 1.4em; line-height: 1.4em;
transition: background .2s; transition: background .2s;
cursor: pointer;
&:hover { &:hover {
background: fade(#000, 50%); background: fade(#000, 50%);

View file

@ -33,7 +33,7 @@
<div class="header__avatar" style="background-image: url('{{ route('user.avatar', user.id) }}')"></div> <div class="header__avatar" style="background-image: url('{{ route('user.avatar', user.id) }}')"></div>
</a> </a>
{% else %} {% else %}
<a class="header__user" href="{{ route('auth.login') }}"> <a class="header__user" href="#">
<div class="header__username">login</div> <div class="header__username">login</div>
<div class="header__avatar" style="background-image: url('/images/no-avatar.png')"></div> <div class="header__avatar" style="background-image: url('/images/no-avatar.png')"></div>
</a> </a>

View file

@ -9,7 +9,7 @@
<div class="landing__inner"> <div class="landing__inner">
<div class="landing__buttons"> <div class="landing__buttons">
<a href="{{ route('auth.register') }}" class="landing__button">register</a> <a href="{{ route('auth.register') }}" class="landing__button">register</a>
<a href="{{ route('auth.login') }}" class="landing__button">login</a> <a href="#" class="landing__button">login</a>
</div> </div>
<div class="landing__text"> <div class="landing__text">
<p>Welcome to my humble abode, it doesn't look like much but if you like rectangles this is the place for you.</p> <p>Welcome to my humble abode, it doesn't look like much but if you like rectangles this is the place for you.</p>

View file

@ -1,36 +0,0 @@
{% extends 'master.twig' %}
{% set title = 'Login' %}
{% block content %}
<div class="auth content content--auth">
<div class="content__header">
Login
</div>
<form method="post" action="{{ route('auth.login') }}">
<input type="hidden" name="redirect" value="{{ server['HTTP_REFERER']|default(route('main.index')) }}">
<label>
<div class="auth__label">Username</div>
<input class="input__text" type="text" name="username" autofocus>
</label>
<label>
<div class="auth__label">Password</div>
<input class="input__text" type="password" name="password">
</label>
<label>
<div class="auth__label auth__label--checkbox">
<input name="remember" type="checkbox">
Remember me!
</div>
</label>
<button class="input__button auth__button" name="session" value="{{ session_id() }}">
<i class="fa fa-sign-in"></i> Login
</button>
<div class="auth__subtext">
<a href="{{ route('auth.register') }}">I don't have an account yet!</a><br>
<a href="{{ route('auth.resetpassword') }}">I forgot my password!</a><br>
<a href="{{ route('auth.reactivate') }}">I want to reactivate my account!</a>
</div>
</form>
</div>
{% endblock %}

View file

@ -30,7 +30,8 @@
</button> </button>
<div class="auth__subtext"> <div class="auth__subtext">
By creating an account you agree to the <a href="{{ route('info.terms') }}">Terms of Service</a>.<br> By creating an account you agree to the <a href="{{ route('info.terms') }}">Terms of Service</a>.<br>
You are only allowed to make a single account. You are only allowed to make a single account.<br>
Didn't get your activation e-mail? <a href="{{ route('auth.reactivate') }}">Click here to resend it</a>!
</div> </div>
</form> </form>
{% endif %} {% endif %}

View file

@ -17,7 +17,7 @@
<input class="input__text" type="password" name="password" autofocus> <input class="input__text" type="password" name="password" autofocus>
</label> </label>
<button class="input__button auth__button"> <button class="input__button auth__button">
Save Password <i class="fa fa-save"></i> Save Password
</button> </button>
{% else %} {% else %}
<label> <label>
@ -29,7 +29,7 @@
<input class="input__text" type="text" name="email"> <input class="input__text" type="text" name="email">
</label> </label>
<button class="input__button auth__button"> <button class="input__button auth__button">
Request Change <i class="fa fa-envelope"></i> Request Change
</button> </button>
<div class="auth__subtext"> <div class="auth__subtext">
<a href="{{ route('info.contact') }}">Contact us</a> if you lost access to your e-mail address! <a href="{{ route('info.contact') }}">Contact us</a> if you lost access to your e-mail address!

View file

@ -62,40 +62,69 @@
{% endif %} {% endif %}
<a class="header__menu-item fa fa-cogs" href="{{ route('settings.index') }}" title="Settings"></a> <a class="header__menu-item fa fa-cogs" href="{{ route('settings.index') }}" title="Settings"></a>
<a class="header__menu-item fa fa-sign-out" href="{{ route('auth.logout') }}" title="Logout"></a> <a class="header__menu-item fa fa-sign-out" href="{{ route('auth.logout') }}" title="Logout"></a>
{% else %}
<a class="header__menu-item fa fa-magic" href="{{ route('auth.register') }}" title="Register"></a>
<a class="header__menu-item fa fa-sign-in" href="{{ route('auth.login') }}" title="Login"></a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
{% if user.id == 0 %}
<div class="header-login-container">
<form class="header-login" method="post" action="javascript:void(0);" onsubmit="yuunoAttemptLogin(this)">
<div class="header-login__sub header-login__sub--form">
<input type="text" name="username" class="input__text header-login__text" placeholder="Username">
<input type="password" name="password" class="input__text header-login__text" placeholder="Password">
<input type="hidden" name="session" value="{{ session_id() }}">
<button class="input__button header-login__button">
<i class="fa fa-sign-in"></i> Login
</button>
</div>
<div class="header-login__sub header-login__sub--buttons">
<a class="input__button header-login__button header-login__button--small" href="{{ route('auth.register') }}">
<i class="fa fa-magic"></i> I don't have an account yet!
</a>
<a class="input__button header-login__button header-login__button--small" href="{{ route('auth.resetpassword') }}">
<i class="fa fa-exclamation-triangle"></i> I don't have access to my account!
</a>
</div>
</form>
</div>
<script>
function yuunoAttemptLogin(form) {
var ajax = new Sakura.AJAX();
console.log(new FormData(form));
ajax.SetUrl("{{ route('auth.login') }}");
ajax.SetFormData(new FormData(form));
ajax.AddCallback(200, function () {
var result = ajax.JSON();
console.log(result);
if (result.error) {
var diag = new Sakura.Dialogue;
diag.Text = result.error;
diag.AddCallback(Sakura.DialogueButton.Ok, function () {
this.Close();
});
diag.Display();
} else if (result.go) {
window.location.assign(result.go);
} else {
window.location.reload();
}
});
ajax.Start(Sakura.HTTPMethod.POST);
}
</script>
{% endif %}
<div id="contentwrapper" class="container__content"> <div id="contentwrapper" class="container__content">
<div id="notifications" class="alerts"></div> <div id="notifications" class="alerts"></div>
<div id="dialogues" class="dialogues"></div> <div id="dialogues" class="dialogues"></div>
{% if not user.isActive and server['REQUEST_URI'] != route('auth.login') %}
<div class="header-login">
<form method="post" action="{{ route('auth.login') }}">
<input type="hidden" name="redirect" value="{{ server['REQUEST_URI'] }}">
<label class="header-login__label">
Username:
<input type="text" name="username" class="input__text header-login__text" placeholder="Username">
</label>
<label class="header-login__label">
Password:
<input type="password" name="password" class="input__text header-login__text" placeholder="Password">
</label>
<label class="header-login__label">
<input type="checkbox" name="remember">
Remember me
</label>
<button class="input__button header-login__button" name="session" value="{{ session_id() }}">
<i class="fa fa-sign-in"></i> Login
</button>
</form>
</div>
{% endif %}
{% if user.restricted %} {% if user.restricted %}
<div class="announce-box announce-box--restricted"> <div class="announce-box announce-box--restricted">
<h1>Your account is currently in <span style="font-weight: 700 !important;">restricted mode</span>!</h1> <h1>Your account is currently in <span style="font-weight: 700 !important;">restricted mode</span>!</h1>

View file

@ -186,14 +186,18 @@
<div class="content profile"> <div class="content profile">
<div class="profile__container"> <div class="profile__container">
<div class="profile__header" style="background-image: url({{ route('user.header', profile.id) }});"> <div class="profile__header" style="background-image: url({{ route('user.header', profile.id) }});">
{% if (user.id == profile.id and not user.restricted and user.activated and user.perms.changeHeader) or user.perms.manageProfileImages %}
<label class="uploader__label"> <label class="uploader__label">
<input type="file" data-target="{{ route('user.header', profile.id) }}" class="uploader" onchange="handleImageChange(this, this.parentElement.parentElement)"> <input type="file" data-target="{{ route('user.header', profile.id) }}" class="uploader" onchange="handleImageChange(this, this.parentElement.parentElement)">
</label> </label>
{% endif %}
<div class="profile__info"> <div class="profile__info">
<div class="avatar avatar--border profile__avatar" style="background-image: url({{ route('user.avatar', profile.id) }}); box-shadow: 0 0 5px #{% if profile.isOnline %}484{% else %}844{% endif %};"> <div class="avatar avatar--border profile__avatar" style="background-image: url({{ route('user.avatar', profile.id) }}); box-shadow: 0 0 5px #{% if profile.isOnline %}484{% else %}844{% endif %};">
{% if (user.id == profile.id and not user.restricted and user.activated and user.perms.changeAvatar) or user.perms.manageProfileImages %}
<label class="uploader__label"> <label class="uploader__label">
<input type="file" data-target="{{ route('user.avatar', profile.id) }}" class="uploader" onchange="handleImageChange(this, this.parentElement.parentElement)"> <input type="file" data-target="{{ route('user.avatar', profile.id) }}" class="uploader" onchange="handleImageChange(this, this.parentElement.parentElement)">
</label> </label>
{% endif %}
</div> </div>
<div class="profile__username"> <div class="profile__username">
<h1 style="color: {{ profile.colour }}; text-shadow: 0 0 7px {% if profile.colour != 'inherit' %}{{ profile.colour }}{% else %}#222{% endif %}; padding: 0 0 2px;" {% if profile.getUsernameHistory %} title="Known as {{ profile.getUsernameHistory[0].username_old }} before {{ profile.getUsernameHistory[0].change_time|date(config('general.date_format')) }}." {% endif %}>{{ profile.username }}</h1> <h1 style="color: {{ profile.colour }}; text-shadow: 0 0 7px {% if profile.colour != 'inherit' %}{{ profile.colour }}{% else %}#222{% endif %}; padding: 0 0 2px;" {% if profile.getUsernameHistory %} title="Known as {{ profile.getUsernameHistory[0].username_old }} before {{ profile.getUsernameHistory[0].change_time|date(config('general.date_format')) }}." {% endif %}>{{ profile.username }}</h1>