diff --git a/VERSION b/VERSION index a22da3b..a5ee8d9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2024.12.06 +2024.12.23 diff --git a/_wip_todo/add_url.php b/_wip_todo/add_url.php index 34494d1..886664c 100644 --- a/_wip_todo/add_url.php +++ b/_wip_todo/add_url.php @@ -10,9 +10,9 @@ if ($config['debug_mode']) { require_once('session.php'); -if (!isset($_SESSION['valid_session'])) { +if (!isset($_SESSION['valid_session'])) { $secretKey = $config['totp_secret']; - $cookieVal = decodeCookie($secretKey); + $cookieVal = isSavedCookieValid($secretKey); if ($cookieVal === false) { # Valid cookie ? header('Location: login.php'); @@ -56,27 +56,30 @@ if (isset($_POST['submit'])) { exit; } } else { ?> - - - - - twtxt - - - - -

twtxt

-
-
- -
URL is invalid, check it!

- - -
-
- -
-
- - + + + + + + twtxt + + + + + +

twtxt

+
+
+ +
URL is invalid, check it!

+ + +
+
+ +
+
+ + + \ No newline at end of file diff --git a/_wip_todo/session.php b/_wip_todo/session.php deleted file mode 100644 index 907fb00..0000000 --- a/_wip_todo/session.php +++ /dev/null @@ -1,101 +0,0 @@ - 'twtxt_session', - 'use_strict_mode' => true, - 'cookie_httponly' => true, - 'cookie_secure' => $config['secure_cookies'], - 'sid_length' => 64, - 'sid_bits_per_character' => 6, - 'cookie_samesite' => 'Strict', // Not compatible with PHP lower than 7.3 -]); - -function has_valid_session() { - $config = parse_ini_file('private/config.ini'); - $secretKey = $config['password']; - - if (isset($_SESSION['valid_session'])) { - return true; - } - - $cookieVal = decodeCookie($secretKey); - if ($cookieVal === false) { - #echo "Invalid cookie"; - return false; - } - - return true; -} - -function encrypt(string $data, string $key, string $method): string { - $ivSize = openssl_cipher_iv_length($method); - $iv = openssl_random_pseudo_bytes($ivSize); - $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv); - # PHP 8.2 - Deprecated: implode(): - # Passing null to parameter #1 ($separator) of type array|string is deprecated - //$encrypted = strtoupper(implode(null, unpack('H*', $encrypted))); - $encrypted = strtoupper(implode(unpack('H*', $encrypted))); - - return $encrypted; -} - -function decrypt(string $data, string $key, string $method): string { - $data = pack('H*', $data); - $ivSize = openssl_cipher_iv_length($method); - $iv = openssl_random_pseudo_bytes($ivSize); - $decrypted = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv); - - return trim($decrypted); -} - -function saveLoginSuccess($secretKey) { - // Set a cookie to remember the user - $_SESSION['valid_session'] = true; - - // Set a cookie value to remember the user - $encoded_cookie_value = generateCookieValue('admin', $secretKey); - $cookie_expiry = time() + (30 * 24 * 60 * 60); // 30 days - - $config = parse_ini_file('private/config.ini'); - - setcookie(COOKIE_NAME, $encoded_cookie_value, [ - 'expires' => $cookie_expiry, - 'secure' => $config['secure_cookies'], - 'httponly' => true, - 'samesite' => 'Strict', - ]); -} - -function generateCookieValue($username, $secretKey) { - $key = bin2hex($secretKey); - - $encrypted = encrypt($username, $key, ENCRYPTION_METHOD); - return $encrypted; -} - -function decodeCookie($secretKey) { - // Retrieve the encoded cookie name - if (!isset($_COOKIE[COOKIE_NAME])) { - return false; - } - - $encoded_cookie_value = $_COOKIE[COOKIE_NAME]; - $key = bin2hex($secretKey); - - $config = parse_ini_file('private/config.ini'); - - // Extend expiry by 30 days - $cookie_expiry = time() + (30 * 24 * 60 * 60); - setcookie(COOKIE_NAME, $encoded_cookie_value, [ - 'expires' => $cookie_expiry, - 'secure' => $config['secure_cookies'], - 'httponly' => true, - 'samesite' => 'Strict', - ]); - - $decrypted = decrypt($encoded_cookie_value, $key, ENCRYPTION_METHOD); - return $decrypted; -} \ No newline at end of file diff --git a/index.php b/index.php index b4cb114..1e51db4 100644 --- a/index.php +++ b/index.php @@ -1,4 +1,4 @@ - 'new_twt.php', '/add' => 'add_feed.php', '/following' => 'following.php', - //'/refresh' => 'load_twt_files.php', '/refresh' => 'refresh.php', '/login' => 'login.php', '/logout' => 'logout.php', '/profile' => 'profile.php', '/replies' => 'replies.php', '/gallery' => 'gallery.php', - //'/profile/([a-zA-Z0-9_-]+)' => 'profile.php', - '/conv/([a-zA-Z0-9]{7})' => 'conv.php', // matches only twtHash of exactly 7 alphanumeric characters - '/post/([a-zA-Z0-9]{7})' => 'post.php', // matches only twtHash of exactly 7 alphanumeric characters - //'/thumb' => 'thumb.php', + '/conv/([a-zA-Z0-9]{7})' => 'conv.php', // matches only twtHash of exactly 7 alphanumeric characters + '/post/([a-zA-Z0-9]{7})' => 'post.php', // matches only twtHash of exactly 7 alphanumeric characters '/upload' => 'upload_img.php', '/webmention' => 'webmention_endpoint.php', + //'/thumb' => 'thumb.php', + //'/profile/([a-zA-Z0-9_-]+)' => 'profile.php', + + # Debug endpoints + '/test_login' => 'test_login.php', ]; // Loop through the defined routes and try to match the request URI foreach ($routes as $pattern => $action) { if (preg_match('#^' . $pattern . '$#', $path, $matches)) { - - // Extract any matched parameters (e.g., username) + + // Extract any matched parameters (e.g., username) if(!empty($matches[1])) { //array_shift($matches); $id = $matches[1]; diff --git a/libs/persistent_session.php b/libs/persistent_session.php new file mode 100644 index 0000000..45d45fd --- /dev/null +++ b/libs/persistent_session.php @@ -0,0 +1,115 @@ + !isset($config[$key])); + +if (!empty($missing_keys)) { + die('Missing required keys in config.ini: ' . implode(', ', $missing_keys)); +} + +# To make it more secure, something like JWT could be used instead + +const COOKIE_NAME = 'timeline_login'; +const ENCRYPTION_METHOD = 'aes-256-cbc'; +const EXPIRATION_DAYS = 30; + +session_start([ + 'name' => 'timeline_session', + 'use_strict_mode' => true, + 'cookie_httponly' => true, + 'cookie_secure' => $config['secure_cookies'], + 'sid_length' => 64, + 'sid_bits_per_character' => 6, + 'cookie_samesite' => 'Strict', # Not compatible with PHP < 7.3 +]); + +function hasValidSession(): bool|string { + # If short lived session is valid + if (isset($_SESSION['valid_session'])) { + return true; + } + + # Otherwise, check the persistent cookie + return isSavedCookieValid(); +} + +function encrypt(string $data, string $key, string $method): string { + $ivSize = openssl_cipher_iv_length($method); + $iv = openssl_random_pseudo_bytes($ivSize); + $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv); + $encrypted = strtoupper(implode(unpack('H*', $encrypted))); + + return $encrypted; +} + +function decrypt(string $data, string $key, string $method): string | bool { + $data = pack('H*', $data); + $ivSize = openssl_cipher_iv_length($method); + $iv = openssl_random_pseudo_bytes($ivSize); + $decrypted = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv); + + var_dump($decrypted); + + if ($decrypted === false) { + return false; + } + + return trim($decrypted); +} + +function saveLoginSuccess() { + $_SESSION['valid_session'] = true; + + $config = parse_ini_file('private/config.ini'); + + # Set a cookie to remember the user + $cookieExpiry = EXPIRATION_DAYS * 24 * 60 * 60 + time(); + $encodedCookieValue = generateCookieValue(strval($cookieExpiry), $config['secret_key']); + + setcookie(COOKIE_NAME, $encodedCookieValue, [ + 'expires' => $cookieExpiry, + 'secure' => $config['secure_cookies'], + 'httponly' => true, + 'samesite' => 'Strict', + ]); +} + +function generateCookieValue($value, $secretKey) { + $key = bin2hex($secretKey); + + $encrypted = encrypt($value, $key, ENCRYPTION_METHOD); + return $encrypted; +} + +function isSavedCookieValid() { + if (!isset($_COOKIE[COOKIE_NAME])) { + return false; + } + + $config = parse_ini_file('private/config.ini'); + + $encoded_cookie_value = $_COOKIE[COOKIE_NAME]; + $key = bin2hex($config['secret_key']); + + $cookieVal = decrypt($encoded_cookie_value, $key, ENCRYPTION_METHOD); + + if ($cookieVal === false) { + deletePersistentCookie(); + return false; + } + + # TODO: Check that the cookie is not expired + + saveLoginSuccess(); # Extend expiracy for previous cookie + + return true; # If it was decoded correctly, it's a valid session +} + +function deletePersistentCookie() { + if (isset($_COOKIE[COOKIE_NAME])) { + unset($_COOKIE[COOKIE_NAME]); + setcookie(COOKIE_NAME, '', time() - 3600); + } +} diff --git a/libs/session.php b/libs/session.php index bb9f9fc..10a688b 100644 --- a/libs/session.php +++ b/libs/session.php @@ -1,30 +1,33 @@
- Selected for both public and private lists - - if( isset($_SESSION['password'])) { - if($_SESSION['password']=="$password") { // Hacky login + // TODO: fix it so if List -> Selected for both public and private lists + + if (isset($_SESSION['password'])) { + if ($_SESSION['password'] == "$passwordInConfig") { // Hacky login // Private lists echo ""; foreach (glob("private/twtxt-*.txt") as $filename) { - if($filename == $_GET['lists']) $attr="selected"; - else $attr = ""; - $listName = $filename; - $listName = str_replace("private/twtxt-", "", $listName); - $listName = str_replace("_", " ", $listName); - $listName = str_replace(".txt", "", $listName); - echo ""; + if ($filename == $_GET['lists']) $attr = "selected"; + else $attr = ""; + $listName = $filename; + $listName = str_replace("private/twtxt-", "", $listName); + $listName = str_replace("_", " ", $listName); + $listName = str_replace(".txt", "", $listName); + echo ""; } // Public Lists echo ""; - } } + } - foreach (glob("twtxt-*.txt") as $filename) { - if($filename == $_GET['lists']) $attr="selected"; - else $attr = ""; - $listName = $filename; - $listName = str_replace("twtxt-", "", $listName); - $listName = str_replace("_", " ", $listName); - $listName = str_replace(".txt", "", $listName); - echo ""; - } + foreach (glob("twtxt-*.txt") as $filename) { + if ($filename == $_GET['lists']) $attr = "selected"; + else $attr = ""; + $listName = $filename; + $listName = str_replace("twtxt-", "", $listName); + $listName = str_replace("_", " ", $listName); + $listName = str_replace(".txt", "", $listName); + echo ""; + } ?> - - + +
\ No newline at end of file diff --git a/partials/lists.php b/partials/lists.php index 32dca1b..ac0d3a1 100644 --- a/partials/lists.php +++ b/partials/lists.php @@ -1,43 +1,43 @@
- Selected for both public and private lists - - if( isset($_SESSION['password'])) { - if($_SESSION['password']=="$password") { // Hacky login + // TODO: fix it so if List -> Selected for both public and private lists + + if (isset($_SESSION['password'])) { + if ($_SESSION['password'] == "$passwordInConfig") { // Hacky login // Private lists echo ""; foreach (glob("private/twtxt-*.txt") as $filename) { - if($filename == $_GET['list']) $attr="selected"; - else $attr = ""; - $listName = $filename; - $listName = str_replace("private/twtxt-", "", $listName); - $listName = str_replace("_", " ", $listName); - $listName = str_replace(".txt", "", $listName); - echo ""; + if ($filename == $_GET['list']) $attr = "selected"; + else $attr = ""; + $listName = $filename; + $listName = str_replace("private/twtxt-", "", $listName); + $listName = str_replace("_", " ", $listName); + $listName = str_replace(".txt", "", $listName); + echo ""; } // Public Lists echo ""; - } } + } - foreach (glob("twtxt-*.txt") as $filename) { - if($filename == $_GET['list']) $attr="selected"; - else $attr = ""; - $listName = $filename; - $listName = str_replace("twtxt-", "", $listName); - $listName = str_replace("_", " ", $listName); - $listName = str_replace(".txt", "", $listName); - //$filename = "TODO".$baseURL."/".$filename; - echo ""; - } + foreach (glob("twtxt-*.txt") as $filename) { + if ($filename == $_GET['list']) $attr = "selected"; + else $attr = ""; + $listName = $filename; + $listName = str_replace("twtxt-", "", $listName); + $listName = str_replace("_", " ", $listName); + $listName = str_replace(".txt", "", $listName); + //$filename = "TODO".$baseURL."/".$filename; + echo ""; + } ?> - - + +
\ No newline at end of file diff --git a/private/config_template.ini b/private/config_template.ini index a49498c..cfef942 100644 --- a/private/config_template.ini +++ b/private/config_template.ini @@ -42,13 +42,18 @@ webmentions_txt_path = "./mentions.txt" public_webmentions = "https://example.com/timeline/mentions.txt" [security] -; Generate it with the TOTP module +; Secret key to encrypt cookies +; Create a new one here: https://randomkeygen.com +secret_key = "553GkZzIYZKx5z0lftt4yKDG4aKb4sAG" + +; Simple password +password = "change_me" + +; A dynamic password (TOTP) changing every 30 seconds +; Use a TOTP client with support for 10 digits like Aegis (Android) totp_digits = 10 totp_secret = "1234567890" ; It's recommended that your site is hosted on HTTPS ; In case it's in HTTP (not secure), set this to false secure_cookies = true - -; Simple password for unnamed user -password = "" \ No newline at end of file diff --git a/views/following.php b/views/following.php index a01c2ad..714e8cd 100644 --- a/views/following.php +++ b/views/following.php @@ -1,7 +1,7 @@ --> Nick URL - + Time ago - - - - - - - - - - - - twts)) { + + + + + + + + + + + + twts)) { - // Then test if latest twt is at start or end of file: - $resetVar = reset(getTwtsFromTwtxtString($currentFollower[1])->twts); - $endVar = end(getTwtsFromTwtxtString($currentFollower[1])->twts); - if ($resetVar->timestamp < $endVar->timestamp) { // TODO: this can be swapped to get time of first twt - echo $endVar->displayDate; - } else { - echo $resetVar->displayDate; + // Then test if latest twt is at start or end of file: + $resetVar = reset(getTwtsFromTwtxtString($currentFollower[1])->twts); + $endVar = end(getTwtsFromTwtxtString($currentFollower[1])->twts); + if ($resetVar->timestamp < $endVar->timestamp) { // TODO: this can be swapped to get time of first twt + echo $endVar->displayDate; + } else { + echo $resetVar->displayDate; + } } - } - ?> + ?> - - + + - + - + \ No newline at end of file diff --git a/views/login.php b/views/login.php index dfa0d93..116824d 100644 --- a/views/login.php +++ b/views/login.php @@ -1,28 +1,30 @@ -
-

Enter password or TOTP

-
-
- -

-
-
+ +
+

Enter password or TOTP

+
+
+ +

+ +

+
+
- + \ No newline at end of file diff --git a/views/logout.php b/views/logout.php index c44d92b..3f1c868 100644 --- a/views/logout.php +++ b/views/logout.php @@ -1,6 +1,7 @@