From 23146417e6df72ff3f1277bb9c9583f15236d981 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Sun, 4 Jun 2017 18:28:08 +0200 Subject: [PATCH] Use session/cookie based authentication with login form instead of basic auth --- Grocy.php | 44 +++++++++++++++++++++++++++++++++ composer.json | 3 +-- grocy.phpproj | 2 ++ index.php | 64 ++++++++++++++++++++++++++++++++++++++++++------ version.txt | 2 +- views/layout.php | 14 +++++++++++ views/login.js | 12 +++++++++ views/login.php | 23 +++++++++++++++++ 8 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 views/login.js create mode 100644 views/login.php diff --git a/Grocy.php b/Grocy.php index 60c676ea..a28d2d6c 100644 --- a/Grocy.php +++ b/Grocy.php @@ -101,4 +101,48 @@ class Grocy return self::$InstalledVersion; } + + /** + * @return boolean + */ + public static function IsValidSession($sessionKey) + { + if ($sessionKey === null || empty($sessionKey)) + { + return false; + } + else + { + return file_exists(__DIR__ . "/data/sessions/$sessionKey.txt"); + } + } + + /** + * @return string + */ + public static function CreateSession() + { + if (!file_exists(__DIR__ . '/data/sessions')) + { + mkdir(__DIR__ . '/data/sessions'); + } + + $now = time(); + foreach (new FilesystemIterator(__DIR__ . '/data/sessions') as $file) + { + if ($now - $file->getCTime() >= 2678400) //31 days + { + unlink(__DIR__ . '/data/sessions/' . $file->getFilename()); + } + } + + $newSessionKey = uniqid() . uniqid() . uniqid(); + file_put_contents(__DIR__ . "/data/sessions/$newSessionKey.txt", ''); + return $newSessionKey; + } + + public static function RemoveSession($sessionKey) + { + unlink(__DIR__ . "/data/sessions/$sessionKey.txt"); + } } diff --git a/composer.json b/composer.json index 095e0937..c92fd905 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,6 @@ "require": { "slim/slim": "^3.8", "slim/php-view": "^2.2", - "morris/lessql": "^0.3.4", - "tuupola/slim-basic-auth": "^2.2" + "morris/lessql": "^0.3.4" } } diff --git a/grocy.phpproj b/grocy.phpproj index f59648ee..f5f02a63 100644 --- a/grocy.phpproj +++ b/grocy.phpproj @@ -30,6 +30,7 @@ + @@ -60,6 +61,7 @@ + diff --git a/index.php b/index.php index db99d49d..55c81c40 100644 --- a/index.php +++ b/index.php @@ -15,6 +15,7 @@ require_once __DIR__ . '/GrocyPhpHelper.php'; $app = new \Slim\App(new \Slim\Container([ 'settings' => [ 'displayErrorDetails' => true, + 'determineRouteBeforeAppMiddleware' => true ], ])); $container = $app->getContainer(); @@ -22,18 +23,65 @@ $container['renderer'] = new PhpRenderer('./views'); if (!Grocy::IsDemoInstallation()) { - $isHttpsReverseProxied = !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'; - $app->add(new \Slim\Middleware\HttpBasicAuthentication([ - 'realm' => 'grocy', - 'secure' => !$isHttpsReverseProxied, - 'users' => [ - HTTP_USER => HTTP_PASSWORD - ] - ])); + $sessionMiddleware = function(Request $request, Response $response, callable $next) + { + $route = $request->getAttribute('route'); + $routeName = $route->getName(); + + if (!Grocy::IsValidSession($_COOKIE['grocy_session']) && $routeName !== 'login') + { + $response = $response->withRedirect('/login'); + } + else + { + $response = $next($request, $response); + } + + return $response; + }; + + $app->add($sessionMiddleware); } $db = Grocy::GetDbConnection(); +$app->get('/login', function(Request $request, Response $response) +{ + return $this->renderer->render($response, '/layout.php', [ + 'title' => 'Login', + 'contentPage' => 'login.php' + ]); +})->setName('login'); + +$app->post('/login', function(Request $request, Response $response) +{ + $postParams = $request->getParsedBody(); + if (isset($postParams['username']) && isset($postParams['password'])) + { + if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD) + { + $sessionKey = Grocy::CreateSession(); + setcookie('grocy_session', $sessionKey, time()+2592000); //30 days + + return $response->withRedirect('/'); + } + else + { + return $response->withRedirect('/login?invalid=true'); + } + } + else + { + return $response->withRedirect('/login?invalid=true'); + } +})->setName('login'); + +$app->get('/logout', function(Request $request, Response $response) +{ + Grocy::RemoveSession($_COOKIE['grocy_session']); + return $response->withRedirect('/'); +}); + $app->get('/', function(Request $request, Response $response) use($db) { $db = Grocy::GetDbConnection(true); //For database schema migration diff --git a/version.txt b/version.txt index 589268e6..e21e727f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.3.0 \ No newline at end of file +1.4.0 \ No newline at end of file diff --git a/views/layout.php b/views/layout.php index 4cc4f6d1..ff582724 100644 --- a/views/layout.php +++ b/views/layout.php @@ -38,6 +38,14 @@ grocy + + diff --git a/views/login.js b/views/login.js new file mode 100644 index 00000000..08134ca3 --- /dev/null +++ b/views/login.js @@ -0,0 +1,12 @@ +$(function() +{ + $('.logout-button').hide(); + + $('#username').focus(); + + if (Grocy.GetUriParam('invalid') === 'true') + { + $('#login-error').text('Invalid credentials, please try again.'); + $('#login-error').show(); + } +}); diff --git a/views/login.php b/views/login.php new file mode 100644 index 00000000..e323543b --- /dev/null +++ b/views/login.php @@ -0,0 +1,23 @@ +
+ +

Login

+ +
+ +
+ + +
+
+ +
+ + +
+
+ + + +
+ +