Compare commits

...
25 Commits
Author SHA1 Message Date
marc-go ad9290d2ad Refactor entry API: consolidate entry handling, update naming conventions, and enhance error reporting 2026-10-11 19:19:36 +02:00
marc-go 205f2e2f73 Enhance API error handling, add lastError tracking, and implement admin home template 2026-10-11 14:37:56 +02:00
admin ce0777bb33 Update file permissions and enhance error handling in API responses 2026-10-10 21:26:38 +02:00
marc-go eb67458ab1 Enhance API error handling and response structure; update app name retrieval method 2026-10-10 19:23:11 +02:00
marc-go 95f8e22f11 Remove debug_print_backtrace() call from API main library 2026-10-10 15:09:13 +02:00
marc-go b4621fc127 Refactor API files: replace require with require_once for improved file inclusion 2026-10-10 15:06:29 +02:00
marc-go 9cb50d7d28 Refactor API structure: update internal handling, enhance input retrieval, and improve template loading 2026-10-10 15:05:24 +02:00
marc-go a33de15f0c Refactor API structure: add internal handling, update templates, and remove deprecated files 2026-10-10 13:43:18 +02:00
admin 313ffec92c Update file permissions and enhance error handling in login template and setup scripts 2026-10-09 21:21:44 +02:00
marc-go c94122c9fd Fix variable name in token validation and add login template 2026-10-09 19:36:56 +02:00
marc-go 6b5a299079 Refactor authentication: streamline cookie handling and enhance username retrieval in responses 2026-10-09 19:20:53 +02:00
admin a1bbae383a Add cookie token lifetime configuration and implement base64 encoding/decoding methods 2026-10-08 16:22:47 +00:00
marc-go bf552e87cc Enhance token generation: include expiration in cookie token and improve authentication checks 2026-10-08 14:50:59 +02:00
marc-go fd1b8d9a0b Refactor entry handling: update date format, enhance entry retrieval, and improve HTML structure 2026-10-07 19:22:40 +02:00
hacker_marc ff2afb4441 Change generateToken to generateCookieToken 2026-10-06 18:24:59 +00:00
marc-go 1a2ee0c176 Refactor API and database interactions: enhance token management, improve entry handling, and add application name retrieval 2026-10-06 19:23:15 +02:00
marc-go 9316818753 Enhance API functionality: add token generation methods, implement entry management, and improve setup process with settings storage 2026-10-06 14:46:25 +02:00
root 8ef5f7a941 Fix Bugs 2026-10-06 12:59:22 +02:00
marc-go 0c73ad14f5 Implement entry page with dynamic content and update template structure 2026-10-05 19:25:13 +02:00
root 1a2e1cc33f Fix Bugs 2026-10-05 18:48:47 +02:00
admin ae54fabd6b Add PASSWORD_DEFAULT to password_hash in api/setup/save.php 2026-10-05 09:43:12 +00:00
admin 0cb0d97529 Add debug settings to setup.html 2026-10-05 09:13:15 +00:00
admin e53e53146c Fix Bugs 2026-10-05 09:12:00 +00:00
admin 3f4e859153 Fix Bugs 2026-10-05 09:09:22 +00:00
admin f2bd887783 Fix Bugs and finish setup.php 2026-10-05 09:09:07 +00:00
27 changed files with 1189 additions and 136 deletions

No files matched your search

+4 -1
View File
@@ -1 +1,4 @@
TOKEN_SIGNATURE=281361f952279bd9530734c67ed04aed2756f0fe00cb998cd6c28324f491484c COOKIE_TOKEN_SIGNATURE=a11a9ea5fd839e616bd08ebc432f641a24ef1f4d74ca478f904e7a3122fc7a05
COOKIE_TOKEN_LIFETIME=86400
API_TOKEN_SIGNATURE=38bcd8a8e9ac3be83ddfcf4c12c19ccb3be70df00e48d43d4f63f54f014d1946
DB_FILE=database.db
+235 -36
View File
@@ -1,10 +1,39 @@
<?php <?php
$debug = true;
if ($debug) {
ini_set("display_errors", 1);
ini_set("display_startup_errors", 1);
error_reporting(E_ALL);
}
// API Main Library // API Main Library
class gbAPI { class gbAPI {
function __construct($authRequired) { public $username;
define("APP_PATH", __DIR__ . "/../"); public $input;
if ($authRequired) { private $db;
private $config;
private $internal;
private $response;
private $lastError;
private function b64url_encode($data) {
return rtrim(strtr(base64_encode($data), "+/", "-_"), "=");
}
private function b64url_decode($data) {
return base64_decode(strtr($data, "-_", "+/"));
}
function __construct($authRequired, $internal) {
if (!defined("APP_PATH")) {
define("APP_PATH", __DIR__ . "/../");
}
$this->internal = $internal;
if ($authRequired && !$internal) {
if (!$this->checkAuth()) { if (!$this->checkAuth()) {
$status = 401; $status = 401;
$data = [ $data = [
@@ -12,16 +41,67 @@ class gbAPI {
"error_text" => "Authentication failed." "error_text" => "Authentication failed."
]; ];
$this->answer($status, $data); $this->lastError = $data;
$this->answer($status, $data, true);
} }
} }
} }
function getConfig() { function getInput() {
return parse_ini_file(APP_PATH . ".env"); if (isset($this->input)) {
return $this->input;
}elseif ($_SERVER["REQUEST_METHOD"] == "POST") {
try {
$input = json_decode(file_get_contents("php://input"), true);
} catch (JsonException $error) {
$error_payload = [
"error" => "invalid_body",
"error_text" => "Can not read request body: " . $error
];
$this->lastError = $error_payload;
$this->answer(400, $error_payload, true);
}
return $input;
}else{
$error_payload = [
"error" => "false_request_method",
"error_text" => "Only POST allowed"
];
$this->lastError = $error_payload;
$this->answer(400, $error_payload, true);
}
} }
function generateToken($username) { function getConfig() {
if (isset($this->config)) {
return $config;
}else{
return parse_ini_file(APP_PATH . ".env");
}
}
function generateCookieToken($username) {
$config = $this->getConfig();
// Generate Token
$payload = [
"user" => $username,
"auth" => true,
"exp" => time() + $config["COOKIE_TOKEN_LIFETIME"]
];
$string = $this->b64url_encode(json_encode($payload));
$secret = $config["COOKIE_TOKEN_SIGNATURE"];
$sig = hash_hmac("sha256", $string, $secret);
return $string . "." . $sig;
}
function generateAPIToken($username) {
$config = $this->getConfig(); $config = $this->getConfig();
// Generate Token // Generate Token
@@ -29,65 +109,184 @@ class gbAPI {
"user" => $username, "user" => $username,
"auth" => true "auth" => true
]; ];
$string = json_encode($payload); $string = $this->b64url_encode(json_encode($payload));
$secret = $config["TOKEN_SIGNATURE"]; $secret = $config["API_TOKEN_SIGNATURE"];
return hash_hmac("sha256", $string, $secret); $sig = hash_hmac("sha256", $string, $secret);
return $string . "." . $sig;
} }
function checkAuth() { function checkAuth() {
if (!isset($_SERVER["HTTP_X_API_KEY"]) && !isset($_COOKIE["auth_token"])) { if (isset($_COOKIE["auth_token"])) {
return false; // Get Config
}elseif (isset($_COOKIE["auth_token"]) && isset($_COOKIE["username"])) { $config = $this->getConfig();
// Check Token // Check Token
$username = $_COOKIE["username"];
$expected = $this->generateToken($username);
$given = $_COOKIE["auth_token"]; $given = $_COOKIE["auth_token"];
if (!is_string($given)) {
return false;
}
$result = hash_equals($expected, $given); $given_string = explode(".", $given);
if (count($given_string) !== 2) {
return false;
}
$sig_expected = hash_hmac("sha256", $given_string[0], $config["COOKIE_TOKEN_SIGNATURE"]);
@$result = hash_equals($sig_expected, $given_string[1]);
if ($result) { if ($result) {
return true; $body = json_decode($this->b64url_decode($given_string[0]), true);
if ($body["exp"] > time()) {
$this->username = $body["user"];
return true;
}else{
return false;
}
}else{
return false;
} }
}elseif (isset($_SERVER["HTTP_X_API_KEY"])) { }elseif (isset($_SERVER["HTTP_X_API_KEY"])) {
// Get config
$config = $this->getConfig();
// Decode Token // Decode Token
$payload = json_decode(base64_decode($_SERVER["HTTP_X_API_KEY"]), true); $given = $_SERVER["HTTP_X_API_KEY"];
$username = $payload["username"];
$given = $payload["auth_token"]; if (!is_string($given)) {
$expected = $this->generateToken($username); return false;
$result = hash_equals($expected, $given);
if ($result) {
return true;
} }
$given_string = explode(".", $given);
if (count($given_string) !== 2) {
return false;
}
$sig_expected = hash_hmac("sha256", $given_string[0], $config["API_TOKEN_SIGNATURE"]);
@$result = hash_equals($sig_expected, $given_string[1]);
} }
return false; return false;
} }
function answer($status, $data) { function answer($status, $data = [], $error = false) {
http_response_code($status); if ($error) {
Header ("Content-Type: application/json; charset=UTF-8"); $return = [];
$data = json_encode($data); $return["status"] = $status;
$json = '{"status":' . $status . ',"data":' . $data . '}'; foreach ($data as $key => $value) {
die($json); $return[$key] = $value;
}
}else{
$return = [
"status" => $status,
"data" => $data
];
}
// Check if request is internal or external
if ($this->internal) {
$this->response = $return;
return;
}else{
http_response_code($status);
header("Content-Type: application/json; charset=UTF-8");
$return = json_encode($return);
die($return);
}
}
function returnLastError() {
if (isset($this->lastError)) {
return $this->lastError;
}else{
return NULL;
}
}
function getAnswer() {
if (!isset($this->response)) {
$error = [
"status" => 500,
"error" => "empty_return",
"error_text" => "API Returned nothing."
];
$this->lastError = $error;
return $error;
}
return $this->response;
}
function getResponse() {
return $this->response;
} }
function dbCommand($command, $expectResult, $stmtArgsRequired = false, $stmtArgs = []) { function dbCommand($command, $expectResult, $stmtArgsRequired = false, $stmtArgs = []) {
$db_path = APP_PATH . "database.db"; if (isset($this->db)) {
$db = $this->db;
}else{
$config = $this->getConfig();
$db_name = $config["DB_FILE"];
$db_path = APP_PATH . $db_name;
$db = new PDO("sqlite:" . $db_path); if (!is_file($db_path)) {
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $error_payload = [
"error" => "database_not_found",
"error_text" => "The Database was not found."
];
$this->lastError = $error_payload;
$api->answer(500, $error_payload, true);
}
if (!is_writeable($db_path)) {
$error_payload = [
"error" => "database_not_writeable",
"error_text" => "The Database is not writeable."
];
$this->lastError = $error_payload;
$this->answer(500, $error_payload, true);
return;
}
$db = new PDO("sqlite:" . $db_path);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
$stmt = $db->prepare($command); $stmt = $db->prepare($command);
$stmt->execute($stmtArgs); $stmt->execute($stmtArgs);
if ($expectResult) { if ($expectResult) {
$result = $stmt->fetch(PDO::FETCH_ASSOC); $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $result; return $result;
} }
} }
}
function getSetting($key) {
$command = "SELECT name, value FROM settings WHERE name = :name";
$value = $this->dbCommand($command, true, true, [":name" => $key]);
if (isset($value[0]["value"])) {
return $value[0]["value"];
}else{
$this->answer(500, [
"error" => "query_returned_nothing",
"error_text" => "The SQL Query Returned nothing usable. Check Database Permissions."
]);
}
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(true, $internal);
$auth = $api->checkAuth();
$lastError = $api->returnLastError();
if ($lastError == NULL) {
if ($auth) {
$api->answer(200, [
"login" => true,
"username" => $api->username
]);
}else{
$api->answer(401, [
"login" => false,
"username" => ""
]);
}
}
+24 -24
View File
@@ -1,50 +1,50 @@
<?php <?php
// Load API Library // Set internal to false so that the answer goes to the client
require "../api.php"; if (!isset($internal)) {
$internal = false;
$api = new gbAPI(false); define("API_DIR", "../");
// Check Request Body
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
$api->answer(400, [
"error" => "false_request_method",
"error_text" => "Only POST allowed"
]);
} }
if (!isset($_POST["username"]) || !isset($_POST["password"])) { // Load API Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Check Request Body
$input = $api->getInput();
if (!isset($input["username"]) || !isset($input["password"])) {
$api->answer(400, [ $api->answer(400, [
"error" => "missing_fields", "error" => "missing_fields",
"error_text" => "Some fields are missing" "error_text" => "Some fields are missing"
]); ], true);
return;
} }
$username = $_POST["username"]; $username = $input["username"];
$password = $_POST["password"]; $password = $input["password"];
// Prepare SQL Command // Prepare SQL Command
$hash = hash("sha256", $password); $command = "SELECT username, password FROM users WHERE username = :username";
$command = "SELECT username, password FROM users WHERE username = :username AND password = :password";
$args = [ $args = [
":username" => $username, ":username" => $username
":password" => $hash
]; ];
// Execute SQL Command // Execute SQL Command
$result = $api->dbCommand($command, true, true, $args); $result = $api->dbCommand($command, true, true, $args);
// Check Result // Check Result
if ($result && password_verify($password, $result["password"])) { if ($result && password_verify($password, $result[0]["password"])) {
// Login successful. Generate Auth Token // Login successful. Generate Auth Token
$token = $api->generateToken($username); $token = $api->generateCookieToken($username);
// Set cookies // Set cookies
setcookie("auth_token", $token, time() + 86400, "/"); setcookie("auth_token", $token, time() + 86400, "/");
setcookie("username", $username, time() + 86400, "/");
$api->answer(200);
}else{ }else{
$api->answer(401, [ $api->answer(401, [
"error" => "unauthorized", "error" => "unauthorized",
"error_text" => "A user with this password does not exists." "error_text" => "A user with this password does not exists."
]); ], true);
} }
+52
View File
@@ -0,0 +1,52 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
$input = $api->getInput();
if (!isset($input["name"]) || !isset($input["text"])) {
$api->answer(400, [
"error" => "missing_fields",
"error_text" => "Some fields are missing"
], true);
return;
}
$html_allowed = $api->getSetting("allow_html");
if ($html_allowed == "true") {
$name = $input["name"];
$text = $input["text"];
}else{
$name = htmlspecialchars($input["name"]);
$text = htmlspecialchars($input["text"]);
}
$date = date("Y-m-d H:i:s");
$approve = $api->getSetting("approve_entrys") == "true" ? true : false;
if ($approve) {
$status = 0;
}else{
$status = 1;
}
// Insert Into Database
$command = "INSERT INTO entrys (name, text, date, status) VALUES (:name, :text, :date, :status)";
$api->dbCommand($command, false, true, [
":name" => $name,
":text" => $text,
":date" => $date,
":status" => $status
]);
$api->answer(200);
+47
View File
@@ -0,0 +1,47 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Get Entrys
$command = "SELECT * FROM entrys WHERE status = :status ORDER BY date DESC";
$result = $api->dbCommand($command, true, true, [":status" => 1]);
$entrys = [];
if (!$result) {
$api->answer(200, [
"entrys" => [],
"empty" => true
]);
return;
}
foreach($result as $row) {
$entry_name = $row["name"];
$entry_text = $row["text"];
$date = new DateTime($row["date"]);
$entry_date = $date->format("d.m.Y H:i:s");
$entry = [
"name" => $entry_name,
"text" => $entry_text,
"date" => $entry_date
];
$entrys[] = $entry;
}
$json = json_encode($entrys);
$api->answer(200, [
"entrys" => $json,
"empty" => false
]);
+37
View File
@@ -0,0 +1,37 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(true, $internal);
$input = $api->getInput();
// Check given Status
if (!isset($input["status"])) {
$api->answer(400, [
"error" => "missing_fields",
"error_text" => "Some fields are missing"
], true);
return;
}
if ($status !== 0 && $status !== 1 && $status !== 2) {
$api->answer(400, [
"error" => "status_invalid",
"error_text" => "The given status is invalid."
], true);
}
$command = "SELECT * FROM entrys WHERE status = :status";
$response = $api->dbCommand($command, true, true, [
":status" => $status
]);
+24
View File
@@ -0,0 +1,24 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Get App Name
$result = $api->getSetting("application_name");
if ($result !== NULL) {
$api->answer(200, [
"application_name" => $result
]);
}else{
$api->answer(500, [
"error" => "empty_query",
"error_text" => "Database query returned nothing."
]);
}
+88 -17
View File
@@ -1,27 +1,47 @@
<?php <?php
// Load Main Libary // Set internal to false so that the answer goes to the client
require "../api.php"; if (!isset($internal)) {
$internal = false;
$api = new gbAPI(false); define("API_DIR", "../");
// Check Request body
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
$api->answer(400, [
"error" => "false_request_method",
"error_text" => "Only POST allowed"
]);
} }
if (!isset($_POST["username"]) || !isset($_POST["password"]) || !isset($_POST["password_repeat"])) { // Load Main Libary
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Check Request body
$input = $api->getInput();
if (!isset($input["username"]) || !isset($input["password"]) || !isset($input["password_repeat"])) {
$api->answer(400, [ $api->answer(400, [
"error" => "missing_fields", "error" => "missing_fields",
"error_text" => "Some fields are missing" "error_text" => "Some fields are missing"
]); ]);
return;
} }
$username = $_POST["username"]; // Check if application already configurated
$password = $_POST["password"]; if (is_file(APP_PATH . "database.db")) {
$password_repeat = $_POST["password_repeat"]; $api->answer(503, [
"error" => "already_configurated",
"error_text" => "The application is already configurated."
]);
return;
}
// Check PDO drivers
if (!extension_loaded("pdo_sqlite") || !extension_loaded("sqlite3")) {
$api->answer(500, [
"error" => "missing_drives",
"error_text" => "Please install the extensions pdo_sqlite and sqlite3."
]);
return;
}
$username = $input["username"];
$password = $input["password"];
$password_repeat = $input["password_repeat"];
// Check Passwords // Check Passwords
if ($password !== $password_repeat) { if ($password !== $password_repeat) {
@@ -29,12 +49,63 @@ if ($password !== $password_repeat) {
"error" => "passwords_dont_match", "error" => "passwords_dont_match",
"error_text" => "The passwords does not match." "error_text" => "The passwords does not match."
]); ]);
return;
} }
// Hash Password // Hash Password
$hash = password_hash($password); $hash = password_hash($password, PASSWORD_DEFAULT);
// CREATE DATABASE STRUCTURE // CREATE DATABASE STRUCTURE
file_get_contents(APP_PATH . "db_structure.sql"); $sql_file = file_get_contents(APP_PATH . "db_structure.sql");
$db_path = APP_PATH . "database.db";
$db = new PDO("sqlite:" . $db_path);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec($sql_file);
// INSERT USER
$command = "INSERT INTO users (username, password, owner) VALUES (:username, :password, :owner)";
$stmt = $db->prepare($command);
$stmt->execute([":username" => $username, ":password" => $hash, ":owner" => 1]);
// INSERT SETTINGS
$command = "INSERT INTO settings (name, value) VALUES (:name, :value)";
$settings = [
"application_name" => "Guestbook",
"allow_html" => "false",
"approve_entrys" => "true",
"allow_new_entrys" => "true",
"template" => "standard"
];
foreach ($settings as $key => $value) {
$stmt = $db->prepare($command);
$stmt->execute([":name" => $key, ":value" => $value]);
}
// WRITE .env AND GENERATE SECRETS
$env = "";
$cookie_token = bin2hex(random_bytes(32));
$env .= "COOKIE_TOKEN_SIGNATURE=" . $cookie_token . "\n";
$env .= "COOKIE_TOKEN_LIFETIME=86400\n";
$api_token = bin2hex(random_bytes(32));
$env .= "API_TOKEN_SIGNATURE=" . $api_token . "\n";
$env .= "DB_FILE=database.db";
file_put_contents(APP_PATH . ".env", $env);
// RETURN SUCCESS MESSAGE
$api->answer(200);
+17
View File
@@ -0,0 +1,17 @@
<?php
// Set internal to false so that the answer goes to the client
if (!isset($internal)) {
$internal = false;
define("API_DIR", "../");
}
// Load Main Library
require_once API_DIR . "api.php";
$api = new gbAPI(false, $internal);
// Get Template
$template = $api->getSetting("template");
if ($template !== NULL) {
$api->answer(200, [
"template_name" => $template
]);
}
Executable → Regular
BIN
View File
Binary file not shown.
Regular → Executable
+15 -10
View File
@@ -1,15 +1,20 @@
CREATE TABLE IF NOT EXISTS "users" ( CREATE TABLE IF NOT EXISTS "users" (
"id" INTEGER NOT NULL, "id" INTEGER PRIMARY KEY AUTOINCREMENT,
"username" VARCHAR NOT NULL, "username" TEXT NOT NULL UNIQUE,
"password" VARCHAR NOT NULL, "password" TEXT NOT NULL,
"owner" INTEGER NOT NULL, "owner" INTEGER NOT NULL DEFAULT 0
PRIMARY KEY("id", "username", "owner")
); );
CREATE TABLE IF NOT EXISTS "entrys" ( CREATE TABLE IF NOT EXISTS "entrys" (
"id" INTEGER NOT NULL, "id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" VARCHAR NOT NULL, "name" TEXT NOT NULL,
"text" VARCHAR NOT NULL, "text" TEXT NOT NULL,
"status" INTEGER, "date" TEXT NOT NULL,
PRIMARY KEY("id") "status" INTEGER NOT NULL DEFAULT 0
); );
CREATE TABLE IF NOT EXISTS "settings" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL
);
+25
View File
@@ -0,0 +1,25 @@
<?php
// Set debug to true
$debug = true;
// Load Main Library
require "../main.php";
// Init Main Class
$gb = new gb($debug);
// Load and init HTML Loader Library
require FRONTEND_PATH . "html_loader.php";
$htmlloader = new HTMLLoader();
// Check Login
$call = new APICall("auth/isauth.php");
$response = $call->response;
if (!$response["data"]["login"]) {
$htmlloader->setPage("login");
$htmlloader->show([], true);
}else{
$htmlloader->setPage("admin_home");
$htmlloader->show([], true);
}
+79 -23
View File
@@ -1,46 +1,77 @@
<?php <?php
class HTMLLoader { class HTMLLoader {
private $template; private $template;
private $page;
private $templates;
private $template_conf;
function __construct() { function __construct() {
// Check if Paths variables exists // Check if Paths variables exists
if (!defined("TEMPLATE_PATH") || !defined("TEMPLATE_CONF") || !defined("APP_DOMAIN")) { if (!defined("TEMPLATE_PATH") || !defined("TEMPLATE_CONF") || !defined("APP_DOMAIN")) {
die("TEMPLATE_PATH or TEMPLATE_CONF are not defined at " . __FILE__); die("TEMPLATE_PATH or TEMPLATE_CONF are not defined at " . __FILE__);
} }
$this->templates = json_decode(file_get_contents(TEMPLATE_CONF), true);
$this->getTemplate();
} }
function setTemplate($template) { function setTemplate($template) {
// Check if template exists // Check if template exists
$file = file_get_contents(TEMPLATE_CONF); if (!array_key_exists($template, $this->templates)) {
$json = json_decode($file, true); die("Template " . $template . " not found.");
if (!array_key_exists($template, $json) && array_key_exists("error", $json)) {
$this->showError('Template "' . $template . '" was not found.');
exit;
}elseif (!array_key_exists("error", $json)){
die("Error Template was not found.");
} }
if (!is_dir(TEMPLATE_PATH . $this->templates[$template]["dir_name"])) {
die("Template " . $template . " not found.");
}
$this->template = $template;
$this->template_conf = file_get_contents(TEMPLATE_PATH . $this->templates[$template]["dir_name"] . "/template.json");
}
function getTemplate() {
// Call API
$call = new APICall("template/get.php");
$response = $call->response;
if ($response["status"] !== 200) {
$this->setTemplate("standard");
$this->showError("Template API Returned an error: " . $response["error_text"]);
}
$template = $response["data"]["template_name"];
$this->template_conf = json_decode(file_get_contents(TEMPLATE_PATH . $this->templates[$template]["dir_name"] . "/template.json"), true);
$this->template = $template; $this->template = $template;
} }
function showError($error) { function setPage($page) {
$file = file_get_contents(TEMPLATE_CONF); if (!array_key_exists($page, $this->template_conf)) {
$json = json_decode($file, true); $this->showError('Page <span class="code">' . $page . '</span> for template <span class="code">' . $this->template . '</span> not found.');
}else{
$this->page = $page;
}
}
$page = $json["error"]; function showError($error) {
$html = file_get_contents(TEMPLATE_PATH . $page["dir_name"] . "/" . $page["html_file"]); $template_dir = $this->templates[$this->template]["dir_name"];
$template_conf = json_decode(file_get_contents(TEMPLATE_PATH . $template_dir . "/template.json"), true);
$page = $template_conf["error"];
$html = file_get_contents(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $page["html_file"]);
$css = ""; $css = "";
// Get CSS // Get CSS
foreach ($page["css_files"] as $file) { foreach ($page["css_files"] as $file) {
// Check if File exists // Check if File exists
if (!is_file(TEMPLATE_PATH . $page["dir_name"] . "/" . $file)) { if (!is_file(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $file)) {
$this->showError('CSS File <span class="code">' . $file . '</span> for Template <span class="code">error</span> in <span class="code">' . __FILE__ . "</span> not found."); die("CSS File " . $file . " not found.");
} }
// Add CSS Files to HTML // Add CSS Files to HTML
$css .= '<link rel="stylesheet" type="text/css" href="http://' . APP_DOMAIN . "/templates/" . $page["dir_name"] . "/" . $file . '">'; $css .= '<link rel="stylesheet" type="text/css" href="http://' . APP_DOMAIN . "/templates/" . $template_dir . "/" . $page["dir_name"] . "/" . $file . '">';
} }
$html = str_replace("%css%", $css, $html); $html = str_replace("%css%", $css, $html);
@@ -51,30 +82,55 @@ class HTMLLoader {
die($replace); die($replace);
} }
function show() { function show($args = [], $showAppName = true) {
if (!isset($this->template)) {
$this->getTemplate();
}
$template = $this->template; $template = $this->template;
$tempalte_conf = $this->template_conf;
$template_dir = $this->templates[$template]["dir_name"];
$file = file_get_contents(TEMPLATE_CONF); $page = $tempalte_conf[$this->page];
$json = json_decode($file, true);
$page = $json[$template]; if (!is_file(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $page["html_file"])) {
$html = file_get_contents(TEMPLATE_PATH . $page["dir_name"] . "/" . $page["html_file"]); $this->showError('HTML File <span class="code">' . $page["html_file"] . '</span> for page <span class="code">' . $page["name"] . '</span> in template <span class="code">' . $template . '</span> does not exists.');
}
$html = file_get_contents(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $page["html_file"]);
if ($showAppName) {
$call = new APICall("settings/appname.php");
$response = $call->response;
if (isset($response["error"])) {
$this->showError("Failed to get Application Name: API Returned an error: " . $response["error_text"]);
}
$app_name = $response["data"]["application_name"];
$html = str_replace("%appname%", $app_name, $html);
}
// Get CSS // Get CSS
$css = ""; $css = "";
foreach ($page["css_files"] as $file) { foreach ($page["css_files"] as $file) {
// Check if File exists // Check if File exists
if (!is_file(TEMPLATE_PATH . $page["dir_name"] . "/" . $file)) { if (!is_file(TEMPLATE_PATH . $template_dir . "/" . $page["dir_name"] . "/" . $file)) {
$this->showError('CSS File <span class="code">' . $file . '</span> for Template <span class="code">' . $this->template . '</span> in <span class="code">' . __FILE__ . "</span> not found."); $this->showError('CSS File <span class="code">' . $file . '</span> for Template <span class="code">' . $this->template . '</span> in <span class="code">' . __FILE__ . "</span> not found.");
} }
// Add CSS Files to HTML // Add CSS Files to HTML
$css .= '<link rel="stylessheet" text="text/css" href="' . APP_DOMAIN . "/templates/" . $page["dir_name"] . "/" . $file . '">'; $css .= '<link rel="stylessheet" text="text/css" href="' . APP_DOMAIN . "/templates/" . $template_dir . "/" . $page["dir_name"] . "/" . $file . '">';
} }
$html = str_replace("%css%", $css, $html); $html = str_replace("%css%", $css, $html);
// Insert Args
foreach ($args as $key => $value) {
$html = str_replace($key, $value, $html);
}
die($html); die($html);
} }
} }
+37
View File
@@ -8,3 +8,40 @@ require "main.php";
// Init GB Class // Init GB Class
$gb = new gb(true); $gb = new gb(true);
// Load HTMLLoader Library
require FRONTEND_PATH . "html_loader.php";
$htmlLoader = new HTMLLoader();
// Build Entry Page
$htmlLoader->setPage("entries_frontend");
// Get entrys
$call = new APICall("entry/get.php");
$response = $call->response;
if ($call->getError() !== false) {
$htmlLoader->showError("Entry API Returned an error: " . $call->getError());
}
if ($response["data"]["empty"]) {
$html_block = "<p>There aren'n any entrys yet. Be the first one!</p>";
}else{
$entrys = json_decode($response["data"]["entrys"], true);
$html_block = "";
foreach($entrys as $entry) {
$name = $entry["name"];
$text = $entry["text"];
$date = $entry["date"];
$html_block .= '
<div class="entry">
<h3 class="md-typescale-display-small">' . $name . '</h3>
<p id="date">' . $date . '</p>
<p>' . $text . '</p>
</div>
';
}
}
$htmlLoader->show(["%entrys%" => $html_block]);
+39 -3
View File
@@ -10,8 +10,9 @@ class gb {
// Define Paths // Define Paths
define("APP_DOMAIN", $_SERVER["SERVER_NAME"]); define("APP_DOMAIN", $_SERVER["SERVER_NAME"]);
define("APP_PATH", __DIR__ . "/.."); define("APP_PATH", __DIR__ . "/../");
define("FRONTEND_PATH", __DIR__ . "/"); define("FRONTEND_PATH", __DIR__ . "/");
define("API_DIR", __DIR__ . "/../api/");
define("TEMPLATE_PATH", FRONTEND_PATH . "templates/"); define("TEMPLATE_PATH", FRONTEND_PATH . "templates/");
define("TEMPLATE_CONF", TEMPLATE_PATH . "templates.json"); define("TEMPLATE_CONF", TEMPLATE_PATH . "templates.json");
@@ -27,7 +28,42 @@ class gb {
$html = new HTMLLoader(); $html = new HTMLLoader();
// Set template to Setup an show it // Set template to Setup an show it
$html->setTemplate("setup"); $html->setTemplate("standard");
$html->show(); $html->setPage("setup");
$html->show([], false);
}
}
// Internal API Call Library
class APICall {
public $response;
private $error;
function __construct($path, $data = []) {
// Set $internal to true so the api return does not go to the browser
$internal = true;
// Check Path
if (!is_file(API_DIR . $path)) {
$this->error = "API Path invalid.";
return false;
}
// Call API
require API_DIR . $path;
// Get return from the API Library
$response = $api->getAnswer();
$this->response = $response;
}
function getError() {
if (isset($this->error)) {
return $this->error;
}else{
return false;
}
} }
} }
Whitespace-only changes.
View File
Whitespace-only changes.
@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import { styles as typescaleStyles } from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<style>
:root {
--md-sys-color-primary: rgb(58, 58, 255);
--active-menu-point-color: rgb(116, 148, 250);
--md-sys-color-surface-container: rgb(223, 228, 255);
}
body {
margin: 0;
}
.layout {
display: flex;
height: 100vh;
overflow: hidden;
}
.sidebar {
flex: 0 0 280px;
width: 280px;
overflow: hidden;
background: var(--md-sys-color-surface-container);
border-right: 1px solid var(--md-sys-color-outline-variant);
transition: flex-basis .2s ease, width .2s ease;
padding: 10px;
overflow-y: auto;
}
.sidebar-header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
white-space: nowrap;
}
.sidebar md-list {
background: transparent;
}
md-list-item.active {
background-color: var(--active-menu-point-color);
border-radius: 28px;
margin-bottom: 10px;
}
md-list-item {
border-radius: 28px;
margin-bottom: 10px;
}
.sidebar.collapsed {
flex-basis: 58px;
width: 58x;
}
.sidebar.collapsed .title,
.sidebar.collapsed .label {
display: none;
}
.content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
@media (max-width: 720px) {
body {
overflow-x: hidden;
}
}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<md-icon-button id="toggle" aria-label="Menü ein-/ausklappen">
<md-icon>menu</md-icon>
</md-icon-button>
<span class="md-typescale-body-large title">%appname%</span>
</div>
<md-list>
<md-list-item type="link" href="/admin/" class="active">
<md-icon slot="start">dashboard</md-icon>
<span class="label">Overview</span>
</md-list-item>
<md-list-item type="link" href="/admin/settings.php">
<md-icon slot="start">settings</md-icon>
<span class="label">Settings</span>
</md-list-item>
</md-list>
</aside>
<main class="content">
<h1 class="md-typescale-display-large">%appname%</h1>
<h3 class="md-typescale-display-medium">Entrys</h3>
<md-list>
<md-list-item>
<div slot="headline">Cucumber</div>
<div slot="supporting-text">Cucumbers are long green fruits that are just as long as this multi-line description</div>
</md-list-item>
</md-list>
</main>
</div>
<script type="module">
const sidebar = document.getElementById('sidebar');
const items = sidebar.querySelectorAll('md-list-item');
const mq = window.matchMedia('(max-width: 720px)');
const applyBreakpoint = () => {
sidebar.classList.toggle('collapsed', mq.matches);
};
applyBreakpoint();
mq.addEventListener('change', applyBreakpoint);
document.getElementById('toggle').addEventListener('click', () => {
sidebar.classList.toggle('collapsed');
});
items.forEach(item => {
item.addEventListener('click', () => {
items.forEach(i => i.classList.remove('active'));
item.classList.add('active');
});
});
</script>
</body>
</html>
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import {styles as typescaleStyles} from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<title>%appname% // Entries</title>
<style>
:root {
--md-sys-color-primary: rgb(58, 58, 255);
}
p {
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
hr {
border: 1px solid rgb(211, 211, 211);
}
.loader {
display: flex;
justify-content: center;
align-items: center;
}
#date {
color: gray;
}
</style>
</head>
<body>
<h1 class="md-typescale-display-large">%appname%</h1>
<md-filled-tonal-button id="entry_add">
Add Entry
</md-filled-tonal-button>
<h2 class="md-typescale-display-medium">All Entrys</h2>
%entrys%
<md-dialog id="add_entry_dialog">
<div slot="headline">
Add entry
</div>
<form slot="content" method="dialog">
<md-filled-text-field id="entry_name" label="Name"></md-filled-text-field><br><br>
<md-filled-text-field id="entry_text" label="Text" rows="5" style="resize: vertical;" type="textarea"></md-filled-text-field>
<div class="loader" id="loader" style="display: none;">
<md-circular-progress id="add_entry_load" indeterminate></md-circular-progress>
</div>
<p id="entry_return"></p>
</form>
<div slot="actions">
<md-filled-button id="entry_submit">Submit</md-filled-button>
<md-text-button id="entry_close">Close</md-text-button>
</div>
</md-dialog>
<script>
document.getElementById("entry_add").addEventListener("click", function() {
document.getElementById("add_entry_dialog").setAttribute("open", "");
});
document.getElementById("entry_close").addEventListener("click", function() {
document.getElementById("add_entry_dialog").removeAttribute("open");
});
document.getElementById("entry_submit").addEventListener("click", function() {
document.getElementById("entry_name").style.display = "none";
document.getElementById("entry_text").style.display = "none";
document.getElementById("entry_submit").style.display = "none";
document.getElementById("entry_close").style.display = "none";
document.getElementById("loader").style.display = "flex";
name = document.getElementById("entry_name").value;
text = document.getElementById("entry_text").value;
fetch("/api/entry/add.php", {
method: "POST",
body: JSON.stringify({
name: name,
text: text
})
})
.then(response => response.json())
.then(data => {
document.getElementById("loader").style.display = "none";
if (data.status !== 200) {
document.getElementById("entry_return").innerHTML = "API returned an error: " + data.error_text;
document.getElementById("entry_close").style.display = "block";
}else{
document.getElementById("entry_return").innerHTML = "Success! Maybe your entry must be aproved by the owner.";
document.getElementById("entry_close").style.display = "block";
}
})
.catch(error => {
document.getElementById("loader").style.display = "none";
document.getElementById("entry_return").innerHTML = "There was an error: " + error;
document.getElementById("entry_close").style.display = "block";
});
});
</script>
</body>
</html>
File renamed without changes.
File renamed without changes.
+76
View File
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>%appname% // Login</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script type="importmap">
{
"imports": {
"@material/web/": "https://esm.run/@material/web/"
}
}
</script>
<script type="module">
import '@material/web/all.js';
import {styles as typescaleStyles} from '@material/web/typography/md-typescale-styles.js';
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
</script>
<style>
.page-content {
display: flex;
justify-content: center;
text-align: center;
align-items: center;
}
.center {
display: block;
}
</style>
</head>
<body>
<div class="page-content">
<div class="center">
<h1 class="md-typescale-display-large">%appname%</h1>
<h3 class="md-typescale-display-medium">Admin Panel Login</h3><br><br>
<p class="md-typescale-body-medium" id="error_msg" style="display: none;"></p>
<md-filled-text-field type="text" id="login_username" placeholder="Username"></md-filled-text-field><br><br>
<md-filled-text-field type="password" id="login_password" placeholder="Password"></md-filled-text-field><br><br>
<md-filled-button id="login_button">Login</md-filled-button>
</div>
</div>
<script>
document.getElementById("login_button").addEventListener("click", function() {
var username = document.getElementById("login_username").value;
var password = document.getElementById("login_password").value;
fetch("/api/auth/login.php", {
method:"POST",
body: JSON.stringify({
username: username,
password: password
}),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
})
.then(response => response.json())
.then(data => {
if (data.status == 200) {
window.location.reload();
}else{
document.getElementById("error_msg").innerHTML = data.error_text;
document.getElementById("error_msg").style.display = "block";
}
})
.catch(error => {
document.getElementById("error_msg").innerHTML = error;
document.getElementById("error_msg").style.display = "block";
})
});
</script>
</body>
</html>
@@ -66,6 +66,18 @@
<md-filled-button id="close-button">Close</md-filled-button> <md-filled-button id="close-button">Close</md-filled-button>
</div> </div>
</md-dialog> </md-dialog>
<md-dialog id="success-dialog">
<div slot="headline">
Success!
</div>
<form slot="content" method="dialog" id="error-dialog-text">
Your Guestbook is finaly configurated! You can find the admin Panel on <span id="success-admin-url"></span>.
</form>
<div slot="actions">
<md-filled-button id="success-dialog-home">Open Guestbook</md-filled-button>
<md-filled-button id="success-dialog-admin">Open Admin Panel</md-filled-button>
</div>
</md-dialog>
<script> <script>
if (window.location.protocol == "http:") { if (window.location.protocol == "http:") {
document.getElementById("http-dialog").setAttribute("open", ""); document.getElementById("http-dialog").setAttribute("open", "");
@@ -80,7 +92,15 @@
document.getElementById("close-button").addEventListener("click", function() { document.getElementById("close-button").addEventListener("click", function() {
document.getElementById("error-dialog").removeAttribute("open"); document.getElementById("error-dialog").removeAttribute("open");
}) });
document.getElementById("success-dialog-home").addEventListener("click", function() {
window.location.reload();
});
document.getElementById("success-dialog-admin").addEventListener("click", function() {
window.location.href = "/admin";
});
function checkSetup() { function checkSetup() {
const username = document.getElementById("setup-username").value; const username = document.getElementById("setup-username").value;
@@ -108,8 +128,11 @@
} }
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => function() { .then(data => {
if (data.status !== 200) { if (data.status == 200) {
document.getElementById("success-admin-url").innerHTML = window.location.href + "admin";
document.getElementById("success-dialog").setAttribute("open", "");
}else{
console.error("Server returned following message: " + data.error); console.error("Server returned following message: " + data.error);
document.getElementById("error-dialog-text").innerHTML = data.display_error; document.getElementById("error-dialog-text").innerHTML = data.display_error;
+38
View File
@@ -0,0 +1,38 @@
{
"error":{
"name":"error",
"dir_name":"error",
"html_file":"error.html",
"css_files":[
"error.css"
]
},
"setup":{
"name":"setup",
"dir_name":"setup",
"html_file":"setup.html",
"css_files":[]
},
"entries_frontend":{
"name":"entries_frontend",
"dir_name":"entries_frontend",
"html_file":"entries_frontend.html",
"css_files":[]
},
"login":{
"name":"login",
"dir_name":"login",
"html_file":"login.html",
"css_files":[]
},
"admin_home":{
"name":"admin_home",
"dir_name":"admin_home",
"html_file":"home.html",
"css_files":[]
}
}
+3 -15
View File
@@ -1,18 +1,6 @@
{ {
"error":{ "standard":{
"name":"error", "name":"Standard Template",
"dir_name":"error", "dir_name":"standard"
"html_file":"error.html",
"css_files":[
"error.css"
]
},
"setup":{
"name":"setup",
"dir_name":"setup",
"html_file":"setup.html",
"css_files":[
"setup.css"
]
} }
} }
+23 -4
View File
@@ -6,9 +6,28 @@
//echo $_SERVER["HTTP_X_API_KEY"]; //echo $_SERVER["HTTP_X_API_KEY"];
$string = '{"user":"marc", "auth":true}'; //$string = '{"user":"marc", "auth":true}';
$secret = "281361f952279bd9530734c67ed04aed2756f0fe00cb998cd6c28324f491484c"; //$secret = "281361f952279bd9530734c67ed04aed2756f0fe00cb998cd6c28324f491484c";
echo "Hash: " . hash_hmac("sha256", $string, $secret); //echo "Hash: " . hash_hmac("sha256", $string, $secret);
echo "Decode: " . hash_equals($string, $secret); //echo "Decode: " . hash_equals($string, $secret);
//print_r(PDO::getAvailableDrivers());
//print_r(json_decode('{"entrys":[{"name":"marc"}]}', true));
//print_r(explode(".", "jakob.stinkt"));
//var_dump(extension_loaded("pdo_sqlite"));
//var_dump(extension_loaded("sqlite3"));
class test {
private $test;
function __construct() {
var_dump(isset($this->test));
}
}
new test();