Added logout and autologin

This commit is contained in:
Jeroen De Meerleer 2021-04-09 15:14:35 +02:00
parent c98e1add62
commit 70ba6473e9
Signed by: JeroenED
GPG Key ID: 28CCCB8F62BFADD6
8 changed files with 113 additions and 19 deletions

View File

@ -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
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

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ vendor/
webcron.old/config.inc.php
\.idea/
.env
storage/database.sqlite
storage/database.sqlite
.DS_Store

View File

@ -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'

View File

@ -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));

View File

@ -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'));
}
}

View File

@ -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;
}
}

View File

@ -14,7 +14,7 @@
</head>
<body>
<div class="container-fluid py-3">
<div class="container-fluid">
<div class="row py-3">
<div class="col-xs-12 col-sm-12">
<div class="page-header">
@ -28,6 +28,7 @@
<li class="nav-item"><a class="nav-link" href="{{ path('job_index') }}">Overview</a></li>
<li class="nav-item"><a class="nav-link" href="addjob.php">Add a new cronjob</a></li>
<li class="nav-item"><a class="nav-link" href="config.php">Configuration</a></li>
<li class="nav-item"><a class="nav-link" href="{{ path('logout') }}">Logout</a></li>
</ul>
</div>
<div class="col-xs-12 col-sm-10">

View File

@ -1,6 +1,8 @@
{% for flash in flashes %}
<div class="alert alert-{{ flash.category }} alert-dismissible fade show" role="alert">
{{ flash.content }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% if flashes is defined and flashes is not empty %}
{% for flash in flashes %}
<div class="alert alert-{{ flash.category }} alert-dismissible fade show" role="alert">
{{ flash.content }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}