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 @@
-
+
+
+
+
\ 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 @@
+
\ 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
+
+
+
+
+
+
+ Save
+
\ 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}
+
+
+
+ ID
+ Name
+ Type
+ Delete
+
+
+
+ {LOOP}
+
+ {ID}
+ {name}
+ {token_type}
+
+
+ {/LOOP}
+ {ALT}
+
+
+ No results to show.
+
+
+ {/ALT}
+
+
+ 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