diff --git a/.gitignore b/.gitignore index bb48e41..5684269 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ www/webdata/config.php www/temp/ www/css/archive/* www/js/archive/* +vendor +config/config.ini +tmp/ \ No newline at end of file diff --git a/Router.php b/Router.php new file mode 100644 index 0000000..332c36e --- /dev/null +++ b/Router.php @@ -0,0 +1,56 @@ + __dir__.'/tmp/']:[]; + +$loader = new Twig\Loader\FilesystemLoader(__dir__ . '/templates/'.Config::get('system', 'template', 'default')."/"); +$twig = new Twig\Environment($loader, $twigSettings); + + +class Config { + private static $instance = null; + private $config; + + private function __construct($file = 'config/config.ini'){ + $this->config = parse_ini_file($file, true); + } + + public static function get(string $category, string $config, $default = false){ + // Singleton method to avoid reading the same file (too) many times + if (self::$instance == null){ + self::$instance = new Config(); + } + + $conf = self::$instance; + + if(!empty($conf->config[$category]) && !empty($conf->config[$category][$config])){ + return $conf->config[$category][$config]; + } + return $default; + } +} \ No newline at end of file diff --git a/application/Auth.php b/application/Auth.php new file mode 100644 index 0000000..8a22048 --- /dev/null +++ b/application/Auth.php @@ -0,0 +1,173 @@ +num_rows == 1){ + // Verify password + list($dbPass, $dbUserId, $dbUserName) = $getUserRes->fetch_row(); + } + + if(password_verify(PwdGen($pass), $dbPass)){ + session_regenerate_id(); + + $md5agent = md5($_SERVER['HTTP_USER_AGENT']); + $userKey = GenKey(); + $shaUserKey = sha1($userKey); + $expire = $stayLoggedIn?30:0; // if "stay logged in", stay logged in for 30 days. + + $updateUserSQL = "INSERT INTO user_login (user_id, ckey, ctime, expire, agent) VALUES (?, ?, ?, ?, ?);"; + if(DB::query($updateUserSQL, $dbUserId, $userKey, time(), $expire, $md5agent)){ + $_SESSION['user_id'] = $dbUserId; + $_SESSION['user_name'] = $dbUserName; + $_SESSION['user_agent'] = $md5agent; + $_SESSION['user_key'] = $shaUserKey; + + if($stayLoggedIn){ + setcookie("auth", $shaUserKey.".".$dbUserId, time() + (30 * 24*60*60), "/", $_SERVER['HTTP_HOST'], isset($_SERVER['HTTPS']), true ); + } + return true; + } + else { + $err[] = "Failed to login.\n".$db->error; + } + } + else { + $err[] = "Username and/or Password is wrong."; + } + + return $err; + } + + + public static function loginFromAuthKey(): bool { + if(isset($_COOKIE['auth'])){ + $md5agent = md5($_SERVER['HTTP_USER_AGENT']); + + $cookieSplode = explode('.', $_COOKIE['auth']); + if(count($cookieSplode) == 2){ + $tkey = $cookieSplode[0]; + $tuid = $cookieSplode[1]; + $verifyLoginCookieRes = DB::query(" + SELECT ul.user_id, ul.ckey, ul.ctime, ul.expire, ul.agent, u.full_name + FROM user_login ul + INNER JOIN user u ON ul.user_id = u.user_id + WHERE ul.user_id = ? + ORDER BY ctime DESC", $tuid); + while($row = $verifyLoginCookieRes->fetch_assoc()){ + if($row['expire'] == 0){ $row['expire'] = 1; } + if( + sha1($row['ckey']) == $tkey && + $row['agent'] == $md5agent && + ($row['ctime']+($row['expire']*24*60*60) > time()) + ){ + session_regenerate_id(); + $_SESSION['user_id'] = $row['user_id']; + $_SESSION['user_name'] = $row['full_name']; + $_SESSION['user_agent'] = $md5agent; + $_SESSION['user_key'] = sha1($row['ckey']); + + return true; + } + } + } + } + return false; + } + + public static function logout(){} + + + public static function checkLogin($strict = false): bool { + $Auth = Auth::getInstance(); + + if( + ($Auth->isLoggedIn && !$strict) || + $Auth->checkSessionLogin() || + $Auth->loginFromAuthKey() + ) { + return true; + } + + unset($_SESSION['user_id']); + return false; + } + + private function checkSessionLogin(): bool { + if(isset($_SESSION['user_id'])){ + $md5agent = md5($_SERVER['HTTP_USER_AGENT']); + if($md5agent == @$_SESSION['user_agent']){ + $verifyLoginSessionRes = DB::query(" + SELECT expire, ctime, ckey + FROM `user_login` + WHERE user_id = ? + AND agent = ?", + $_SESSION['user_id'], $md5agent); + + while ($row = $verifyLoginSessionRes->fetch_assoc()){ + if( + (sha1($row['ckey']) == $_SESSION['user_key']) && + ($row['expire'] == 0 || $row['ctime']+($row['expire']*24*60*60) > time()) + ){ + return true; + } + } + } + } + return false; + } +} + +function PwdGen($pass, $returnHashed = false): string { + $pepper = Config::get('security', 'pepper', 'IAmBadAtSecurity'); + $pwd_peppered = hash_hmac("sha256", $pass, $pepper); + if(!$returnHashed){ + return $pwd_peppered; + } + + return password_hash($pwd_peppered, PASSWORD_ARGON2ID, ['threads' => 2]); +} + +function GenKey($length = 21): string{ + $password = ""; + $possible = "0123456789abcdefghijkmnopqrstuvwxyz"; + + $i = 0; + + while($i < $length){ + $char = substr($possible, mt_rand(0, strlen($possible)-1), 1); + + if(!strstr($password, $char)){ + $password .= $char; + $i++; + } + } + + return $password; +} \ No newline at end of file diff --git a/application/DB.php b/application/DB.php new file mode 100644 index 0000000..6611bdf --- /dev/null +++ b/application/DB.php @@ -0,0 +1,82 @@ +db = new mysqli( + Config::get('database', 'host'), + Config::get('database', "user"), + Config::get('database', "pass"), + Config::get('database', "database") + ); + + if($this->db->connect_error){ + die("Connection failed: " . $this->db->connect_error); + } + } + + public static function getInstance(): DB { + if(self::$instance == null){ + self::$instance = new DB(); + } + + return self::$instance; + } + + public static function query($sql, ...$params){ + return self::doQuery(0, $sql, ...$params); + } + + public static function queryTest($sql, ...$params){ + return self::doQuery(1, $sql, ...$params); + } + + public static function queryPreview($sql, ...$params){ + return self::doQuery(2, $sql, ...$params); + } + + private static function doQuery($mode, $sql, ...$params){ + /* + Modes: + 0: Execute normally + 1: Execute, but print query + 2: Only print query + */ + + // Check if query parameters comes as an array, or as individual arguments + if(count($params) == 1 && is_array($params[0])){ + $params = $params[0]; + } + + // Create type-specification for query + $types = ""; + foreach($params as &$value){ + if(is_double($value)){ + $types .= "d"; + } + elseif(is_integer($value)){ + $types .= "i"; + } + else { + $types .= "s"; + } + } + + $db = DB::getInstance(); + + if($mode >= 1){ + printf(str_replace('?', "'%s'", $sql)."\n", ...$params); + } + if($mode >= 2) { + return []; + } + + $stmt = $db->db->prepare($sql); + $stmt->bind_param($types, ...$params); + $stmt->execute(); + + return $stmt->get_result(); + } +} \ No newline at end of file diff --git a/application/WebPage.php b/application/WebPage.php new file mode 100644 index 0000000..852c5c1 --- /dev/null +++ b/application/WebPage.php @@ -0,0 +1,92 @@ +pr = Config::get('system', 'projectroot', ''); + $this->loggedIn = Auth::checkLogin(); + + if($this instanceof RequireAuth && !$this->loggedIn) { + $returnTo = str_ireplace('index.php', '', $_SERVER['PHP_SELF']); + $_SESSION['pre-auth'] = $returnTo; + header("Location: ".$this->pr."/login.php"); + return; + } + + try { + $this->prepareNavbar(); + $this->load(); + + if(!$this->template){ + $this->template = str_replace('.php', '.html', $_SERVER['SCRIPT_NAME']); + } + + if($this->httpStatus == 200){ + global $twig; + + if(DEBUG){ + header("X-Template: ".$this->template); + header("X-Script-name: ".$_SERVER['SCRIPT_NAME']); + } + + http_response_code(200); + + echo $twig->render($this->template, $this->vars()); + } + else { + http_response_code($this->httpStatus); + print_r( $this->vars() ); + } + } + catch (\Twig\Error\LoaderError $e){ + http_response_code(500); + if( $e->getCode() == 0){ + print_r($e->getMessage()); + } + else { + echo $e; + } + } + catch(\Twig\Error\RuntimeError | \Twig\Error\SyntaxError | Exception $e) { + http_response_code(500); + echo $e; + print_r( $this->vars() ); + } + } + + function load(){ throw new Exception("Incomplete implementation"); } + + function vars(): array { + return (array) $this; + } + + private function prepareNavbar(){ + $this->activePage = str_replace('index.php', '', $_SERVER['REQUEST_URI']); + + $this->navbar['links'] = array_merge([ + ["href"=>"/", "name"=>"Home", "active"=>$this->activePage=="/"?'active':''], + ["href"=>"/plan/", "name"=>"Plan", "active"=>$this->activePage=="/plan/"?'active':''], + ["href"=>"/review/", "name"=>"Review", "active"=>$this->activePage=="/review/"?'active':''] + ], $this->navbar ?? []); + } +} + +class WebPageAuth extends WebPage { + public function __construct(){ + if(Auth::checklogin()){ + parent::__construct(); + } + } +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4777dcc --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "twbs/bootstrap": "5.1.3", + "twig/twig": "^3.3", + "ext-mysqli": "*" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..215e281 --- /dev/null +++ b/composer.lock @@ -0,0 +1,310 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "0daa3afffc94d70bd593d310cef916f8", + "packages": [ + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "twbs/bootstrap", + "version": "v5.1.3", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "1a6fdfae6be09b09eaced8f0e442ca6f7680a61e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/1a6fdfae6be09b09eaced8f0e442ca6f7680a61e", + "reference": "1a6fdfae6be09b09eaced8f0e442ca6f7680a61e", + "shasum": "" + }, + "replace": { + "twitter/bootstrap": "self.version" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Otto", + "email": "markdotto@gmail.com" + }, + { + "name": "Jacob Thornton", + "email": "jacobthornton@gmail.com" + } + ], + "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", + "homepage": "https://getbootstrap.com/", + "keywords": [ + "JS", + "css", + "framework", + "front-end", + "mobile-first", + "responsive", + "sass", + "web" + ], + "support": { + "issues": "https://github.com/twbs/bootstrap/issues", + "source": "https://github.com/twbs/bootstrap/tree/v5.1.3" + }, + "time": "2021-10-09T06:43:19+00:00" + }, + { + "name": "twig/twig", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "8442df056c51b706793adf80a9fd363406dd3674" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8442df056c51b706793adf80a9fd363406dd3674", + "reference": "8442df056c51b706793adf80a9fd363406dd3674", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.3.10" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2022-04-06T06:47:41+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/models/Model.php b/models/Model.php new file mode 100644 index 0000000..62d92ad --- /dev/null +++ b/models/Model.php @@ -0,0 +1,13 @@ +query("SELECT * FROM recipe"); + while($row = $result->fetch_assoc()){ + $recipe = new Recipe(); + $recipe-> + + $recipes[] = $recipe; + } + + return $recipes; + } +} \ No newline at end of file diff --git a/models/products.php b/models/products.php new file mode 100644 index 0000000..2e0ebcf --- /dev/null +++ b/models/products.php @@ -0,0 +1,5 @@ + + +
+ + + +Coming soon
+{{ changes }}+
Do you not have an account? Register
+Coming soon
-=$changes;?>-
Do you not have an account? Register
-