From 70ba6473e96a29314f7eb503efda94e64d330d9c Mon Sep 17 00:00:00 2001 From: Jeroen De Meerleer Date: Fri, 9 Apr 2021 15:14:35 +0200 Subject: [PATCH] Added logout and autologin --- .env.sample | 12 ++++++- .gitignore | 3 +- config/routes.yaml | 11 ++++-- lib/Framework/Controller.php | 2 +- src/Controller/SecurityController.php | 37 ++++++++++++++++++-- src/Repository/User.php | 50 ++++++++++++++++++++++++--- templates/base.html.twig | 3 +- templates/flashes.html.twig | 14 ++++---- 8 files changed, 113 insertions(+), 19 deletions(-) diff --git a/.env.sample b/.env.sample index 8e6b572..f5cc467 100644 --- a/.env.sample +++ b/.env.sample @@ -15,4 +15,14 @@ DATABASE="sqlite://storage/database.sqlite" ## This secret value is used to encrypt secret values (eg. ssh-keys, http-auth passwords, etc) ## You should consider your already stored secret values lost when changing this value. -SECRET=ImNotThatSecretSoPleaseChangeMe0123456789 \ No newline at end of file +SECRET=ImNotThatSecretSoPleaseChangeMe0123456789 +ENCRYPTION_METHOD="AES-256-CBC" +HASHING_METHOD="sha256" + +############### +### COOKIES ### +############### + +## This secret value is used to encrypt secret values (eg. ssh-keys, http-auth passwords, etc) +## You should consider your already stored secret values lost when changing this value. +COOKIE_LIFETIME=2592000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3da5c49..47e36ce 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ vendor/ webcron.old/config.inc.php \.idea/ .env -storage/database.sqlite \ No newline at end of file +storage/database.sqlite +.DS_Store diff --git a/config/routes.yaml b/config/routes.yaml index 4f967c9..16a9f79 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -4,9 +4,14 @@ default: _controller: JeroenED\Webcron\Controller\JobController::defaultAction login: - path: '/login' - defaults: - _controller: JeroenED\Webcron\Controller\SecurityController::loginAction + path: '/login' + defaults: + _controller: JeroenED\Webcron\Controller\SecurityController::loginAction + +logout: + path: '/logout' + defaults: + _controller: JeroenED\Webcron\Controller\SecurityController::logoutAction login_check: path: '/login_check' diff --git a/lib/Framework/Controller.php b/lib/Framework/Controller.php index c36d74f..ff7767b 100644 --- a/lib/Framework/Controller.php +++ b/lib/Framework/Controller.php @@ -52,7 +52,7 @@ abstract class Controller public function render(string $template, array $vars = []): Response { if(empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') { - $vars['flashes'] = $_SESSION['flashes']; + $vars['flashes'] = $_SESSION['flashes'] ?? [] ; $_SESSION['flashes'] = []; } $response = new Response($this->twig->render($template, $vars)); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index cc00b3c..a19acb6 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -14,20 +14,53 @@ class SecurityController extends Controller { if(isset($_SESSION['isAuthenticated']) && $_SESSION['isAuthenticated']) { return new RedirectResponse($this->generateRoute('default')); + } elseif(isset($_COOKIE['autologin_enable']) && $_COOKIE['autologin_enable'] == true) { + $userRepository = new User($this->getDbCon()); + $userId = $userRepository->checkAuthentication($_COOKIE['autologin_user'], $_COOKIE['autologin_auth'], true); + if($userId !== false) { + $_SESSION['user.id'] = $userId; + $_SESSION['isAuthenticated'] = true; + } else { + return new RedirectResponse($this->generateRoute('logout')); + } + return new RedirectResponse($this->generateRoute('default')); } return $this->render('security/login.html.twig'); } + public function logoutAction(): Response + { + $_SESSION['isAuthenticated'] = false; + unset($_SESSION['user.id']); + unset($_COOKIE['autologin_auth']); + unset($_COOKIE['autologin_user']); + unset($_COOKIE['autologin_enable']); + setcookie('autologin_auth', "", time() - 3600); + setcookie('autologin_user', "", time() - 3600); + setcookie('autologin_enable', "", time() - 3600); + $this->addFlash('success', 'Successfully logged out'); + return new RedirectResponse($this->generateRoute('login')); + } + public function loginCheckAction(): Response { $request = $this->getRequest(); $userRepository = new User($this->getDbCon()); $credentials = $request->request->all(); - if($userRepository->checkAuthentication($credentials['name'], $credentials['passwd'])) { + $userId = $userRepository->checkAuthentication($credentials['name'], $credentials['passwd']); + if($userId !== false) { + $_SESSION['user.id'] = $userId; $_SESSION['isAuthenticated'] = true; + + if(isset($credentials['autologin'])) { + $token = $userRepository->createAutologinToken($credentials['passwd']); + setcookie('autologin_auth', $token, time() + $_ENV['COOKIE_LIFETIME'], "/"); + setcookie('autologin_user', $credentials['name'], time() + $_ENV['COOKIE_LIFETIME'], "/"); + setcookie('autologin_enable', true, time() + $_ENV['COOKIE_LIFETIME'], "/"); + } return new RedirectResponse($this->generateRoute('default')); } - $this->addFlash('danger', 'Username or password incorrect'); + $this->addFlash('danger', 'Login Failed'); return new RedirectResponse($this->generateRoute('login')); } } \ No newline at end of file diff --git a/src/Repository/User.php b/src/Repository/User.php index beb8654..4a50355 100644 --- a/src/Repository/User.php +++ b/src/Repository/User.php @@ -15,17 +15,59 @@ class User $this->dbcon = $dbcon; } - public function checkAuthentication(string $user, string $password): bool + /** + * @param string $user + * @param string $password + * @param bool $autologin + * @return int|bool + * @throws \Doctrine\DBAL\Driver\Exception + * @throws \Doctrine\DBAL\Exception + */ + public function checkAuthentication(string $user, string $password, bool $autologin = false): int|bool { $userSql = "SELECT * from user WHERE email = :user"; $userStmt = $this->dbcon->prepare($userSql); $userRslt = $userStmt->execute([':user' => $user]); if($user = $userRslt->fetchAssociative()) { - $shaPass = hash('sha256', $password); - if(password_verify($shaPass, $user['password'])) { - return true; + if($autologin) $password = $this->getPassFromAutologinToken($password); + + $password = hash($_ENV['HASHING_METHOD'], $password); + + if(password_verify($password, $user['password'])) { + return $user['id']; } } return false; } + + public function createAutologinToken($password): string { + $method = $_ENV['ENCRYPTION_METHOD']; + $key = hash($_ENV['HASHING_METHOD'], $_ENV['SECRET'], true); + $iv = openssl_random_pseudo_bytes(16); + $time = time(); + + $ciphertext = openssl_encrypt($password . substr($time, -7), $method, $key, OPENSSL_RAW_DATA, $iv); + $hash = hash_hmac($_ENV['HASHING_METHOD'], $ciphertext . $iv, $key, true); + return base64_encode(json_encode(['time' => $time, 'password' => base64_encode($iv . $hash . $ciphertext)])); + } + + public function getPassFromAutologinToken($token) { + $extracted = json_decode(base64_decode($token), true); + $method = $_ENV['ENCRYPTION_METHOD']; + $encrypted = base64_decode($extracted['password']); + $iv = substr($encrypted, 0, 16); + $hash = substr($encrypted, 16, 32); + $ciphertext = substr($encrypted, 48); + $key = hash($_ENV['HASHING_METHOD'], $_ENV['SECRET'], true); + + if (!hash_equals(hash_hmac($_ENV['HASHING_METHOD'], $ciphertext . $iv, $key, true), $hash)) return null; + + $decryption = openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv); + + return ( + (($extracted['time'] + $_ENV['COOKIE_LIFETIME']) > time()) && + substr($extracted['time'], -7) == substr($decryption, -7) + ) + ? substr($decryption, 0, -7) : null; + } } \ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index 605a2e9..caddd22 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -14,7 +14,7 @@ -
+
diff --git a/templates/flashes.html.twig b/templates/flashes.html.twig index 18c3040..3af10e3 100644 --- a/templates/flashes.html.twig +++ b/templates/flashes.html.twig @@ -1,6 +1,8 @@ -{% for flash in flashes %} - -{% endfor %} \ No newline at end of file +{% if flashes is defined and flashes is not empty %} + {% for flash in flashes %} + + {% endfor %} +{% endif %} \ No newline at end of file