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
+ + + +
+
\ 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