misuzu/public/auth.php

366 lines
13 KiB
PHP
Raw Normal View History

<?php
2018-02-10 23:18:49 +00:00
use Carbon\Carbon;
2018-07-21 23:54:12 +00:00
use Misuzu\Application;
2018-05-16 02:58:21 +00:00
use Misuzu\Database;
require_once __DIR__ . '/../misuzu.php';
2018-05-27 00:20:35 +00:00
$usernameValidationErrors = [
'trim' => 'Your username may not start or end with spaces!',
2018-05-27 00:20:35 +00:00
'short' => sprintf('Your username is too short, it has to be at least %d characters!', MSZ_USERNAME_MIN_LENGTH),
'long' => sprintf("Your username is too long, it can't be longer than %d characters!", MSZ_USERNAME_MAX_LENGTH),
'invalid' => 'Your username contains invalid characters.',
'in-use' => 'This username is already taken!',
];
$preventRegistration = $app->disableRegistration();
2018-03-22 02:56:41 +00:00
$isSubmission = !empty($_POST['auth']) && is_array($_POST['auth']);
$authMode = $isSubmission ? ($_POST['auth']['mode'] ?? '') : ($_GET['m'] ?? 'login');
$authUsername = $isSubmission ? ($_POST['auth']['username'] ?? '') : ($_GET['username'] ?? '');
$authEmail = $isSubmission ? ($_POST['auth']['email'] ?? '') : ($_GET['email'] ?? '');
$authPassword = $_POST['auth']['password'] ?? '';
$authVerification = $_POST['auth']['verification'] ?? '';
2018-08-15 01:12:58 +00:00
tpl_vars([
2018-05-27 00:20:35 +00:00
'prevent_registration' => $preventRegistration,
'auth_mode' => $authMode,
'auth_username' => $authUsername,
'auth_email' => $authEmail,
2018-05-27 00:20:35 +00:00
]);
2018-05-27 00:20:35 +00:00
switch ($authMode) {
case 'get_user':
echo user_id_from_username($_GET['u'] ?? '');
break;
case 'logout':
2018-05-16 02:58:21 +00:00
if (!$app->hasActiveSession()) {
2018-03-22 02:56:41 +00:00
header('Location: /');
return;
}
2018-03-22 18:07:02 +00:00
if (isset($_GET['s']) && tmp_csrf_verify($_GET['s'])) {
set_cookie_m('uid', '', -3600);
set_cookie_m('sid', '', -3600);
2018-05-27 00:20:35 +00:00
user_session_delete($app->getSessionId());
2018-03-22 02:56:41 +00:00
header('Location: /');
return;
}
2018-08-15 01:12:58 +00:00
echo tpl_render('auth.logout');
2018-07-21 23:54:12 +00:00
break;
case 'reset':
if ($app->hasActiveSession()) {
header('Location: /settings.php');
break;
}
$resetUser = (int)($_POST['user'] ?? $_GET['u'] ?? 0);
$getResetUser = Database::prepare('
SELECT `user_id`, `username`
FROM `msz_users`
WHERE `user_id` = :user_id
');
$getResetUser->bindValue('user_id', $resetUser);
$resetUser = $getResetUser->execute() ? $getResetUser->fetch(PDO::FETCH_ASSOC) : [];
if (empty($resetUser)) {
header('Location: ?m=forgot');
break;
}
tpl_var('auth_reset_message', "A verification code should've been sent to your e-mail address.");
2018-07-21 23:54:12 +00:00
while ($isSubmission) {
2018-07-21 23:54:12 +00:00
$validateRequest = Database::prepare('
SELECT COUNT(`user_id`) > 0
FROM `msz_users_password_resets`
WHERE `user_id` = :user
AND `verification_code` = :code
AND `verification_code` IS NOT NULL
AND `reset_requested` > NOW() - INTERVAL 1 HOUR
');
$validateRequest->bindValue('user', $resetUser['user_id']);
$validateRequest->bindValue('code', $authVerification);
2018-07-21 23:54:12 +00:00
$validateRequest = $validateRequest->execute()
? (bool)$validateRequest->fetchColumn()
: false;
if (!$validateRequest) {
2018-08-15 01:12:58 +00:00
tpl_var('auth_reset_error', 'Invalid verification code!');
2018-07-21 23:54:12 +00:00
break;
}
tpl_var('reset_verify', $authVerification);
2018-07-21 23:54:12 +00:00
if (empty($authPassword['new'])
|| empty($authPassword['confirm'])
|| $authPassword['new'] !== $authPassword['confirm']) {
2018-08-15 01:12:58 +00:00
tpl_var('auth_reset_error', 'Your passwords didn\'t match!');
2018-07-21 23:54:12 +00:00
break;
}
if (user_validate_password($authPassword['new']) !== '') {
2018-08-15 01:12:58 +00:00
tpl_var('auth_reset_error', 'Your password is too weak!');
2018-07-21 23:54:12 +00:00
break;
}
$updatePassword = Database::prepare('
UPDATE `msz_users`
SET `password` = :password
WHERE `user_id` = :user
');
$updatePassword->bindValue('user', $resetUser['user_id']);
$updatePassword->bindValue('password', user_password_hash($authPassword['new']));
2018-07-21 23:54:12 +00:00
if ($updatePassword->execute()) {
audit_log('PASSWORD_RESET', $resetUser['user_id']);
} else {
throw new UnexpectedValueException('Password reset failed.');
}
$invalidateCode = Database::prepare('
UPDATE `msz_users_password_resets`
SET `verification_code` = NULL
WHERE `verification_code` = :code
AND `user_id` = :user
');
$invalidateCode->bindValue('user', $resetUser['user_id']);
$invalidateCode->bindValue('code', $authVerification);
2018-07-21 23:54:12 +00:00
if (!$invalidateCode->execute()) {
throw new UnexpectedValueException('Verification code invalidation failed.');
}
header('Location: /auth.php?m=login&u=' . $resetUser['username']);
2018-07-21 23:54:12 +00:00
break;
}
2018-08-15 01:12:58 +00:00
echo tpl_render('auth.password', [
2018-07-21 23:54:12 +00:00
'reset_user' => $resetUser,
]);
break;
case 'forgot':
if ($app->hasActiveSession()) {
header('Location: /');
break;
}
while ($isSubmission) {
if (empty($authEmail)) {
2018-08-15 01:12:58 +00:00
tpl_var('auth_forgot_error', 'Please enter an e-mail address.');
2018-07-21 23:54:12 +00:00
break;
}
$forgotUser = Database::prepare('
SELECT `user_id`, `username`, `email`
FROM `msz_users`
WHERE LOWER(`email`) = LOWER(:email)
');
$forgotUser->bindValue('email', $authEmail);
2018-07-21 23:54:12 +00:00
$forgotUser = $forgotUser->execute() ? $forgotUser->fetch(PDO::FETCH_ASSOC) : [];
if (empty($forgotUser)) {
tpl_var('auth_forgot_error', 'This user is not registered with us.');
2018-07-21 23:54:12 +00:00
break;
}
$ipAddress = ip_remote_address();
2018-07-21 23:54:12 +00:00
$emailSent = Database::prepare('
SELECT COUNT(`verification_code`) > 0
FROM `msz_users_password_resets`
WHERE `user_id` = :user
AND `reset_ip` = INET6_ATON(:ip)
AND `reset_requested` > NOW() - INTERVAL 1 HOUR
AND `verification_code` IS NOT NULL
');
$emailSent->bindValue('user', $forgotUser['user_id']);
$emailSent->bindValue('ip', $ipAddress);
$emailSent = $emailSent->execute()
? (bool)$emailSent->fetchColumn()
: false;
if (!$emailSent) {
$verificationCode = bin2hex(random_bytes(6));
$insertResetKey = Database::prepare('
REPLACE INTO `msz_users_password_resets`
(`user_id`, `reset_ip`, `verification_code`)
VALUES
(:user, INET6_ATON(:ip), :code)
');
$insertResetKey->bindValue('user', $forgotUser['user_id']);
$insertResetKey->bindValue('ip', $ipAddress);
$insertResetKey->bindValue('code', $verificationCode);
if (!$insertResetKey->execute()) {
throw new UnexpectedValueException('A verification code failed to insert.');
}
$messageBody = <<<MSG
Hey {$forgotUser['username']},
You, or someone pretending to be you, has requested a password reset for your account.
Your verification code is: {$verificationCode}
If you weren't the person who requested this reset, please send a reply to this e-mail.
MSG;
$message = (new Swift_Message('Flashii Password Reset'))
->setFrom($app->getMailSender())
2018-07-21 23:54:12 +00:00
->setTo([$forgotUser['email'] => $forgotUser['username']])
->setBody($messageBody);
Application::mailer()->send($message);
}
header("Location: ?m=reset&username={$forgotUser['user_id']}");
2018-07-21 23:54:12 +00:00
break;
}
2018-08-15 01:12:58 +00:00
echo tpl_render('auth.auth');
break;
case 'login':
2018-05-16 02:58:21 +00:00
if ($app->hasActiveSession()) {
2018-03-22 02:56:41 +00:00
header('Location: /');
break;
}
2018-05-27 00:20:35 +00:00
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$authLoginError = '';
while ($isSubmission) {
$ipAddress = ip_remote_address();
2018-03-22 03:45:59 +00:00
if (!isset($authUsername, $authPassword)) {
2018-05-27 00:20:35 +00:00
$authLoginError = "You didn't fill all the forms!";
2018-03-22 02:56:41 +00:00
break;
}
2018-05-27 00:20:35 +00:00
$remainingAttempts = user_login_attempts_remaining($ipAddress);
2018-05-16 02:58:21 +00:00
if ($remainingAttempts < 1) {
2018-05-27 00:20:35 +00:00
$authLoginError = 'Too many failed login attempts, try again later.';
2018-03-22 03:45:59 +00:00
break;
}
$getUser = Database::prepare('
2018-05-16 02:58:21 +00:00
SELECT `user_id`, `password`
FROM `msz_users`
WHERE LOWER(`email`) = LOWER(:email)
OR LOWER(`username`) = LOWER(:username)
');
$getUser->bindValue('email', $authUsername);
$getUser->bindValue('username', $authUsername);
2018-05-16 02:58:21 +00:00
$userData = $getUser->execute() ? $getUser->fetch() : [];
$userId = (int)($userData['user_id'] ?? 0);
2018-05-27 00:20:35 +00:00
$loginFailedError = sprintf(
"Invalid username or password, %d attempt%s remaining.",
$remainingAttempts - 1,
$remainingAttempts === 2 ? '' : 's'
);
2018-05-16 02:58:21 +00:00
if ($userId < 1) {
2018-05-27 00:20:35 +00:00
user_login_attempt_record(false, null, $ipAddress, $userAgent);
$authLoginError = $loginFailedError;
2018-03-22 02:56:41 +00:00
break;
}
if (!password_verify($authPassword, $userData['password'])) {
2018-05-27 00:20:35 +00:00
user_login_attempt_record(false, $userId, $ipAddress, $userAgent);
$authLoginError = $loginFailedError;
2018-03-22 02:56:41 +00:00
break;
}
2018-02-10 23:18:49 +00:00
2018-05-27 00:20:35 +00:00
user_login_attempt_record(true, $userId, $ipAddress, $userAgent);
$sessionKey = user_session_create($userId, $ipAddress, $userAgent);
if ($sessionKey === '') {
$authLoginError = 'Unable to create new session, contact an administrator ASAP.';
2018-05-16 02:58:21 +00:00
break;
}
2018-03-22 03:45:59 +00:00
2018-05-16 02:58:21 +00:00
$app->startSession($userId, $sessionKey);
$cookieLife = Carbon::now()->addMonth()->timestamp;
set_cookie_m('uid', $userId, $cookieLife);
set_cookie_m('sid', $sessionKey, $cookieLife);
2018-03-22 02:56:41 +00:00
header('Location: /');
return;
}
2018-05-27 00:20:35 +00:00
if (!empty($authLoginError)) {
2018-08-15 01:12:58 +00:00
tpl_var('auth_login_error', $authLoginError);
2018-03-22 02:56:41 +00:00
}
2018-08-15 01:12:58 +00:00
echo tpl_render('auth.auth');
break;
2018-01-28 03:32:28 +00:00
case 'register':
2018-05-16 02:58:21 +00:00
if ($app->hasActiveSession()) {
2018-03-22 02:56:41 +00:00
header('Location: /');
}
2018-05-27 00:20:35 +00:00
$authRegistrationError = '';
while ($isSubmission) {
2018-05-27 00:20:35 +00:00
if ($preventRegistration) {
$authRegistrationError = 'Registration is not allowed on this instance.';
2018-03-22 02:56:41 +00:00
break;
}
if (!isset($authUsername, $authPassword, $authEmail)) {
2018-05-27 00:20:35 +00:00
$authRegistrationError = "You didn't fill all the forms!";
2018-03-22 02:56:41 +00:00
break;
}
$usernameValidation = user_validate_username($authUsername, true);
2018-05-27 00:20:35 +00:00
if ($usernameValidation !== '') {
$authRegistrationError = $usernameValidationErrors[$usernameValidation];
2018-03-22 02:56:41 +00:00
break;
}
$emailValidation = user_validate_email($authEmail, true);
2018-05-27 00:20:35 +00:00
if ($emailValidation !== '') {
$authRegistrationError = $emailValidation === 'in-use'
? 'This e-mail address has already been used!'
: 'The e-mail address you entered is invalid!';
break;
}
if (user_validate_password($authPassword) !== '') {
2018-05-27 00:20:35 +00:00
$authRegistrationError = 'Your password is too weak!';
break;
}
2018-05-27 00:20:35 +00:00
$createUser = user_create(
$authUsername,
$authPassword,
$authEmail,
ip_remote_address()
2018-05-27 00:20:35 +00:00
);
if ($createUser < 1) {
$authRegistrationError = 'Something happened?';
2018-05-16 02:58:21 +00:00
break;
}
2018-05-27 00:20:35 +00:00
user_role_add($createUser, MSZ_ROLE_MAIN);
2018-05-16 02:58:21 +00:00
2018-08-15 01:12:58 +00:00
tpl_var('auth_register_message', 'Welcome to Flashii! You may now log in.');
break;
}
2018-05-27 00:20:35 +00:00
if (!empty($authRegistrationError)) {
2018-08-15 01:12:58 +00:00
tpl_var('auth_register_error', $authRegistrationError);
2018-03-22 02:56:41 +00:00
}
2018-08-15 01:12:58 +00:00
echo tpl_render('auth.auth');
break;
}