diff --git a/app/ActionCode.php b/app/ActionCode.php index 770d642..81535a3 100644 --- a/app/ActionCode.php +++ b/app/ActionCode.php @@ -22,7 +22,7 @@ class ActionCode public static function generate(string $action, int $user = 0): string { // Generate a code - $code = uniqid(); + $code = bin2hex(random_bytes(8)); // Insert it DB::table('actioncodes') diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index 3c573e1..0cc343b 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -37,6 +37,23 @@ class AuthController extends Controller ]); } + protected function authenticate(User $user): void + { + // Generate a session key + $session = CurrentSession::create( + $user->id, + Net::ip(), + get_country_code(), + clean_string($_SERVER['HTTP_USER_AGENT'] ?? '') + ); + + $cookiePrefix = config('cookie.prefix'); + setcookie("{$cookiePrefix}id", $user->id, time() + 604800, '/'); + setcookie("{$cookiePrefix}session", $session->key, time() + 604800, '/'); + + $this->touchRateLimit($user->id, true); + } + /** * End the current session. * @throws HttpMethodNotAllowedException @@ -99,19 +116,7 @@ class AuthController extends Controller return $this->json(['error' => "Your account isn't activated, check your e-mail!"]); } - // Generate a session key - $session = CurrentSession::create( - $user->id, - Net::ip(), - get_country_code(), - clean_string($_SERVER['HTTP_USER_AGENT'] ?? '') - ); - - $cookiePrefix = config('cookie.prefix'); - setcookie("{$cookiePrefix}id", $user->id, time() + 604800, '/'); - setcookie("{$cookiePrefix}session", $session->key, time() + 604800, '/'); - - $this->touchRateLimit($user->id, true); + $this->authenticate($user); $msg = ['error' => null]; @@ -124,78 +129,52 @@ class AuthController extends Controller /** * Do a registration attempt. + * @throws HttpMethodNotAllowedException * @return string */ public function register(): string { - // Preliminarily set registration to failed - $redirect = route('auth.register'); + if (!session_check()) { + return view('auth/register', compact('registered_username')); + } // Check if authentication is disallowed if (config('user.disable_registration')) { - $message = 'Registration is disabled for security checkups! Try again later.'; - return view('global/information', compact('message', 'redirect')); + return $this->json(['error' => 'Registration is currently disabled.']); } - if (!session_check()) { - // Attempt to check if a user has already registered from the current IP - $getUserIP = DB::table('users') - ->where('register_ip', Net::pton(Net::ip())) - ->orWhere('last_ip', Net::pton(Net::ip())) - ->get(); - - $vars = []; - - if ($getUserIP) { - $vars = [ - 'haltRegistration' => count($getUserIP) > 1, - 'haltName' => $getUserIP[array_rand($getUserIP)]->username, - ]; - } - - return view('auth/register', $vars); - } - - // Grab forms $username = $_POST['username'] ?? null; $password = $_POST['password'] ?? null; $email = $_POST['email'] ?? null; - // Append username and email to the redirection url - $redirect .= "?username={$username}&email={$email}"; - // Attempt to get account data $user = User::construct(clean_string($username, true, true)); // Check if the username already exists if ($user && $user->id !== 0) { $message = "{$user->username} is already a member here!" - . " If this is you please use the password reset form instead of making a new account."; - return view('global/information', compact('message', 'redirect')); + . "\r\nIf this is you please use the password reset form instead of making a new account."; + return $this->json(['error' => $message]); } // Username too short if (strlen($username) < config('user.name_min')) { - $message = 'Your name must be at least 3 characters long.'; - return view('global/information', compact('message', 'redirect')); + return $this->json(['error' => 'Your name must be at least 3 characters long.']); } // Username too long if (strlen($username) > config('user.name_max')) { - $message = 'Your name can\'t be longer than 16 characters.'; - return view('global/information', compact('message', 'redirect')); + return $this->json(['error' => "Your name can't be longer than 16 characters."]); } // Check if the given email address is formatted properly if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - $message = 'Your e-mail address is formatted incorrectly.'; - return view('global/information', compact('message', 'redirect')); + return $this->json(['error' => 'Your e-mail address is formatted incorrectly.']); } // Check the MX record of the email if (!check_mx_record($email)) { - $message = 'No valid MX-Record found on the e-mail address you supplied.'; - return view('global/information', compact('message', 'redirect')); + return $this->json(['error' => 'No valid MX-Record found on the e-mail address you supplied.']); } // Check if the e-mail has already been used @@ -203,122 +182,81 @@ class AuthController extends Controller ->where('email', $email) ->count(); if ($emailCheck) { - $message = 'Someone already registered using this email!'; - return view('global/information', compact('message', 'redirect')); + return $this->json(['error' => 'Someone already registered using this email!']); } // Check password entropy if (password_entropy($password) < config('user.pass_min_entropy')) { - $message = 'Your password is too weak, try adding some special characters.'; - return view('global/information', compact('message', 'redirect')); + return $this->json([ + 'error' => 'Your password is considered too weak, try adding some special characters.' + ]); } - $requireActive = boolval(config('user.require_activation')); - $user = User::create($username, $password, $email, !$requireActive); + $activation = boolval(config('user.require_activation')); + $user = User::create($username, $password, $email, !$activation); // Check if we require e-mail activation - if ($requireActive) { + if ($activation) { + $message = 'Almost there, only activation is left! Please check your e-mail for the link.'; $this->sendActivationMail($user); + } else { + $this->authenticate($user); } - // Return true with a specific message if needed - $redirect = route('main.index'); - $message = 'Your registration went through!'; - $message .= $requireActive ? ' An activation e-mail has been sent.' : ' Welcome to ' . config('general.name') . '!'; - - return view('global/information', compact('message', 'redirect')); + return $this->json(['text' => $message ?? null, 'go' => route('main.index')]); } /** - * Do a activation attempt. + * Do a (re)activation attempt. * @return string */ public function activate(): string { - // Preliminarily set activation to failed - $redirect = route('main.index'); + // activation link handler + if (isset($_GET['u'], $_GET['k'])) { + $user = User::construct($_GET['u'] ?? null); - // Attempt to get the required GET parameters - $userId = $_GET['u'] ?? 0; - $key = $_GET['k'] ?? ""; + if ($user->id === 0 + || $user->activated + || !ActionCode::validate('ACTIVATE', $_GET['k'] ?? null, $user->id)) { + return view('auth/activate_failed'); + } - // Attempt to create a user object - $user = User::construct($userId); + DB::table('users') + ->where('user_id', $user->id) + ->update(['user_activated' => 1]); - // Quit if the user ID is 0 - if ($user->id === 0) { - $message = "This user does not exist! Contact us if you think this isn't right."; - return view('global/information', compact('message', 'redirect')); + $this->authenticate($user); + + return redirect(route('main.index')); } - // Check if the user is already active - if ($user->activated) { - $message = "Your account is already activated! Why are you here?"; - return view('global/information', compact('message', 'redirect')); + // resend handler + if (session_check() && isset($_POST['username'], $_POST['email'])) { + $id = DB::table('users') + ->where('username_clean', clean_string($_POST['username'], true)) + ->where('email', clean_string($_POST['email'], true)) + ->value('user_id'); + + if (!$id) { + return $this->json(['error' => "Couldn't find this username/e-mail combination, double check them!"]); + } + + $user = User::construct($id); + + if ($user->activated) { + return $this->json(['error' => 'Your account is already activated! Why are you here?', 'go' => route('main.index')]); + } + + $this->sendActivationMail($user); + + return $this->json([ + 'text' => "The e-mail should be on its way, don't forget to also check your spam folder!", + 'go' => route('main.index') + ]); } - // Validate the activation key - $action = ActionCode::validate('ACTIVATE', $key, $user->id); - - if (!$action) { - $message = "Invalid activation code! Contact us if you think this isn't right."; - return view('global/information', compact('message', 'redirect')); - } - - DB::table('users') - ->where('user_id', $userId) - ->update(['user_activated' => 1]); - - $redirect = route('main.index'); - $message = "Your account is activated, welcome to " . config('general.name') . "!"; - return view('global/information', compact('message', 'redirect')); - } - - /** - * Do a reactivation preparation attempt. - * @return string - */ - public function reactivate(): string - { - // Validate session - if (!session_check()) { - return view('auth/reactivate'); - } - - // Preliminarily set registration to failed - $redirect = route('auth.reactivate'); - - // Grab forms - $username = isset($_POST['username']) ? clean_string($_POST['username'], true) : null; - $email = isset($_POST['email']) ? clean_string($_POST['email'], true) : null; - - // Do database request - $getUser = DB::table('users') - ->where('username_clean', $username) - ->where('email', $email) - ->get(['user_id']); - - // Check if user exists - if (!$getUser) { - $message = "User not found! Double check your username and e-mail address!"; - return view('global/information', compact('message', 'redirect')); - } - - // Create user object - $user = User::construct($getUser[0]->user_id); - - // Check if a user is activated - if ($user->activated) { - $message = "Your account is already activated! Why are you here?"; - return view('global/information', compact('message', 'redirect')); - } - - // Send activation e-mail to user - $this->sendActivationMail($user); - - $redirect = route('main.index'); - $message = "Sent the e-mail! Make sure to check your spam folder as well!"; - return view('global/information', compact('message', 'redirect')); + return view('auth/activate'); } /** @@ -327,64 +265,54 @@ class AuthController extends Controller */ public function resetPassword(): string { - // Validate session - if (!session_check()) { - return view('auth/resetpassword'); - } + if (session_check()) { + $userId = $_POST['user'] ?? 0; + $key = $_POST['key'] ?? ""; + $password = $_POST['password'] ?? ""; + $userName = clean_string($_POST['username'] ?? "", true); + $email = clean_string($_POST['email'] ?? "", true); + $user = User::construct($userId ? $userId : $userName); - // Preliminarily set action to failed - $redirect = route('main.index'); - - // Attempt to get the various required GET parameters - $userId = $_POST['user'] ?? 0; - $key = $_POST['key'] ?? ""; - $password = $_POST['password'] ?? ""; - $userName = clean_string($_POST['username'] ?? "", true); - $email = clean_string($_POST['email'] ?? "", true); - - // Create user object - $user = User::construct($userId ? $userId : $userName); - - // Quit if the user ID is 0 - if ($user->id === 0 || ($email !== null ? $email !== $user->email : false)) { - $message = "This user does not exist! Contact us if you think this isn't right."; - return view('global/information', compact('message', 'redirect')); - } - - // Check if the user is active - if (!$user->activated) { - $message = "Your account is deactivated, go activate it first..."; - return view('global/information', compact('message', 'redirect')); - } - - if ($key && $password) { - // Check password entropy - if (password_entropy($password) < config('user.pass_min_entropy')) { - $message = "Your password doesn't meet the strength requirements!"; - return view('global/information', compact('message', 'redirect')); + if ($user->id === 0 || (strlen($email) > 0 ? $email !== $user->email : false)) { + return $this->json(['error' => "This user does not exist! Contact us if you think this isn't right."]); } - // Validate the activation key - $action = ActionCode::validate('LOST_PASS', $key, $user->id); - - if (!$action) { - $message = "Invalid verification code! Contact us if you think this isn't right."; - return view('global/information', compact('message', 'redirect')); + if (!$user->activated) { + return $this->json([ + 'error' => "Your account isn't activated, go activate it first...", + 'go' => route('auth.activate'), + ]); } - $user->setPassword($password); + if ($key && $password) { + if (password_entropy($password) < config('user.pass_min_entropy')) { + return $this->json(['error' => "Your password doesn't meet the strength requirements!"]); + } + + $action = ActionCode::validate('LOST_PASS', $key, $user->id); + + if (!$action) { + return $this->json(['error' => "Invalid verification code! Contact us if you think this isn't right."]); + } + + $user->setPassword($password); + $this->authenticate($user); + + return $this->json([ + 'text' => "Changed your password!", + 'go' => route('main.index'), + ]); + } - $message = "Changed your password! You may now log in."; - $redirect = route('main.index'); - } else { - // Send the e-mail $this->sendPasswordMail($user); - $message = "Sent the e-mail, keep an eye on your spam folder as well!"; - $redirect = route('main.index'); + return $this->json([ + 'text' => "The e-mail should be on its way, don't forget to also check your spam folder!", + 'go' => route('main.index'), + ]); } - return view('global/information', compact('message', 'redirect')); + return view('auth/resetpassword'); } /** diff --git a/app/Controllers/Settings/AdvancedController.php b/app/Controllers/Settings/AdvancedController.php index b98e0ae..7ec0662 100644 --- a/app/Controllers/Settings/AdvancedController.php +++ b/app/Controllers/Settings/AdvancedController.php @@ -8,6 +8,7 @@ namespace Sakura\Controllers\Settings; use Phroute\Phroute\Exception\HttpMethodNotAllowedException; use Sakura\CurrentSession; +use Sakura\DB; use Sakura\Session; /** diff --git a/app/Controllers/UserController.php b/app/Controllers/UserController.php index 8654964..7b043f3 100644 --- a/app/Controllers/UserController.php +++ b/app/Controllers/UserController.php @@ -34,7 +34,7 @@ class UserController extends Controller if ($profile->id === 0) { // Fetch from username_history $check = DB::table('username_history') - ->where('username_old_clean', clean_string($id, true, true)) + ->where('username_old_clean', clean_string($id, true)) ->orderBy('change_id', 'desc') ->first(); @@ -55,7 +55,7 @@ class UserController extends Controller public function resolve(string $name): string { $id = DB::table('users') - ->where('username_clean', clean_string($name, true, true)) + ->where('username_clean', clean_string($name, true)) ->value('user_id'); if (!$id) { diff --git a/resources/views/yuuno/auth/activate.twig b/resources/views/yuuno/auth/activate.twig new file mode 100644 index 0000000..3662578 --- /dev/null +++ b/resources/views/yuuno/auth/activate.twig @@ -0,0 +1,68 @@ +{% extends 'master.twig' %} + +{% set title = 'Request activation' %} + +{% block js %} + +{% endblock %} + +{% block content %} +
+
+ Request activation +
+
+ + + + +
+ If you lost access to your e-mail account, please contact us. +
+
+
+{% endblock %} diff --git a/resources/views/yuuno/auth/activate_failed.twig b/resources/views/yuuno/auth/activate_failed.twig new file mode 100644 index 0000000..c43fc65 --- /dev/null +++ b/resources/views/yuuno/auth/activate_failed.twig @@ -0,0 +1,24 @@ +{% extends 'master.twig' %} + +{% set title = 'Activation failed' %} + +{% block content %} +
+
+ Activation failed +
+
+

Something went wrong while trying to activate your account!

+

This can be caused by the following things:

+ +

Or alternatively...

+
+ + Resend activation e-mail + +
+{% endblock %} diff --git a/resources/views/yuuno/auth/reactivate.twig b/resources/views/yuuno/auth/reactivate.twig deleted file mode 100644 index ba27dc6..0000000 --- a/resources/views/yuuno/auth/reactivate.twig +++ /dev/null @@ -1,24 +0,0 @@ -{% extends 'master.twig' %} - -{% set title = 'Reactivate account' %} - -{% block content %} -
-
- Reactivate account -
-
- - - -
-
-{% endblock %} diff --git a/resources/views/yuuno/auth/register.twig b/resources/views/yuuno/auth/register.twig index 2586aa7..33ad330 100644 --- a/resources/views/yuuno/auth/register.twig +++ b/resources/views/yuuno/auth/register.twig @@ -2,38 +2,79 @@ {% set title = 'Register' %} +{% block js %} + +{% endblock %} + {% block content %}
- {% if config('user.disable_registration') %} -
-

Registration is disabled.

-

Please try again later.

- {% else %}
Register
-
- - - - -
- By creating an account you agree to the Terms of Service.
- You are only allowed to make a single account.
- Didn't get your activation e-mail? Click here to resend it! -
-
- {% endif %} + {% if config('user.disable_registration') %} +
+

You can't create an account right now!

+

Please try again later, sorry for the inconvenience.

+ {% else %} +
+ + + + + +
+ By creating an account you agree to the Terms of Service.
+ You are only allowed to make a single account.
+ Didn't get your activation e-mail? Click here to resend it! +
+
+ {% endif %}
{% endblock %} diff --git a/resources/views/yuuno/auth/resetpassword.twig b/resources/views/yuuno/auth/resetpassword.twig index 783ead4..a1e6daf 100644 --- a/resources/views/yuuno/auth/resetpassword.twig +++ b/resources/views/yuuno/auth/resetpassword.twig @@ -1,15 +1,60 @@ {% extends 'master.twig' %} -{% set title = 'Reset Password' %} +{% set title = 'Reset password' %} +{% set verified = get.u is defined and get.k is defined %} + +{% block js %} + +{% endblock %} {% block content %}
- Reset Password + Reset password
-
+ - {% if get.u is defined and get.k is defined %} + {% if verified %} -
Contact us if you lost access to your e-mail address! diff --git a/resources/views/yuuno/global/information.twig b/resources/views/yuuno/global/information.twig deleted file mode 100644 index 61f3abd..0000000 --- a/resources/views/yuuno/global/information.twig +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'master.twig' %} - -{% set title = 'Information' %} - -{% block content %} -
-
-

Information

-
- {{ message|default('') }} - {% if redirect is defined %}
Click here if you aren't being redirected.{% endif %} -
-
-{% endblock %} diff --git a/resources/views/yuuno/user/profile.twig b/resources/views/yuuno/user/profile.twig index 2800b98..f1033e5 100644 --- a/resources/views/yuuno/user/profile.twig +++ b/resources/views/yuuno/user/profile.twig @@ -175,7 +175,7 @@

The requested user does not exist!

There are a few possible reasons for this: -
    +
    • They changed their username.
    • They may have been restricted.
    • You made a typo.
    • diff --git a/routes.php b/routes.php index b3ffc6a..2d93a16 100644 --- a/routes.php +++ b/routes.php @@ -61,9 +61,8 @@ Router::group(['before' => 'maintenance'], function () { Router::post('/register', 'AuthController@register', 'auth.register'); Router::get('/reset-password', 'AuthController@resetPassword', 'auth.resetpassword'); Router::post('/reset-password', 'AuthController@resetPassword', 'auth.resetpassword'); - Router::get('/reactivate', 'AuthController@reactivate', 'auth.reactivate'); - Router::post('/reactivate', 'AuthController@reactivate', 'auth.reactivate'); Router::get('/activate', 'AuthController@activate', 'auth.activate'); + Router::post('/activate', 'AuthController@activate', 'auth.activate'); }); // Info