Hello web interface (user accounts with bcrypt)
This commit is contained in:
parent
2c0ddfca78
commit
3d4ca1421d
4 changed files with 128 additions and 65 deletions
|
@ -11,13 +11,12 @@ How do I install this?
|
|||
2. Copy all files to your web hosting.
|
||||
3. Open your website in a browser.
|
||||
|
||||
Installation requirements are Apache, mod_rewrite and PHP 5.3.
|
||||
With Yellow you don't get a lot of extra stuff. There are [Yellow extensions](https://github.com/markseu/yellowcms-extensions/blob/master/README.md).
|
||||
Installation requirements are Apache, mod_rewrite, mod_ssl and PHP 5.3.
|
||||
|
||||
How do I get started?
|
||||
----------------------
|
||||
You already have everything you need. Start by editing your website.
|
||||
That's it. For more information see [Yellow documentation](https://github.com/markseu/yellowcms-extensions/blob/master/documentation/README.md).
|
||||
---------------------
|
||||
You already have everything you need. Start by editing your own website.
|
||||
There are [Yellow extensions](https://github.com/markseu/yellowcms-extensions). For more information see [Yellow documentation](https://github.com/markseu/yellowcms-extensions/blob/master/documentation/README.md)
|
||||
|
||||
License and thanks
|
||||
------------------
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// Yellow user accounts
|
||||
// Format: Email, password (sha256 with email prefix as salt), name, language, home
|
||||
// Format: Email, password hash, name, language, home
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// Web interface core plugin
|
||||
class YellowWebinterface
|
||||
{
|
||||
const Version = "0.2.8";
|
||||
const Version = "0.2.9";
|
||||
var $yellow; //access to API
|
||||
var $users; //web interface users
|
||||
var $active; //web interface is active location? (boolean)
|
||||
|
@ -19,12 +19,14 @@ class YellowWebinterface
|
|||
$this->yellow->config->setDefault("webinterfaceLocation", "/edit/");
|
||||
$this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
|
||||
$this->yellow->config->setDefault("webinterfaceUserHome", "/");
|
||||
$this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt");
|
||||
$this->yellow->config->setDefault("webinterfaceUserHashCost", "10");
|
||||
$this->users = new YellowWebinterfaceUsers($yellow);
|
||||
$this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"));
|
||||
}
|
||||
|
||||
// Handle web interface location
|
||||
function onRequest($location)
|
||||
function onRequest($serverName, $serverBase, $location, $fileName)
|
||||
{
|
||||
$statusCode = 0;
|
||||
if($this->checkLocation($location))
|
||||
|
@ -141,8 +143,17 @@ class YellowWebinterface
|
|||
if(!empty($email) && !empty($password) && (empty($home) || $home[0]=='/'))
|
||||
{
|
||||
$fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile");
|
||||
$statusCode = $this->users->createUser($fileName, $email, $password, $name, $language, $home) ? 200 : 500;
|
||||
if($statusCode != 200) echo "ERROR updating configuration: Can't write file '$fileName'!\n";
|
||||
$algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
|
||||
$cost = $this->yellow->config->get("webinterfaceUserHashCost");
|
||||
$hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
|
||||
if(empty($hash))
|
||||
{
|
||||
$statusCode = 500;
|
||||
echo "ERROR creating hash: Algorithm '$algorithm' not supported!\n";
|
||||
} else {
|
||||
$statusCode = $this->users->createUser($fileName, $email, $hash, $name, $language, $home) ? 200 : 500;
|
||||
if($statusCode != 200) echo "ERROR updating configuration: Can't write file '$fileName'!\n";
|
||||
}
|
||||
echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "");
|
||||
echo ($this->users->isExisting($email) ? "updated" : "created")."\n";
|
||||
} else {
|
||||
|
@ -230,10 +241,10 @@ class YellowWebinterface
|
|||
$this->loginFailed = true;
|
||||
}
|
||||
} else if(isset($_COOKIE["login"])) {
|
||||
$cookie = $_COOKIE["login"];
|
||||
if($this->users->checkCookie($cookie))
|
||||
list($email, $session) = $this->users->getCookieInformation($_COOKIE["login"]);
|
||||
if($this->users->checkCookie($email, $session))
|
||||
{
|
||||
$this->users->email = $this->users->getCookieEmail($cookie);
|
||||
$this->users->email = $email;
|
||||
} else {
|
||||
$this->loginFailed = true;
|
||||
}
|
||||
|
@ -315,22 +326,21 @@ class YellowWebinterfaceUsers
|
|||
}
|
||||
|
||||
// Set user data
|
||||
function set($email, $password, $name, $language, $home)
|
||||
function set($email, $hash, $name, $language, $home)
|
||||
{
|
||||
$this->users[$email] = array();
|
||||
$this->users[$email]["email"] = $email;
|
||||
$this->users[$email]["password"] = $password;
|
||||
$this->users[$email]["hash"] = $hash;
|
||||
$this->users[$email]["name"] = $name;
|
||||
$this->users[$email]["language"] = $language;
|
||||
$this->users[$email]["home"] = $home;
|
||||
$this->users[$email]["session"] = hash("sha256", $email.$password.$password.$email);
|
||||
}
|
||||
|
||||
// Create or update user in file
|
||||
function createUser($fileName, $email, $password, $name, $language, $home)
|
||||
function createUser($fileName, $email, $hash, $name, $language, $home)
|
||||
{
|
||||
$email = strreplaceu(',', '-', $email);
|
||||
$password = hash("sha256", $email.$password);
|
||||
$hash = strreplaceu(',', '-', $hash);
|
||||
$fileNewUser = true;
|
||||
$fileData = @file($fileName);
|
||||
if($fileData)
|
||||
|
@ -345,7 +355,7 @@ class YellowWebinterfaceUsers
|
|||
$name = strreplaceu(',', '-', empty($name) ? $matches[3] : $name);
|
||||
$language = strreplaceu(',', '-', empty($language) ? $matches[4] : $language);
|
||||
$home = strreplaceu(',', '-', empty($home) ? $matches[5] : $home);
|
||||
$fileDataNew .= "$email,$password,$name,$language,$home\n";
|
||||
$fileDataNew .= "$email,$hash,$name,$language,$home\n";
|
||||
$fileNewUser = false;
|
||||
continue;
|
||||
}
|
||||
|
@ -358,7 +368,7 @@ class YellowWebinterfaceUsers
|
|||
$name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
|
||||
$language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
|
||||
$home = strreplaceu(',', '-', empty($home) ? $this->yellow->config->get("webinterfaceUserHome") : $home);
|
||||
$fileDataNew .= "$email,$password,$name,$language,$home\n";
|
||||
$fileDataNew .= "$email,$hash,$name,$language,$home\n";
|
||||
}
|
||||
return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
|
||||
}
|
||||
|
@ -366,7 +376,8 @@ class YellowWebinterfaceUsers
|
|||
// Check user login
|
||||
function checkUser($email, $password)
|
||||
{
|
||||
return $this->isExisting($email) && hash("sha256", $email.$password)==$this->users[$email]["password"];
|
||||
$algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
|
||||
return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($password, $algorithm, $this->users[$email]["hash"]);
|
||||
}
|
||||
|
||||
// Create browser cookie
|
||||
|
@ -374,30 +385,30 @@ class YellowWebinterfaceUsers
|
|||
{
|
||||
if($this->isExisting($email))
|
||||
{
|
||||
$salt = hash("sha256", uniqid(mt_rand(), true));
|
||||
$text = $email.";".$salt.";".hash("sha256", $salt.$this->users[$email]["session"]);
|
||||
setcookie($cookieName, $text, time()+60*60*24*30*365*10, "/");
|
||||
$location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
|
||||
$session = $this->yellow->toolbox->createHash($this->users[$email]["hash"], "sha256");
|
||||
if(empty($session)) $session = "error-hash-algorithm-sha256";
|
||||
setcookie($cookieName, "$email,$session", time()+60*60*24*30*365, $location);
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy browser cookie
|
||||
function destroyCookie($cookieName)
|
||||
{
|
||||
setcookie($cookieName, "", time()-3600, "/");
|
||||
$location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
|
||||
setcookie($cookieName, "", time()-3600, $location);
|
||||
}
|
||||
|
||||
// Return information from browser cookie
|
||||
function getCookieInformation($cookie)
|
||||
{
|
||||
return explode(',', $cookie, 2);
|
||||
}
|
||||
|
||||
// Check user login from browser cookie
|
||||
function checkCookie($cookie)
|
||||
function checkCookie($email, $session)
|
||||
{
|
||||
list($email, $salt, $session) = explode(';', $cookie);
|
||||
return $this->isExisting($email) && hash("sha256", $salt.$this->users[$email]["session"])==$session;
|
||||
}
|
||||
|
||||
// Return user email from browser cookie
|
||||
function getCookieEmail($cookie)
|
||||
{
|
||||
list($email, $salt, $session) = explode(';', $cookie);
|
||||
return $email;
|
||||
return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($this->users[$email]["hash"], "sha256", $session);
|
||||
}
|
||||
|
||||
// Return user name
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// Yellow main class
|
||||
class Yellow
|
||||
{
|
||||
const Version = "0.2.17";
|
||||
const Version = "0.2.18";
|
||||
var $page; //current page
|
||||
var $pages; //pages from file system
|
||||
var $config; //configuration
|
||||
|
@ -65,7 +65,7 @@ class Yellow
|
|||
if(method_exists($value["obj"], "onRequest"))
|
||||
{
|
||||
$this->pages->requestHandler = $key;
|
||||
$statusCode = $value["obj"]->onRequest($location);
|
||||
$statusCode = $value["obj"]->onRequest($serverName, $serverBase, $location, $fileName);
|
||||
if($statusCode != 0) break;
|
||||
}
|
||||
}
|
||||
|
@ -1527,18 +1527,18 @@ class YellowToolbox
|
|||
{
|
||||
switch($statusCode)
|
||||
{
|
||||
case 0: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode No data"; break;
|
||||
case 200: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode OK"; break;
|
||||
case 301: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved permanently"; break;
|
||||
case 302: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved temporarily"; break;
|
||||
case 303: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Reload please"; break;
|
||||
case 304: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not modified"; break;
|
||||
case 400: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Bad request"; break;
|
||||
case 401: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unauthorised"; break;
|
||||
case 404: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not found"; break;
|
||||
case 424: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Does not exist"; break;
|
||||
case 500: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Server error"; break;
|
||||
default: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unknown status";
|
||||
case 0: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode No data"; break;
|
||||
case 200: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode OK"; break;
|
||||
case 301: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved permanently"; break;
|
||||
case 302: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved temporarily"; break;
|
||||
case 303: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Reload please"; break;
|
||||
case 304: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not modified"; break;
|
||||
case 400: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Bad request"; break;
|
||||
case 401: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unauthorised"; break;
|
||||
case 404: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not found"; break;
|
||||
case 424: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Does not exist"; break;
|
||||
case 500: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Server error"; break;
|
||||
default: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unknown status";
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
@ -1733,21 +1733,6 @@ class YellowToolbox
|
|||
if(preg_match("/^.*\/([\w\-]+)/", $text, $matches)) $text = ucfirst($matches[1]);
|
||||
return $text;
|
||||
}
|
||||
|
||||
// Detect web browser language
|
||||
function detectBrowserLanguage($languagesAllowed, $languageDefault)
|
||||
{
|
||||
$language = $languageDefault;
|
||||
if(isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
|
||||
{
|
||||
foreach(preg_split("/,\s*/", $_SERVER["HTTP_ACCEPT_LANGUAGE"]) as $string)
|
||||
{
|
||||
$tokens = explode(';', $string, 2);
|
||||
if(in_array($tokens[0], $languagesAllowed)) { $language = $tokens[0]; break; }
|
||||
}
|
||||
}
|
||||
return $language;
|
||||
}
|
||||
|
||||
// Detect PNG and JPG image dimensions
|
||||
function detectImageDimensions($fileName)
|
||||
|
@ -1788,7 +1773,75 @@ class YellowToolbox
|
|||
}
|
||||
return array($width, $height);
|
||||
}
|
||||
|
||||
|
||||
// Create random text for cryptography
|
||||
function createSalt($length, $bcryptFormat = false)
|
||||
{
|
||||
$dataBuffer = $salt = "";
|
||||
$dataBufferSize = $bcryptFormat ? intval(ceil($length/4) * 3) : intval(ceil($length/2));
|
||||
if(empty($dataBuffer) && function_exists("mcrypt_create_iv"))
|
||||
{
|
||||
$dataBuffer = @mcrypt_create_iv($dataBufferSize, MCRYPT_DEV_URANDOM);
|
||||
}
|
||||
if(empty($dataBuffer) && function_exists("openssl_random_pseudo_bytes"))
|
||||
{
|
||||
$dataBuffer = @openssl_random_pseudo_bytes($dataBufferSize);
|
||||
}
|
||||
if(strlenb($dataBuffer) == $dataBufferSize)
|
||||
{
|
||||
if($bcryptFormat)
|
||||
{
|
||||
$salt = substrb(base64_encode($dataBuffer), 0, $length);
|
||||
$base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
$bcrypt64Chars = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
$salt = strtr($salt, $base64Chars, $bcrypt64Chars);
|
||||
} else {
|
||||
$salt = substrb(bin2hex($dataBuffer), 0, $length);
|
||||
}
|
||||
}
|
||||
return $salt;
|
||||
}
|
||||
|
||||
// Create hash with random salt
|
||||
function createHash($text, $algorithm, $cost = 0)
|
||||
{
|
||||
$hash = "";
|
||||
switch($algorithm)
|
||||
{
|
||||
case "bcrypt": $prefix = sprintf("$2y$%02d$", $cost);
|
||||
$salt = $this->createSalt(22, true);
|
||||
$hash = crypt($text, $prefix.$salt);
|
||||
if(empty($salt) || strlenb($hash)!=60) $hash = "";
|
||||
break;
|
||||
case "sha256": $prefix = "$5y$";
|
||||
$salt = $this->createSalt(32);
|
||||
$hash = "$prefix$salt".hash("sha256", $salt.$text);
|
||||
if(empty($salt) || strlenb($hash)!=100) $hash = "";
|
||||
break;
|
||||
}
|
||||
return $hash;
|
||||
}
|
||||
|
||||
// Verify that text matches hash
|
||||
function verifyHash($text, $algorithm, $hash)
|
||||
{
|
||||
$hashCalculated = "";
|
||||
switch($algorithm)
|
||||
{
|
||||
case "bcrypt": if(substrb($hash, 0, 4) == "$2y$") $hashCalculated = crypt($text, $hash); break;
|
||||
case "sha256": if(substrb($hash, 0, 4) == "$5y$")
|
||||
{
|
||||
$prefix = substrb($hash, 0, 4);
|
||||
$salt = substrb($hash, 4, 32);
|
||||
$hashCalculated = "$prefix$salt".hash("sha256", $salt.$text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
$ok = !empty($hashCalculated) && strlenb($hashCalculated)==strlenb($hash);
|
||||
if($ok) for($i=0; $i<strlenb($hashCalculated); ++$i) $ok &= $hashCalculated[$i] == $hash[$i];
|
||||
return $ok;
|
||||
}
|
||||
|
||||
// Start timer
|
||||
function timerStart(&$time)
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue