From 485d85cb0aed17c319458738fa64661a18c45f76 Mon Sep 17 00:00:00 2001 From: Joey Kimsey Date: Sat, 7 Dec 2024 01:58:27 -0500 Subject: [PATCH 01/15] token support, api fixes and security, dark mode --- app/classes/api_controller.php | 108 +++++++++- app/classes/forms.php | 38 ++++ app/controllers/admin/tokens.php | 90 ++++++++ app/controllers/api/auth.php | 38 ++++ app/controllers/api/login.php | 51 +++++ app/controllers/home.php | 18 ++ app/css/main-dark.css | 51 +++++ app/css/main.css | 47 +++++ app/functions/common.php | 14 ++ app/js/main.js | 22 +- app/models/sessions.php | 16 -- app/models/token.php | 198 ++++++++++++++++++ app/models/user.php | 57 +++-- app/plugins/subscribe/views/footer/right.html | 5 +- app/templates/default/default.inc.php | 1 + app/views/about.html | 0 app/views/admin/tokens/create.html | 28 +++ app/views/admin/tokens/edit.html | 29 +++ app/views/admin/tokens/list.html | 30 +++ app/views/admin/tokens/view.html | 48 +++++ app/views/contact.html | 0 app/views/footer/right.html | 4 + app/views/privacy.html | 0 app/views/switches.html | 52 +++++ bin/tempus_project.php | 47 +---- docker/ttp-nginx/cors.conf | 27 +++ 26 files changed, 934 insertions(+), 85 deletions(-) create mode 100644 app/controllers/admin/tokens.php create mode 100644 app/controllers/api/auth.php create mode 100644 app/controllers/api/login.php create mode 100644 app/css/main-dark.css create mode 100644 app/models/token.php create mode 100644 app/views/about.html create mode 100644 app/views/admin/tokens/create.html create mode 100644 app/views/admin/tokens/edit.html create mode 100644 app/views/admin/tokens/list.html create mode 100644 app/views/admin/tokens/view.html create mode 100644 app/views/contact.html create mode 100644 app/views/privacy.html create mode 100644 app/views/switches.html create mode 100644 docker/ttp-nginx/cors.conf diff --git a/app/classes/api_controller.php b/app/classes/api_controller.php index 43fd7c7..99faa73 100644 --- a/app/classes/api_controller.php +++ b/app/classes/api_controller.php @@ -16,11 +16,19 @@ use TheTempusProject\Houdini\Classes\Template; use TheTempusProject\TheTempusProject as App; use TheTempusProject\Hermes\Functions\Redirect; use TheTempusProject\Bedrock\Functions\Session; +use TheTempusProject\Bedrock\Classes\Config; +use TheTempusProject\Models\Token; class ApiController extends Controller { - public function __construct() { + protected static $canAccessApplicationApi = false; + protected static $canAccessUserApi = false; + protected static $canAccessAuthenticationApi = false; + protected static $authToken; + + public function __construct( $secure = true ) { parent::__construct(); - if ( ! App::verifyApiRequest() ) { + $this->verifyApiRequest(); + if ( $secure && ! $this->canUseApi() ) { Session::flash( 'error', 'You do not have permission to view this page.' ); return Redirect::home(); } @@ -29,4 +37,100 @@ class ApiController extends Controller { Template::addHeader( 'Content-Type: application/json; charset=utf-8' ); Template::setTemplate( 'api' ); } + + protected function canUseApi() { + return ( $this->canUseUserApi() || $this->canUseAppApi() || $this->canUseAuthApi() ); + } + + protected function canUseUserApi() { + $apiEnabled = Config::getValue( 'api/apiAccessApp' ); + if ( empty( $apiEnabled ) ) { + return false; + } + return self::$canAccessUserApi; + } + + protected function canUseAppApi() { + $apiEnabled = Config::getValue( 'api/apiAccessPersonal' ); + if ( empty( $apiEnabled ) ) { + return false; + } + return self::$canAccessApplicationApi; + } + + protected function canUseAuthApi() { + return self::$canAccessAuthenticationApi; + } + + public function verifyApiRequest() { + $tokens = new Token; + $secret = null; + + $bearer_token = $this->getBearerToken(); + if ( ! empty( $bearer_token ) ) { + $token = $tokens->findByToken( $bearer_token ); + } else { + $secret = $this->getSecretToken(); + if ( empty( $secret ) ) { + return; + } + $token = $tokens->findBySecret( $secret ); + } + if ( empty( $token ) ) { + return; + } + self::$authToken = $token; + if ( $token->expiresAt <= time() && empty( $secret ) ) { + return; + } + if ( $token->expiresAt <= time() ) { + self::$canAccessAuthenticationApi = true; + return; + } + if ( $token->token_type == 'app' ) { + self::$canAccessApplicationApi = true; + return; + } + if ( $token->token_type == 'user' ) { + self::$canAccessUserApi = true; + return; + } + return $result; + } + + public function getSecretToken() { + $headers = $this->getAuthorizationHeader(); + if ( ! empty( $headers ) ) { + if ( preg_match( '/Secret\s(\S+)/', $headers, $matches ) ) { + return $matches[1]; + } + } + return null; + } + + public function getBearerToken() { + $headers = $this->getAuthorizationHeader(); + if ( ! empty( $headers ) ) { + if ( preg_match( '/Bearer\s(\S+)/', $headers, $matches ) ) { + return $matches[1]; + } + } + return null; + } + + public function getAuthorizationHeader(){ + $headers = null; + if ( isset( $_SERVER['Authorization'] ) ) { + $headers = trim( $_SERVER["Authorization"] ); + } elseif ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) { + $headers = trim( $_SERVER["HTTP_AUTHORIZATION"] ); + } elseif ( function_exists( 'apache_request_headers' ) ) { + $requestHeaders = apache_request_headers(); + $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders)); + if ( isset( $requestHeaders['Authorization'] ) ) { + $headers = trim( $requestHeaders['Authorization'] ); + } + } + return $headers; + } } diff --git a/app/classes/forms.php b/app/classes/forms.php index a60b7af..7fb11b2 100644 --- a/app/classes/forms.php +++ b/app/classes/forms.php @@ -112,6 +112,8 @@ class Forms extends Check { self::addHandler( 'newGroup', __CLASS__, 'newGroup' ); self::addHandler( 'editGroup', __CLASS__, 'editGroup' ); self::addHandler( 'install', __CLASS__, 'install' ); + self::addHandler( 'adminCreateToken', __CLASS__, 'adminCreateToken' ); + self::addHandler( 'apiLogin', __CLASS__, 'apiLogin' ); self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] ); self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] ); self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] ); @@ -608,4 +610,40 @@ class Forms extends Check { } return true; } + + public static function adminCreateToken() { + if ( !Input::exists( 'name' ) ) { + self::addUserError( 'You must specify a name' ); + return false; + } + if ( !Input::exists( 'token_type' ) ) { + self::addUserError( 'You must specify a token_type' ); + return false; + } + return true; + } + + public static function adminEditToken() { + if ( !Input::exists( 'name' ) ) { + self::addUserError( 'You must specify a name' ); + return false; + } + if ( !Input::exists( 'token_type' ) ) { + self::addUserError( 'You must specify a token_type' ); + return false; + } + return true; + } + + public static function apiLogin() { + if ( !self::checkUsername( Input::post( 'username' ) ) ) { + self::addUserError( 'Invalid username.' ); + return false; + } + if ( !self::password( Input::post( 'password' ) ) ) { + self::addUserError( 'Invalid password.' ); + return false; + } + return true; + } } diff --git a/app/controllers/admin/tokens.php b/app/controllers/admin/tokens.php new file mode 100644 index 0000000..5655b22 --- /dev/null +++ b/app/controllers/admin/tokens.php @@ -0,0 +1,90 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Admin; + +use TheTempusProject\Classes\Forms as TTPForms; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Houdini\Classes\Issues; +use TheTempusProject\Houdini\Classes\Navigation; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Houdini\Classes\Forms; +use TheTempusProject\Classes\AdminController; +use TheTempusProject\Models\Token; +use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Hermes\Functions\Redirect; +use TheTempusProject\Bedrock\Functions\Session; + +class Tokens extends AdminController { + public static $token; + + public function __construct() { + parent::__construct(); + self::$title = 'Admin - Tokens'; + self::$token = new Token; + $view = Navigation::activePageSelect( 'nav.admin', '/admin/tokens' ); + Components::set( 'ADMINNAV', $view ); + } + + public function create() { + if ( Input::exists( 'submit' ) ) { + if ( !TTPForms::check( 'adminCreateToken' ) ) { + Issues::add( 'error', [ 'There was an error with your token.' => Check::userErrors() ] ); + } + if ( self::$token->create( + Input::post( 'name' ), + Input::post( 'notes' ), + Input::post( 'token_type' ) + ) ) { + Session::flash( 'success', 'Token Created' ); + Redirect::to( 'admin/tokens' ); + } + } + Views::view( 'admin.tokens.create' ); + } + + public function delete( $id = null ) { + if ( self::$token->delete( [ $id ] ) ) { + Session::flash( 'success', 'Token deleted.' ); + } + Redirect::to( 'admin/tokens' ); + } + + public function edit( $id = null ) { + $token = self::$token->findById( $id ); + if ( Input::exists( 'submit' ) ) { + if ( !TTPForms::check( 'adminEditToken' ) ) { + Issues::add( 'error', [ 'There was an error with your token.' => Check::userErrors() ] ); + } else { + if ( self::$token->update( + $id, + Input::post( 'name' ), + Input::post( 'notes' ), + Input::post( 'token_type' ) + ) ) { + Session::flash( 'success', 'Token Updated' ); + Redirect::to( 'admin/tokens' ); + } + } + } + Forms::selectOption( $token->token_type ); + return Views::view( 'admin.tokens.edit', $token ); + } + + public function index() { + return Views::view( 'admin.tokens.list', self::$token->listPaginated() ); + } + + public function view( $id = null ) { + return Views::view( 'admin.tokens.view', self::$token->findById( $id ) ); + } +} diff --git a/app/controllers/api/auth.php b/app/controllers/api/auth.php new file mode 100644 index 0000000..a0de860 --- /dev/null +++ b/app/controllers/api/auth.php @@ -0,0 +1,38 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Api; + +use TheTempusProject\Models\User; +use TheTempusProject\Classes\ApiController; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Models\Token; + +class Auth extends ApiController { + public static $tokens; + + public function __construct() { + parent::__construct(); + self::$tokens = new Token; + } + + public function refresh() { + $token = self::$tokens->refresh( self::$authToken->ID ); + if ( empty( $token ) ) { + $responseType = 'error'; + $response = 'IRDK'; + } else { + $responseType = 'token'; + $response = $token; + } + Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); + } +} \ No newline at end of file diff --git a/app/controllers/api/login.php b/app/controllers/api/login.php new file mode 100644 index 0000000..279a1cb --- /dev/null +++ b/app/controllers/api/login.php @@ -0,0 +1,51 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Api; + +use TheTempusProject\Classes\ApiController; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Models\Token; +use TheTempusProject\Models\User; +use TheTempusProject\Houdini\Classes\Template; +use TheTempusProject\Classes\Forms; +use TheTempusProject\Bedrock\Functions\Input; + +class Login extends ApiController { + public static $tokens; + public static $user; + + public function __construct() { + parent::__construct( false ); + self::$tokens = new Token; + self::$user = new User; + // Template::addHeader( 'Access-Control-Allow-Origin: *' ); + // Template::addHeader( 'Content-Type: application/json; charset=utf-8' ); + } + + public function index() { + header('Access-Control-Allow-Origin: *'); + if ( !Forms::check( 'apiLogin' ) ) { + $responseType = 'error'; + $response = 'malformed input1'; + return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); + } + $user = self::$user->authorize( Input::post( 'username' ), Input::post( 'password' ) ); + if ( ! $user ) { + $responseType = 'error'; + $response = 'bad credentials'; + return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); + } + $responseType = 'token'; + $token = self::$tokens->findOrCreateUserToken( $user->ID ); + return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $token ], true )]); + } +} \ No newline at end of file diff --git a/app/controllers/home.php b/app/controllers/home.php index 3090638..71a7d67 100644 --- a/app/controllers/home.php +++ b/app/controllers/home.php @@ -98,4 +98,22 @@ class Home extends Controller { // this should look up comments and blog posts with the hashtag in them Views::view( 'hashtags' ); } + + public function about() { + self::$title = 'About - {SITENAME}'; + self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.'; + Views::view( 'switches' ); + } + + public function contact() { + self::$title = 'contact - {SITENAME}'; + self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.'; + Views::view( 'contact' ); + } + + public function privacy() { + self::$title = 'privacy - {SITENAME}'; + self::$pageDescription = '{SITENAME} Terms and Conditions of use. Please use {SITENAME} safely.'; + Views::view( 'privacy' ); + } } diff --git a/app/css/main-dark.css b/app/css/main-dark.css new file mode 100644 index 0000000..627223d --- /dev/null +++ b/app/css/main-dark.css @@ -0,0 +1,51 @@ +/* General body background and text color */ +body { + background-color: #121212; /* Dark background */ + color: #e0e0e0; /* Light text */ + } + + /* Navbar */ + .navbar { + background-color: #1f1f1f; + border-color: #333; + } + .navbar a { + color: #e0e0e0; + } + .navbar a:hover { + color: #ffffff; + } + + /* Panels */ + .panel { + background-color: #1f1f1f; + border-color: #333; + color: #e0e0e0; + } + .panel-heading { + background-color: #333; + color: #ffffff; + } + + /* Buttons */ + .btn { + background-color: #333; + color: #ffffff; + border-color: #444; + } + .btn:hover { + background-color: #444; + border-color: #555; + } + + /* Forms */ + .form-control { + background-color: #1f1f1f; + color: #e0e0e0; + border: 1px solid #333; + } + .form-control:focus { + border-color: #555; + box-shadow: none; + } + \ No newline at end of file diff --git a/app/css/main.css b/app/css/main.css index 495c7d6..d1b1bd6 100644 --- a/app/css/main.css +++ b/app/css/main.css @@ -666,4 +666,51 @@ ul.alert-dropdown { .pagination { padding-left: 75px; +} + + + +.material-switch > input[type="checkbox"] { + display: none; +} + +.material-switch > label { + cursor: pointer; + height: 0px; + position: relative; + width: 40px; +} + +.material-switch > label::before { + background: rgb(0, 0, 0); + box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.5); + border-radius: 8px; + content: ''; + height: 16px; + margin-top: -8px; + position:absolute; + opacity: 0.3; + transition: all 0.4s ease-in-out; + width: 40px; +} +.material-switch > label::after { + background: rgb(255, 255, 255); + border-radius: 16px; + box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); + content: ''; + height: 24px; + left: -4px; + margin-top: -8px; + position: absolute; + top: -4px; + transition: all 0.3s ease-in-out; + width: 24px; +} +.material-switch > input[type="checkbox"]:checked + label::before { + background: inherit; + opacity: 0.5; +} +.material-switch > input[type="checkbox"]:checked + label::after { + background: inherit; + left: 20px; } \ No newline at end of file diff --git a/app/functions/common.php b/app/functions/common.php index 2626c38..28b2ff7 100644 --- a/app/functions/common.php +++ b/app/functions/common.php @@ -88,4 +88,18 @@ function iv( $variable ) { echo '
';
     echo var_export( $variable, true );
     echo '
'; +} + +function generateToken(): string { + return bin2hex(random_bytes(32)); // Generates a 64-character hexadecimal token +} + +function generateRandomString( $length = 10 ) { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charactersLength = strlen( $characters ); + $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $randomString .= $characters[random_int(0, $charactersLength - 1)]; + } + return $randomString; } \ No newline at end of file diff --git a/app/js/main.js b/app/js/main.js index 86fe116..d1fcd2e 100644 --- a/app/js/main.js +++ b/app/js/main.js @@ -108,4 +108,24 @@ window.onload = function () { // Update padding on window resize window.addEventListener('resize', updateFooterPadding); -}; \ No newline at end of file +}; + + document.addEventListener('DOMContentLoaded', function () { + const toggleButton = document.getElementById('dark-mode-toggle'); + const darkModeStylesheet = document.getElementById('dark-mode-stylesheet'); + + // Check if dark mode is saved in localStorage + if (localStorage.getItem('darkMode') === 'enabled') { + darkModeStylesheet.disabled = false; + } + + toggleButton.addEventListener('click', function () { + if (darkModeStylesheet.disabled) { + darkModeStylesheet.disabled = false; + localStorage.setItem('darkMode', 'enabled'); + } else { + darkModeStylesheet.disabled = true; + localStorage.setItem('darkMode', 'disabled'); + } + }); + }); \ No newline at end of file diff --git a/app/models/sessions.php b/app/models/sessions.php index 6a99070..a25ab8f 100644 --- a/app/models/sessions.php +++ b/app/models/sessions.php @@ -145,22 +145,6 @@ class Sessions extends DatabaseModel { return true; } - public function checkToken( $apiToken, $create = false ) { - $user = new User; - if ( $apiToken === false ) { - return false; - } - $result = $user->findByToken( $apiToken ); - if ( $result === false ) { - Debug::info( 'sessions->checkToken - could not find user by token.' ); - return false; - } - if ( $create ) { - return $this->newSession( null, false, false, $result->ID ); - } - return true; - } - /** * Creates a new session from the data provided. The * expiration time is optional and will be set to the diff --git a/app/models/token.php b/app/models/token.php new file mode 100644 index 0000000..167e961 --- /dev/null +++ b/app/models/token.php @@ -0,0 +1,198 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Models; + +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\Bedrock\Classes\Config; +use TheTempusProject\TheTempusProject as App; + +class Token extends DatabaseModel { + public $tableName = 'tokens'; + public $modelVersion = '1.0'; + public $configName = 'api'; + public $databaseMatrix = [ + [ 'name', 'varchar', '128' ], + [ 'token_type', 'varchar', '8' ], + [ 'notes', 'text', '' ], + [ 'token', 'varchar', '64' ], + [ 'secret', 'varchar', '256' ], + [ 'createdAt', 'int', '10' ], + [ 'createdBy', 'int', '10' ], + [ 'expiresAt', 'int', '10' ], + ]; + public $permissionMatrix = [ + 'addAppToken' => [ + 'pretty' => 'Add Application Tokens', + 'default' => false, + ], + 'addAppToken' => [ + 'pretty' => 'Add Personal Tokens', + 'default' => false, + ], + ]; + public $configMatrix = [ + 'apiAccessApp' => [ + 'type' => 'radio', + 'pretty' => 'Enable Api Access for Personal Tokens.', + 'default' => true, + ], + 'apiAccessPersonal' => [ + 'type' => 'radio', + 'pretty' => 'Enable Api Access for Personal Tokens.', + 'default' => true, + ], + 'AppAccessTokenExpiration' => [ + 'type' => 'text', + 'pretty' => 'How long before app tokens expire (in seconds)', + 'default' => 2592000, + ], + 'UserAccessTokenExpiration' => [ + 'type' => 'text', + 'pretty' => 'How long before user tokens expire (in seconds)', + 'default' => 604800, + ], + ]; + + public function create( $name, $note, $token_type = 'app' ) { + if ( 'app' == $token_type ) { + $expiration = Config::getValue( 'api/AppAccessTokenExpiration' ); + if ( empty( $expiration ) ) { + $expiration = $this->configMatrix['AppAccessTokenExpiration']['default']; + } + } else { + $expiration = Config::getValue( 'api/UserAccessTokenExpiration' ); + if ( empty( $expiration ) ) { + $expiration = $this->configMatrix['UserAccessTokenExpiration']['default']; + } + } + $expireTime = time() + $expiration; + + $fields = [ + 'name' => $name, + 'notes' => $note, + 'token_type' => $token_type, + 'createdBy' => App::$activeUser->ID, + 'createdAt' => time(), + 'expiresAt' => $expireTime, + 'token' => generateToken(), + 'secret' => generateRandomString(256), + ]; + if ( self::$db->insert( $this->tableName, $fields ) ) { + return true; + } + return false; + } + + public function findOrCreateUserToken( $user_id ) { + $test = $this->findUserToken( $user_id ); + if ( ! empty( $test ) ) { + return $test->token; + } + + $expiration = Config::getValue( 'api/UserAccessTokenExpiration' ); + if ( empty( $expiration ) ) { + $expiration = $this->configMatrix['UserAccessTokenExpiration']['default']; + } + $expireTime = time() + $expiration; + $token = generateToken(); + $fields = [ + 'name' => 'Browser Token', + 'notes' => 'findOrCreateUserToken', + 'token_type' => 'user', + 'createdBy' => $user_id, + 'createdAt' => time(), + 'expiresAt' => $expireTime, + 'token' => $token, + 'secret' => generateRandomString(256), + ]; + if ( self::$db->insert( $this->tableName, $fields ) ) { + return $token; + } + return false; + } + + public function update( $id, $name, $note, $token_type = 'app' ) { + $fields = [ + 'name' => $name, + 'notes' => $note, + 'token_type' => $token_type, + ]; + if ( self::$db->update( $this->tableName, $id, $fields ) ) { + return true; + } + return false; + } + + public function refresh( $id, $token_type = 'app' ) { + if ( 'app' == $token_type ) { + $expiration = Config::getValue( 'api/AppAccessTokenExpiration' ); + if ( empty( $expiration ) ) { + $expiration = $this->configMatrix['AppAccessTokenExpiration']['default']; + } + } else { + $expiration = Config::getValue( 'api/UserAccessTokenExpiration' ); + if ( empty( $expiration ) ) { + $expiration = $this->configMatrix['UserAccessTokenExpiration']['default']; + } + } + $expireTime = time() + $expiration; + $token = generateToken(); + + $fields = [ + 'expiresAt' => $expireTime, + 'token' => $token, + ]; + if ( self::$db->update( $this->tableName, $id, $fields ) ) { + return $token; + } + return false; + } + + public function findByforwardedUrl( $url ) { + if ( !Check::url( $url ) ) { + Debug::warn( "Invalid forwarded_url: $url" ); + return false; + } + $routeData = self::$db->get( $this->tableName, [ 'forwarded_url', '=', $url ] ); + if ( !$routeData->count() ) { + Debug::warn( "Could not find route by forwarded url: $url" ); + return false; + } + return $this->filter( $routeData->first() ); + } + + public function findByToken( $token ) { + $data = self::$db->get( $this->tableName, [ 'token', '=', $token ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } + + public function findBySecret( $secret ) { + $data = self::$db->get( $this->tableName, [ 'secret', '=', $secret ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } + + public function findUserToken( $user_id ) { + $data = self::$db->get( $this->tableName, [ 'createdBy', '=', $user_id, 'AND', 'token_type', '=', 'user' ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } +} diff --git a/app/models/user.php b/app/models/user.php index d31fb99..1ab7566 100644 --- a/app/models/user.php +++ b/app/models/user.php @@ -43,7 +43,6 @@ class User extends DatabaseModel { [ 'name', 'varchar', '20' ], [ 'confirmationCode', 'varchar', '80' ], [ 'prefs', 'text', '' ], - [ 'auth_token', 'text', '' ], ]; public $permissionMatrix = [ 'uploadImages' => [ @@ -694,33 +693,45 @@ class User extends DatabaseModel { return $this->data; } - public function findByToken( $token ) { - $data = self::$db->get( $this->tableName, [ 'auth_token', '=', $token ] ); - if ( ! $data->count() ) { + public function authorize( $username, $password ) { + if ( !isset( self::$log ) ) { + self::$log = new Log; + } + if ( !$this->get( $username ) ) { + self::$log->login( 0, "API: User not found: $username" ); return false; } - return $data->first(); - } - - public function addAccessToken( $id, $length = 64 ) { - if ( ! Check::id( $id ) ) { + // login attempts protection. + $timeLimit = ( time() - 3600 ); + $limit = Config::getValue( 'main/loginLimit' ); + $user = $this->data(); + if ( $limit > 0 ) { + $limitCheck = self::$db->get( + 'logs', + [ + 'source', '=', 'login', + 'AND', + 'userID', '=', $user->ID, + 'AND', + 'time', '>=', $timeLimit, + 'AND', + 'action', '!=', 'pass', + ] + ); + if ( $limitCheck->count() >= $limit ) { + self::$log->login( $user->ID, 'API: Too many failed attempts.' ); + return false; + } + } + if ( !Check::password( $password ) ) { + self::$log->login( $user->ID, 'API: Invalid Password.' ); return false; } - $fields = [ 'auth_token' => $this->generateRandomString( $length ) ]; - if ( !self::$db->update( $this->tableName, $id, $fields ) ) { - Debug::error( "User: $id not updated." ); + if ( !Hash::check( $password, $user->password ) ) { + self::$log->login( $user->ID, 'API: Wrong Password.' ); return false; } - return true; - } - - private function generateRandomString( $length = 10 ) { - $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $charactersLength = strlen( $characters ); - $randomString = ''; - for ($i = 0; $i < $length; $i++) { - $randomString .= $characters[random_int(0, $charactersLength - 1)]; - } - return $randomString; + self::$log->login( $this->data()->ID, 'API: pass' ); + return $user; } } diff --git a/app/plugins/subscribe/views/footer/right.html b/app/plugins/subscribe/views/footer/right.html index de5c05b..70302b7 100644 --- a/app/plugins/subscribe/views/footer/right.html +++ b/app/plugins/subscribe/views/footer/right.html @@ -1,4 +1,3 @@ -

Subscribe

+
+ + +
\ No newline at end of file diff --git a/app/templates/default/default.inc.php b/app/templates/default/default.inc.php index 0d3519f..b63ee2d 100644 --- a/app/templates/default/default.inc.php +++ b/app/templates/default/default.inc.php @@ -38,6 +38,7 @@ class DefaultLoader extends Loader { Components::set( 'FONT_AWESOME_URL', self::FONT_AWESOME_URL ); } $this->addCss( '' ); + $this->addCss( '' ); $this->addJs( '' ); Components::setIfNull( 'LOGO', Config::getValue( 'main/logo' ) ); Components::setIfNull( 'FOOTER_LEFT', Navigation::getMenuView( 'footer.left', 'FOOTER_LINKS', App::FOOTER_MENU_NAME, false ) ); diff --git a/app/views/about.html b/app/views/about.html new file mode 100644 index 0000000..e69de29 diff --git a/app/views/admin/tokens/create.html b/app/views/admin/tokens/create.html new file mode 100644 index 0000000..08f6add --- /dev/null +++ b/app/views/admin/tokens/create.html @@ -0,0 +1,28 @@ +
+ Create Token +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
\ No newline at end of file diff --git a/app/views/admin/tokens/edit.html b/app/views/admin/tokens/edit.html new file mode 100644 index 0000000..cbc15e4 --- /dev/null +++ b/app/views/admin/tokens/edit.html @@ -0,0 +1,29 @@ +
+ Edit Token +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
\ No newline at end of file diff --git a/app/views/admin/tokens/list.html b/app/views/admin/tokens/list.html new file mode 100644 index 0000000..644903b --- /dev/null +++ b/app/views/admin/tokens/list.html @@ -0,0 +1,30 @@ +Tokens +{PAGINATION} + + + + + + + + + + + {LOOP} + + + + + + + {/LOOP} + {ALT} + + + + {/ALT} + +
IDNameTypeDelete
{ID}{name}{token_type}
+ No results to show. +
+ Create \ No newline at end of file diff --git a/app/views/admin/tokens/view.html b/app/views/admin/tokens/view.html new file mode 100644 index 0000000..760a4be --- /dev/null +++ b/app/views/admin/tokens/view.html @@ -0,0 +1,48 @@ +
+
+
+
+
+

{name}

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type{token_type}
Created At{DTC}{createdAt}{/DTC}
Expires At{DTC}{expiresAt}{/DTC}
Token{token}
Secret{secret}
Notes{notes}
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/app/views/contact.html b/app/views/contact.html new file mode 100644 index 0000000..e69de29 diff --git a/app/views/footer/right.html b/app/views/footer/right.html index 86a0cac..18e85b8 100644 --- a/app/views/footer/right.html +++ b/app/views/footer/right.html @@ -12,4 +12,8 @@ Privacy Policy +
+ + +
\ No newline at end of file diff --git a/app/views/privacy.html b/app/views/privacy.html new file mode 100644 index 0000000..e69de29 diff --git a/app/views/switches.html b/app/views/switches.html new file mode 100644 index 0000000..1c13bef --- /dev/null +++ b/app/views/switches.html @@ -0,0 +1,52 @@ +
+
+ +
Material Design Switch Demos
+ + +
    +
  • + Bootstrap Switch Default +
    + + +
    +
  • +
  • + Bootstrap Switch Primary +
    + + +
    +
  • +
  • + Bootstrap Switch Success +
    + + +
    +
  • +
  • + Bootstrap Switch Info +
    + + +
    +
  • +
  • + Bootstrap Switch Warning +
    + + +
    +
  • +
  • + Bootstrap Switch Danger +
    + + +
    +
  • +
+
+
\ No newline at end of file diff --git a/bin/tempus_project.php b/bin/tempus_project.php index a713ef7..4211427 100644 --- a/bin/tempus_project.php +++ b/bin/tempus_project.php @@ -82,6 +82,10 @@ class TheTempusProject extends Bedrock { 'text' => ' Contact', 'url' => '{ROOT_URL}admin/contact', ], + [ + 'text' => ' Tokens', + 'url' => '{ROOT_URL}admin/tokens', + ], [ 'text' => ' Modules', 'url' => [ @@ -399,8 +403,7 @@ class TheTempusProject extends Bedrock { self::$activePrefs = $user->getDefaultPreferences(); // PREFERENCES_JSON if ( !$sessions->checkSession( Session::get( 'SessionID' ) ) && - !$sessions->checkCookie( Cookie::get( 'RememberToken' ), true ) && - !$sessions->checkToken( self::getBearerToken(), true ) + !$sessions->checkCookie( Cookie::get( 'RememberToken' ), true ) ) { Debug::info( 'Sessions->authenticate - Could not authenticate cookie or session' ); return false; @@ -527,46 +530,6 @@ class TheTempusProject extends Bedrock { echo ''; } - public static function verifyApiRequest() { - $token = self::getBearerToken(); - if ( empty( $token ) ) { - return false; - } - $user = new User; - $result = $user->findByToken( $token ); - return $result; - } - - private static function getAuthorizationHeader(){ - $headers = null; - if ( isset( $_SERVER['Authorization'] ) ) { - $headers = trim( $_SERVER["Authorization"] ); - } elseif ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) { - $headers = trim( $_SERVER["HTTP_AUTHORIZATION"] ); - } elseif ( function_exists( 'apache_request_headers' ) ) { - $requestHeaders = apache_request_headers(); - $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders)); - if ( isset( $requestHeaders['Authorization'] ) ) { - $headers = trim( $requestHeaders['Authorization'] ); - } - } - return $headers; - } - - /** - * get access token from header - * */ - private static function getBearerToken() { - $headers = self::getAuthorizationHeader(); - // HEADER: Get the access token from the header - if ( ! empty( $headers ) ) { - if ( preg_match( '/Bearer\s(\S+)/', $headers, $matches ) ) { - return $matches[1]; - } - } - return null; - } - /** * Echos useful information about the installation. * diff --git a/docker/ttp-nginx/cors.conf b/docker/ttp-nginx/cors.conf new file mode 100644 index 0000000..5bb8590 --- /dev/null +++ b/docker/ttp-nginx/cors.conf @@ -0,0 +1,27 @@ + +location /api/ { + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Request-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + if ($request_method = 'POST') { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Request-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; + } + if ($request_method = 'GET') { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Request-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; + } +} \ No newline at end of file From 4ab9d33b01063e501c6ee2cc029c2f47c92b3a00 Mon Sep 17 00:00:00 2001 From: Joey Kimsey Date: Sun, 8 Dec 2024 04:43:06 -0500 Subject: [PATCH 02/15] bootstrap 4 update and bugfixes --- app/css/main-dark.css | 374 +++++++++++++++++- app/css/main.css | 137 +------ app/js/main.js | 1 + app/models/group.php | 2 +- app/models/log.php | 2 +- app/models/routes.php | 8 +- app/models/user.php | 2 +- app/plugins/blog/models/posts.php | 12 +- app/plugins/blog/plugin.php | 2 +- app/plugins/blog/templates/blog.tpl | 9 +- app/plugins/blog/views/admin/dashboard.html | 6 +- app/plugins/blog/views/admin/list.html | 4 +- app/plugins/blog/views/admin/view.html | 2 - app/plugins/blog/views/recentWidget.html | 6 +- app/plugins/blog/views/sidebar.html | 10 +- app/plugins/blog/views/sidebar2.html | 8 +- app/plugins/bugreport/views/admin/list.html | 4 +- app/plugins/bugreport/views/admin/view.html | 14 +- app/plugins/comments/models/comments.php | 2 +- .../comments/views/admin/dashboard.html | 4 +- app/plugins/comments/views/admin/list.html | 4 +- app/plugins/comments/views/control.html | 8 +- app/plugins/comments/views/list.html | 16 +- app/plugins/feedback/views/admin/list.html | 4 +- app/plugins/feedback/views/admin/view.html | 14 +- app/plugins/messages/models/message.php | 16 +- app/plugins/messages/plugin.php | 30 +- app/plugins/messages/views/mesage.html | 14 +- app/plugins/messages/views/message.html | 14 +- .../views/nav/recentMessagesDropdown.html | 19 +- .../notifications/models/notification.php | 6 +- app/plugins/notifications/plugin.php | 14 +- app/plugins/notifications/views/list.html | 4 +- .../nav/recentNotificationsDropdown.html | 15 +- app/plugins/subscribe/views/admin/list.html | 2 +- app/resources/templates/example.tpl | 6 +- app/templates/admin/admin.tpl | 53 ++- app/templates/default/default.inc.php | 2 +- app/templates/default/default.tpl | 38 +- app/views/admin/dashboard/dash.html | 6 +- app/views/admin/dashboard/users.html | 6 +- app/views/admin/groups/list.html | 4 +- app/views/admin/groups/list_members.html | 8 +- app/views/admin/groups/view.html | 12 +- app/views/admin/logs/admin.html | 2 +- app/views/admin/logs/admin_list.html | 4 +- app/views/admin/logs/error.html | 2 +- app/views/admin/logs/error_list.html | 4 +- app/views/admin/logs/login.html | 2 +- app/views/admin/logs/login_list.html | 4 +- app/views/admin/modules/models/list.html | 2 +- app/views/admin/modules/models/view.html | 12 +- app/views/admin/modules/plugins/view.html | 12 +- app/views/admin/routes/list.html | 4 +- app/views/admin/routes/view.html | 12 +- app/views/admin/tokens/list.html | 2 +- app/views/admin/tokens/view.html | 12 +- app/views/admin/users/list.html | 4 +- app/views/admin/users/view.html | 20 +- app/views/alpha/index.html | 4 +- app/views/copy.html | 2 +- app/views/footer/container.html | 26 +- app/views/forms/iconSelect.html | 12 +- app/views/index.html | 74 ++-- app/views/install/nav.html | 20 +- app/views/issues/error.html | 2 +- app/views/issues/info.html | 2 +- app/views/issues/notice.html | 2 +- app/views/issues/success.html | 2 +- app/views/login.html | 49 ++- app/views/nav/admin.html | 9 +- app/views/nav/main.html | 2 +- app/views/nav/statusLoggedIn.html | 17 +- app/views/nav/statusLoggedOut.html | 73 +++- app/views/nav/usercp.html | 2 +- app/views/profile.html | 18 +- app/views/switches.html | 18 +- bin/tempus_project.php | 5 + composer.json | 4 +- 79 files changed, 861 insertions(+), 504 deletions(-) diff --git a/app/css/main-dark.css b/app/css/main-dark.css index 627223d..ba4ff32 100644 --- a/app/css/main-dark.css +++ b/app/css/main-dark.css @@ -48,4 +48,376 @@ body { border-color: #555; box-shadow: none; } - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /** + * app/css/main.css + * + * This file is for any css that should be applied site wide. + * + * @version 3.0 + * @author Joey Kimsey + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ + +@media ( min-width: 768px ) { + .side-nav { + /* background-color: #222; */ + } + .side-nav li a:hover, + .side-nav li a:focus { + background-color: #fff !important; + } +} + +/** +* Other +*/ +.bars { + /* background-color: #333; */ + /* box-shadow: 0 5px 0 #333, 0 10px 0 #333; */ +} +.bg-gray { + /* background-image: -moz-linear-gradient( center bottom, #BBBBBB 0%, #F0F0F0 100% ); */ + /* box-shadow: 0 1px 0 #B4B3B3; */ +} +.UI-buffer { + /* border-bottom: 1px solid #CCCCCC; */ +} +.UI-page-buffer { + /* border-bottom: 1px solid #CCCCCC; */ +} +.table-user-information>tbody>tr { + /* border-top: 1px solid rgb( 221, 221, 221 ); */ +} +.sticky-foot-head { + /* background: #EDEFF1; */ + /* border-bottom: 1px solid #CCCCCC; */ + /* border-top: 1px solid #DDDDDD; */ +} +.sticky-foot { + background-color: #fff; +} +.sticky-copy { + background: #282828; /* E3E3E3; */ + border-bottom: 1px solid #b0b0b0; /* CCCCCC; */ + border-top: 1px solid #bfbfbf; /* DDDDDD; */ +} + +/** +* Main Carousel +*/ +.main-text { + color: #000; +} +.btn-clear { + color: #000; + border-color: #000; +} +.btn-clear:hover { + color: #fff; + /* background-color: #F000FF; */ +} + +/** +* Top Navigation +*/ +.top-nav>li>a { + /* color: #999; */ +} +.top-nav>li>a:hover, +.top-nav>li>a:focus, +.top-nav>.open>a, +.top-nav>.open>a:hover, +.top-nav>.open>a:focus { + color: #000; + background-color: #fff; +} +.top-nav>.open>.dropdown-menu { + /* border: 1px solid rgba( 0, 0, 0, .15 ); */ + background-color: #000; + /* -webkit-box-shadow: 0 6px 12px rgba( 0, 0, 0, .175 ); */ + /* box-shadow: 0 6px 12px rgba( 0, 0, 0, .175 ); */ +} + +/** +* Messages Dropdown +*/ +li.message-header { + /* border-bottom: 1px solid rgba( 0, 0, 0, .15 ); */ +} +li.message-preview { + /* border-bottom: 1px solid rgba( 0, 0, 0, .15 ); */ +} + +/** +* Widget +*/ +.widget li.list-group-item { + /* border-top: 1px solid #ddd; */ +} +.widget li.list-group-item:hover { + /* background-color: rgba( 86, 61, 124, .1 ); */ +} +.widget .mic-info { + /* color: #666666; */ +} + +/** +* Footer and Copyright +*/ +.copy { + background: #282828; /* E3E3E3; */ + border-bottom: 1px solid #b0b0b0; /* CCCCCC; */ + border-top: 1px solid #bfbfbf; /* DDDDDD; */ +} +.footer-head { + background: #282828; /* E3E3E3; */ + border-bottom: 1px solid #b0b0b0; /* CCCCCC; */ + border-top: 1px solid #bfbfbf; /* DDDDDD; */ +} +.footer-head h3 { + border-bottom: 1px solid #a1a7ad; /* BAC1C8 */ + color: #4a5c6e; /* 54697E */ +} +.footer-head ul { + /* color: #7F8C8D; */ +} +.footer-head a { + /* color: #78828D */ +} + +/** +* Side Navigation +*/ +.side-nav>li>ul>li>a { + /* color: #999; */ +} +.side-nav>li>ul>li>a:hover { + color: #000; +} +.side-nav .active > a { + color: #000; + background-color: #fff; +} +.side-nav .active > a:hover { + color: #000; + background-color: #fff; +} + +/** +* Social +*/ +.social span { + /* background: none repeat scroll 0 0 #B5B5B5; */ + /* border: 2px solid #B5B5B5; */ +} +.social span a { + /* color: #EDEFF1; */ +} +.social span:hover { + /* border: 2px solid #2c3e50; + background: #2c3e50; */ +} +.social span a i { + /* color: #EDEFF1 !important; */ +} + +/** +* Newsletter Box +*/ +.newsletter-box input#appendedInputButton { + background: #000; +} +.newsletter-box .btn { + color: #000; +} + +/** +* Colored Badges +*/ +.badge { + color: #000; + /* background-color: #999999; */ +} +.badge:hover { + color: #000; +} +.badge-error { + /* background-color: #b94a48; */ +} +.badge-error:hover { + /* background-color: #953b39; */ +} +.badge-warning { + /* background-color: #f89406; */ +} +.badge-warning:hover { + /* background-color: #c67605; */ +} +.badge-success { + /* background-color: #468847; */ +} +.badge-success:hover { + /* background-color: #356635; */ +} +.badge-info { + /* background-color: #3a87ad; */ +} +.badge-info:hover { + /* background-color: #2d6987; */ +} +.badge-inverse { + /* background-color: #333333; */ +} +.badge-inverse:hover { + /* background-color: #1a1a1a; */ +} + +/** +* Install Terms +*/ +.install-terms { + /* border: 1px solid #ccc; + background: #f2f2f2; */ +} +.install-terms p, +.install-terms li { + /* color: #333; */ +} +.install-terms h3 { + color: #fff; +} +.install-terms h4 { + color: #fff; +} +.install-terms strong { + color: #fff; +} + +/** +* Terms Page +*/ +.terms-page { + /* border: 1px solid #ccc; + background: #f2f2f2; */ +} +.terms-page p, +.terms-page li { + /* color: #333; */ +} +.terms-page h3 { + color: #fff; +} +.terms-page h4 { + color: #fff; +} +.terms-page strong { + color: #fff; +} + +/** +* Terms +*/ +.terms { + /* border: 1px solid #ccc; */ + /* background: #f2f2f2; */ +} +.terms p, +.terms li { + /* color: #333; */ +} +.terms h3 { + color: #fff; +} +.terms h4 { + color: #fff; +} +.terms strong { + color: #fff; +} + +.material-switch > label::before { + /* background: rgb(0, 0, 0); */ + /* box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.5); */ +} +.material-switch > label::after { + /* background: rgb(255, 255, 255); */ + /* box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); */ +} \ No newline at end of file diff --git a/app/css/main.css b/app/css/main.css index d1b1bd6..b048703 100644 --- a/app/css/main.css +++ b/app/css/main.css @@ -31,45 +31,11 @@ pre { padding-right: 225px; padding-left: 0; } - .side-nav { - right: 0; - left: auto; - } - .side-nav { - position: fixed; - top: 51px; - left: 225px; - width: 225px; - margin-left: -225px; - border: none; - border-radius: 0; - overflow-y: auto; - background-color: #222; - bottom: 53px; - overflow-x: hidden; - padding-bottom: 10px; - } - .side-nav>li>a { - width: 225px; - } - .side-nav li a:hover, - .side-nav li a:focus { - outline: none; - background-color: #000 !important; - } } /** * Other */ -.custom-nav { - display: relative; - float: right; -} -.navbar-form-alt { - margin-top: 10px; - margin-bottom: 10px; -} .bars { display: block; width: 60px; @@ -158,7 +124,7 @@ pre { .dynamic-footer-padding { padding-bottom: var(--footer-height); } -.footer-head .navbar-toggle { +.footer-head .navbar-toggler { display: inline-block; float: none; } @@ -225,45 +191,6 @@ pre { margin: 0 auto; } -/** - * Top Navigation - */ -.top-nav { - padding: 0 15px; -} -.top-nav>li { - display: inline-block; - float: left; -} -.top-nav>li>a { - padding-top: 15px; - padding-bottom: 15px; - line-height: 20px; - color: #999; -} -.top-nav>li>a:hover, -.top-nav>li>a:focus, -.top-nav>.open>a, -.top-nav>.open>a:hover, -.top-nav>.open>a:focus { - color: #fff; - background-color: #000; -} -.top-nav>.open>.dropdown-menu { - float: left; - position: absolute; - margin-top: 0; - border: 1px solid rgba( 0, 0, 0, .15 ); - border-top-left-radius: 0; - border-top-right-radius: 0; - background-color: #fff; - -webkit-box-shadow: 0 6px 12px rgba( 0, 0, 0, .175 ); - box-shadow: 0 6px 12px rgba( 0, 0, 0, .175 ); -} -.top-nav>.open>.dropdown-menu>li>a { - white-space: normal; -} - /** * Messages Dropdown */ @@ -327,44 +254,6 @@ ul.alert-dropdown { border-top-right-radius: 0px; } -/** - * Signin Form - */ -.form-signin { - max-width: 330px; - padding: 15px; - margin: 0 auto; -} -.form-signin .form-signin-heading, -.form-signin .checkbox { - margin-bottom: 10px; -} -.form-signin .checkbox { - font-weight: normal; -} -.form-signin .form-control { - position: relative; - height: auto; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 10px; - font-size: 16px; -} -.form-signin .form-control:focus { - z-index: 2; -} -.form-signin input[type="text"] { - margin-bottom: -1px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.form-signin input[type="password"] { - margin-bottom: 10px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - /** * Footer and Copyright */ @@ -420,30 +309,6 @@ ul.alert-dropdown { color: #78828D } -/** - * Side Navigation - */ -.side-nav>li>ul { - padding: 0; -} -.side-nav>li>ul>li>a { - display: block; - padding: 10px 15px 10px 38px; - text-decoration: none; - color: #999; -} -.side-nav>li>ul>li>a:hover { - color: #fff; -} -.side-nav .active > a { - color: #fff; - background-color: #080808; -} -.side-nav .active > a:hover { - color: #fff; - background-color: #080808; -} - /** * Social */ diff --git a/app/js/main.js b/app/js/main.js index d1fcd2e..e4008c8 100644 --- a/app/js/main.js +++ b/app/js/main.js @@ -117,6 +117,7 @@ window.onload = function () { // Check if dark mode is saved in localStorage if (localStorage.getItem('darkMode') === 'enabled') { darkModeStylesheet.disabled = false; + toggleButton.checked = true; } toggleButton.addEventListener('click', function () { diff --git a/app/models/group.php b/app/models/group.php index 4a375f6..bbc512b 100644 --- a/app/models/group.php +++ b/app/models/group.php @@ -257,7 +257,7 @@ class Group extends DatabaseModel { if ( $group === false ) { return false; } - $members = self::$db->getPaginated( 'users', [ 'userGroup', '=', $id ] ); + $members = self::$db->get( 'users', [ 'userGroup', '=', $id ] ); if ( !$members->count() ) { Debug::info( "list members: Could not find anyone in group: $id" ); return false; diff --git a/app/models/log.php b/app/models/log.php index 9131548..efd7b4a 100644 --- a/app/models/log.php +++ b/app/models/log.php @@ -87,7 +87,7 @@ class Log extends DatabaseModel { } public function list( $filter = null ) { - $logData = self::$db->getPaginated( $this->tableName, [ 'source', '=', $filter ] ); + $logData = self::$db->get( $this->tableName, [ 'source', '=', $filter ] ); if ( !$logData->count() ) { return false; } diff --git a/app/models/routes.php b/app/models/routes.php index 19b26b2..63a23d1 100644 --- a/app/models/routes.php +++ b/app/models/routes.php @@ -128,7 +128,7 @@ class Routes extends DatabaseModel { } $routeData = self::$db->get( $this->tableName, [ 'nickname', '=', $name ] ); if ( !$routeData->count() ) { - Debug::warn( "Could not find a group named: $name" ); + Debug::info( "Routes:findByName: Could not find a route named: $name" ); return false; } return $this->filter( $routeData->first() ); @@ -137,7 +137,7 @@ class Routes extends DatabaseModel { public function findByOriginalUrl( $url ) { $routeData = self::$db->get( $this->tableName, [ 'original_url', '=', $url ] ); if ( !$routeData->count() ) { - Debug::warn( "Could not find route by original url: $url" ); + Debug::info( "Routes:findByOriginalUrl: Could not find route by original url: $url" ); return false; } return $this->filter( $routeData->first() ); @@ -145,12 +145,12 @@ class Routes extends DatabaseModel { public function findByforwardedUrl( $url ) { if ( !Check::url( $url ) ) { - Debug::warn( "Invalid forwarded_url: $url" ); + Debug::warn( "Routes:findByforwardedUrl: Invalid forwarded_url: $url" ); return false; } $routeData = self::$db->get( $this->tableName, [ 'forwarded_url', '=', $url ] ); if ( !$routeData->count() ) { - Debug::warn( "Could not find route by forwarded url: $url" ); + Debug::info( "Routes:findByforwardedUrl: Could not find route by forwarded url: $url" ); return false; } return $this->filter( $routeData->first() ); diff --git a/app/models/user.php b/app/models/user.php index 1ab7566..aa0f80d 100644 --- a/app/models/user.php +++ b/app/models/user.php @@ -446,7 +446,7 @@ class User extends DatabaseModel { */ public function recent( $limit = null ) { if ( empty( $limit ) ) { - $data = self::$db->getpaginated( $this->tableName, '*' ); + $data = self::$db->get( $this->tableName, '*' ); } else { $data = self::$db->get( $this->tableName, [ 'ID', '>', '0' ], 'ID', 'DESC', [ 0, $limit ] ); } diff --git a/app/plugins/blog/models/posts.php b/app/plugins/blog/models/posts.php index 5f8873d..471f51d 100644 --- a/app/plugins/blog/models/posts.php +++ b/app/plugins/blog/models/posts.php @@ -221,9 +221,9 @@ class Posts extends DatabaseModel { $whereClause = '*'; } if ( empty( $limit ) ) { - $postData = self::$db->getPaginated( $this->tableName, $whereClause ); + $postData = self::$db->get( $this->tableName, $whereClause ); } else { - $postData = self::$db->getPaginated( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + $postData = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); } if ( !$postData->count() ) { Debug::info( 'No Blog posts found.' ); @@ -239,7 +239,7 @@ class Posts extends DatabaseModel { } else { $whereClause = ['draft', '=', '0']; } - $postData = self::$db->getPaginated( $this->tableName, $whereClause ); + $postData = self::$db->get( $this->tableName, $whereClause ); if ( !$postData->count() ) { Debug::info( 'No Blog posts found.' ); @@ -263,7 +263,7 @@ class Posts extends DatabaseModel { $firstDayUnix = date( 'U', strtotime( "first day of $year" ) ); $lastDayUnix = date( 'U', strtotime( "last day of $year" ) ); $whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] ); - $postData = self::$db->getPaginated( $this->tableName, $whereClause ); + $postData = self::$db->get( $this->tableName, $whereClause ); if ( !$postData->count() ) { Debug::info( 'No Blog posts found.' ); @@ -282,7 +282,7 @@ class Posts extends DatabaseModel { $whereClause = ['draft', '=', '0', 'AND']; } $whereClause = array_merge( $whereClause, ['author' => $ID] ); - $postData = self::$db->getPaginated( $this->tableName, $whereClause ); + $postData = self::$db->get( $this->tableName, $whereClause ); if ( !$postData->count() ) { Debug::info( 'No Blog posts found.' ); @@ -311,7 +311,7 @@ class Posts extends DatabaseModel { $month = date( 'F', $firstDayUnix ); $lastDayUnix = date( 'U', strtotime( "last day of $month $year" ) ); $whereClause = array_merge( $whereClause, ['created', '<=', $lastDayUnix, 'AND', 'created', '>=', $firstDayUnix] ); - $postData = self::$db->getPaginated( $this->tableName, $whereClause ); + $postData = self::$db->get( $this->tableName, $whereClause ); if ( !$postData->count() ) { Debug::info( 'No Blog posts found.' ); diff --git a/app/plugins/blog/plugin.php b/app/plugins/blog/plugin.php index d784824..dd90d1b 100644 --- a/app/plugins/blog/plugin.php +++ b/app/plugins/blog/plugin.php @@ -28,7 +28,7 @@ class Blog extends Plugin { public $pluginDescription = 'A simple plugin to add a blog to your installation.'; public $admin_links = [ [ - 'text' => ' Blog', + 'text' => ' Blog', 'url' => '{ROOT_URL}admin/blog', ], ]; diff --git a/app/plugins/blog/templates/blog.tpl b/app/plugins/blog/templates/blog.tpl index 0be2524..eceda38 100644 --- a/app/plugins/blog/templates/blog.tpl +++ b/app/plugins/blog/templates/blog.tpl @@ -32,7 +32,6 @@ - @@ -44,7 +43,7 @@ \ No newline at end of file diff --git a/app/plugins/blog/views/post.html b/app/plugins/blog/views/post.html index 9a55303..bd0a8c9 100644 --- a/app/plugins/blog/views/post.html +++ b/app/plugins/blog/views/post.html @@ -7,8 +7,8 @@ {content} {ADMIN}
- Delete - Edit + Delete + Edit
{/ADMIN} diff --git a/app/plugins/bugreport/views/admin/list.html b/app/plugins/bugreport/views/admin/list.html index b62e48a..7f4d302 100644 --- a/app/plugins/bugreport/views/admin/list.html +++ b/app/plugins/bugreport/views/admin/list.html @@ -20,8 +20,8 @@ {ID} {DTC}{time}{/DTC} {description} - - + + diff --git a/app/plugins/comments/views/admin/dashboard.html b/app/plugins/comments/views/admin/dashboard.html index 49f63ec..7fc7b6d 100644 --- a/app/plugins/comments/views/admin/dashboard.html +++ b/app/plugins/comments/views/admin/dashboard.html @@ -13,8 +13,8 @@ {authorName} {content} - - + + {/LOOP} {ALT} diff --git a/app/plugins/comments/views/admin/list.html b/app/plugins/comments/views/admin/list.html index 1a7d600..37b460d 100644 --- a/app/plugins/comments/views/admin/list.html +++ b/app/plugins/comments/views/admin/list.html @@ -22,8 +22,8 @@ {contentTitle} {content} {DTC}{created}{/DTC} - - + + diff --git a/app/plugins/comments/views/control.html b/app/plugins/comments/views/control.html index df9007c..26661f4 100644 --- a/app/plugins/comments/views/control.html +++ b/app/plugins/comments/views/control.html @@ -1,8 +1,8 @@ \ No newline at end of file diff --git a/app/plugins/feedback/views/admin/list.html b/app/plugins/feedback/views/admin/list.html index 345dd2b..0a60223 100644 --- a/app/plugins/feedback/views/admin/list.html +++ b/app/plugins/feedback/views/admin/list.html @@ -20,8 +20,8 @@ {ID} {DTC}{time}{/DTC} {feedback} - - + + diff --git a/app/plugins/messages/views/nav/recentMessagesDropdown.html b/app/plugins/messages/views/nav/recentMessagesDropdown.html index 12254b6..7cd7f31 100644 --- a/app/plugins/messages/views/nav/recentMessagesDropdown.html +++ b/app/plugins/messages/views/nav/recentMessagesDropdown.html @@ -1,15 +1,15 @@ -