token support, api fixes and security, dark mode
This commit is contained in:
@ -16,11 +16,19 @@ use TheTempusProject\Houdini\Classes\Template;
|
|||||||
use TheTempusProject\TheTempusProject as App;
|
use TheTempusProject\TheTempusProject as App;
|
||||||
use TheTempusProject\Hermes\Functions\Redirect;
|
use TheTempusProject\Hermes\Functions\Redirect;
|
||||||
use TheTempusProject\Bedrock\Functions\Session;
|
use TheTempusProject\Bedrock\Functions\Session;
|
||||||
|
use TheTempusProject\Bedrock\Classes\Config;
|
||||||
|
use TheTempusProject\Models\Token;
|
||||||
|
|
||||||
class ApiController extends Controller {
|
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();
|
parent::__construct();
|
||||||
if ( ! App::verifyApiRequest() ) {
|
$this->verifyApiRequest();
|
||||||
|
if ( $secure && ! $this->canUseApi() ) {
|
||||||
Session::flash( 'error', 'You do not have permission to view this page.' );
|
Session::flash( 'error', 'You do not have permission to view this page.' );
|
||||||
return Redirect::home();
|
return Redirect::home();
|
||||||
}
|
}
|
||||||
@ -29,4 +37,100 @@ class ApiController extends Controller {
|
|||||||
Template::addHeader( 'Content-Type: application/json; charset=utf-8' );
|
Template::addHeader( 'Content-Type: application/json; charset=utf-8' );
|
||||||
Template::setTemplate( 'api' );
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,8 @@ class Forms extends Check {
|
|||||||
self::addHandler( 'newGroup', __CLASS__, 'newGroup' );
|
self::addHandler( 'newGroup', __CLASS__, 'newGroup' );
|
||||||
self::addHandler( 'editGroup', __CLASS__, 'editGroup' );
|
self::addHandler( 'editGroup', __CLASS__, 'editGroup' );
|
||||||
self::addHandler( 'install', __CLASS__, 'install' );
|
self::addHandler( 'install', __CLASS__, 'install' );
|
||||||
|
self::addHandler( 'adminCreateToken', __CLASS__, 'adminCreateToken' );
|
||||||
|
self::addHandler( 'apiLogin', __CLASS__, 'apiLogin' );
|
||||||
self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] );
|
self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] );
|
||||||
self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] );
|
self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] );
|
||||||
self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] );
|
self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] );
|
||||||
@ -608,4 +610,40 @@ class Forms extends Check {
|
|||||||
}
|
}
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
90
app/controllers/admin/tokens.php
Normal file
90
app/controllers/admin/tokens.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/controllers/admin/tokens.php
|
||||||
|
*
|
||||||
|
* This is the admin app/user tokens controller.
|
||||||
|
*
|
||||||
|
* @version 3.0
|
||||||
|
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||||
|
* @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 ) );
|
||||||
|
}
|
||||||
|
}
|
38
app/controllers/api/auth.php
Normal file
38
app/controllers/api/auth.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/controllers/api/auth.php
|
||||||
|
*
|
||||||
|
* This is the api authentication controller.
|
||||||
|
*
|
||||||
|
* @version 3.0
|
||||||
|
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||||
|
* @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 )]);
|
||||||
|
}
|
||||||
|
}
|
51
app/controllers/api/login.php
Normal file
51
app/controllers/api/login.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/controllers/api/auth.php
|
||||||
|
*
|
||||||
|
* This is the api authentication controller.
|
||||||
|
*
|
||||||
|
* @version 3.0
|
||||||
|
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||||
|
* @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 )]);
|
||||||
|
}
|
||||||
|
}
|
@ -98,4 +98,22 @@ class Home extends Controller {
|
|||||||
// this should look up comments and blog posts with the hashtag in them
|
// this should look up comments and blog posts with the hashtag in them
|
||||||
Views::view( 'hashtags' );
|
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' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
51
app/css/main-dark.css
Normal file
51
app/css/main-dark.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -667,3 +667,50 @@ ul.alert-dropdown {
|
|||||||
.pagination {
|
.pagination {
|
||||||
padding-left: 75px;
|
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;
|
||||||
|
}
|
@ -89,3 +89,17 @@ function iv( $variable ) {
|
|||||||
echo var_export( $variable, true );
|
echo var_export( $variable, true );
|
||||||
echo '</pre>';
|
echo '</pre>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
@ -109,3 +109,23 @@ window.onload = function () {
|
|||||||
// Update padding on window resize
|
// Update padding on window resize
|
||||||
window.addEventListener('resize', updateFooterPadding);
|
window.addEventListener('resize', updateFooterPadding);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -145,22 +145,6 @@ class Sessions extends DatabaseModel {
|
|||||||
return true;
|
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
|
* Creates a new session from the data provided. The
|
||||||
* expiration time is optional and will be set to the
|
* expiration time is optional and will be set to the
|
||||||
|
198
app/models/token.php
Normal file
198
app/models/token.php
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/models/token.php
|
||||||
|
*
|
||||||
|
* This class is used for the manipulation of the tokens database table.
|
||||||
|
*
|
||||||
|
* @version 3.0
|
||||||
|
* @author Joey Kimsey <Joey@thetempusproject.com>
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
@ -43,7 +43,6 @@ class User extends DatabaseModel {
|
|||||||
[ 'name', 'varchar', '20' ],
|
[ 'name', 'varchar', '20' ],
|
||||||
[ 'confirmationCode', 'varchar', '80' ],
|
[ 'confirmationCode', 'varchar', '80' ],
|
||||||
[ 'prefs', 'text', '' ],
|
[ 'prefs', 'text', '' ],
|
||||||
[ 'auth_token', 'text', '' ],
|
|
||||||
];
|
];
|
||||||
public $permissionMatrix = [
|
public $permissionMatrix = [
|
||||||
'uploadImages' => [
|
'uploadImages' => [
|
||||||
@ -694,33 +693,45 @@ class User extends DatabaseModel {
|
|||||||
return $this->data;
|
return $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findByToken( $token ) {
|
public function authorize( $username, $password ) {
|
||||||
$data = self::$db->get( $this->tableName, [ 'auth_token', '=', $token ] );
|
if ( !isset( self::$log ) ) {
|
||||||
if ( ! $data->count() ) {
|
self::$log = new Log;
|
||||||
|
}
|
||||||
|
if ( !$this->get( $username ) ) {
|
||||||
|
self::$log->login( 0, "API: User not found: $username" );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $data->first();
|
// login attempts protection.
|
||||||
}
|
$timeLimit = ( time() - 3600 );
|
||||||
|
$limit = Config::getValue( 'main/loginLimit' );
|
||||||
public function addAccessToken( $id, $length = 64 ) {
|
$user = $this->data();
|
||||||
if ( ! Check::id( $id ) ) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
$fields = [ 'auth_token' => $this->generateRandomString( $length ) ];
|
}
|
||||||
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
|
if ( !Check::password( $password ) ) {
|
||||||
Debug::error( "User: $id not updated." );
|
self::$log->login( $user->ID, 'API: Invalid Password.' );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
if ( !Hash::check( $password, $user->password ) ) {
|
||||||
|
self::$log->login( $user->ID, 'API: Wrong Password.' );
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
self::$log->login( $this->data()->ID, 'API: pass' );
|
||||||
private function generateRandomString( $length = 10 ) {
|
return $user;
|
||||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
$charactersLength = strlen( $characters );
|
|
||||||
$randomString = '';
|
|
||||||
for ($i = 0; $i < $length; $i++) {
|
|
||||||
$randomString .= $characters[random_int(0, $charactersLength - 1)];
|
|
||||||
}
|
|
||||||
return $randomString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<div class="col-lg-3 text-center">
|
<div class="col-lg-3 text-center">
|
||||||
<h3>Subscribe</h3>
|
<h3>Subscribe</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@ -12,4 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="material-switch" style="margin-top: 25px; margin-bottom: 25px;">
|
||||||
|
<input name="dark-mode-toggle" type="checkbox" id="dark-mode-toggle" style="margin-top: 25px; margin-bottom: 25px; text-align:left;"/>
|
||||||
|
<label for="dark-mode-toggle" class="label-default" style="text-align:left;"></label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
@ -38,6 +38,7 @@ class DefaultLoader extends Loader {
|
|||||||
Components::set( 'FONT_AWESOME_URL', self::FONT_AWESOME_URL );
|
Components::set( 'FONT_AWESOME_URL', self::FONT_AWESOME_URL );
|
||||||
}
|
}
|
||||||
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main.css">' );
|
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main.css">' );
|
||||||
|
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main-dark.css" id="dark-mode-stylesheet" disabled>' );
|
||||||
$this->addJs( '<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/js/main.js"></script>' );
|
$this->addJs( '<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/js/main.js"></script>' );
|
||||||
Components::setIfNull( 'LOGO', Config::getValue( 'main/logo' ) );
|
Components::setIfNull( 'LOGO', Config::getValue( 'main/logo' ) );
|
||||||
Components::setIfNull( 'FOOTER_LEFT', Navigation::getMenuView( 'footer.left', 'FOOTER_LINKS', App::FOOTER_MENU_NAME, false ) );
|
Components::setIfNull( 'FOOTER_LEFT', Navigation::getMenuView( 'footer.left', 'FOOTER_LINKS', App::FOOTER_MENU_NAME, false ) );
|
||||||
|
0
app/views/about.html
Normal file
0
app/views/about.html
Normal file
28
app/views/admin/tokens/create.html
Normal file
28
app/views/admin/tokens/create.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<form action="" method="post" class="form-horizontal">
|
||||||
|
<legend>Create Token</legend>
|
||||||
|
<fieldset>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name" class="col-lg-6 control-label">Name:</label>
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<input class="form-control" type="text" name="name" id="name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="notes" class="col-lg-3 control-label">Notes</label>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<textarea class="form-control" name="notes" maxlength="2000" rows="10" cols="50" id="notes"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="token_type" class="col-lg-6 control-label">Token Type:</label>
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<select name="token_type" id="token_type" class="">
|
||||||
|
<option value='app' selected>Application</option>
|
||||||
|
<option value='user'>User</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<input type="hidden" name="token" value="{TOKEN}">
|
||||||
|
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Create</button><br>
|
||||||
|
</form>
|
29
app/views/admin/tokens/edit.html
Normal file
29
app/views/admin/tokens/edit.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<form action="" method="post" class="form-horizontal">
|
||||||
|
<legend>Edit Token</legend>
|
||||||
|
<fieldset>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name" class="col-lg-6 control-label">Name:</label>
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<input class="form-control" type="text" name="name" id="name" value="{name}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="notes" class="col-lg-3 control-label">Notes</label>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<textarea class="form-control" name="notes" maxlength="2000" rows="10" cols="50" id="notes">{notes}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="token_type" class="col-lg-6 control-label">Token Type:</label>
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<select name="token_type" id="token_type" class="">
|
||||||
|
{OPTION=token_type}
|
||||||
|
<option value='app'>Application</option>
|
||||||
|
<option value='user'>User</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<input type="hidden" name="token" value="{TOKEN}">
|
||||||
|
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Save</button><br>
|
||||||
|
</form>
|
30
app/views/admin/tokens/list.html
Normal file
30
app/views/admin/tokens/list.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<legend>Tokens</legend>
|
||||||
|
{PAGINATION}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 15%">ID</th>
|
||||||
|
<th style="width: 45%">Name</th>
|
||||||
|
<th style="width: 25%">Type</th>
|
||||||
|
<th style="width: 15%">Delete</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{LOOP}
|
||||||
|
<tr>
|
||||||
|
<td align="center">{ID}</td>
|
||||||
|
<td><a href='{ROOT_URL}admin/tokens/view/{ID}'>{name}</a></td>
|
||||||
|
<td>{token_type}</td>
|
||||||
|
<td><a href="{ROOT_URL}admin/tokens/delete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="glyphicon glyphicon-trash"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
{/LOOP}
|
||||||
|
{ALT}
|
||||||
|
<tr>
|
||||||
|
<td align="center" colspan="6">
|
||||||
|
No results to show.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/ALT}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<a href="{ROOT_URL}admin/tokens/create" class="btn btn-sm btn-primary" role="button">Create</a>
|
48
app/views/admin/tokens/view.html
Normal file
48
app/views/admin/tokens/view.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-6 col-xs-offset-0 col-sm-offset-0 col-md-offset-3 col-lg-offset-3 top-pad" >
|
||||||
|
<div class="panel panel-primary">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{name}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class=" col-md-9 col-lg-9 ">
|
||||||
|
<table class="table table-user-primary">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Type</td>
|
||||||
|
<td>{token_type}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Created At</td>
|
||||||
|
<td>{DTC}{createdAt}{/DTC}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Expires At</td>
|
||||||
|
<td>{DTC}{expiresAt}{/DTC}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Token</td>
|
||||||
|
<td>{token}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Secret</td>
|
||||||
|
<td>{secret}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Notes</td>
|
||||||
|
<td>{notes}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
<a href="{ROOT_URL}admin/tokens/delete/{ID}" class="btn btn-sm btn-danger" role="button">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
0
app/views/contact.html
Normal file
0
app/views/contact.html
Normal file
@ -12,4 +12,8 @@
|
|||||||
<a href="{ROOT_URL}privacy">Privacy Policy</a>
|
<a href="{ROOT_URL}privacy">Privacy Policy</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="material-switch" style="margin-top: 25px; margin-bottom: 25px;">
|
||||||
|
<input name="dark-mode-toggle" type="checkbox" id="dark-mode-toggle"/>
|
||||||
|
<label for="dark-mode-toggle" class="label-default"></label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
0
app/views/privacy.html
Normal file
0
app/views/privacy.html
Normal file
52
app/views/switches.html
Normal file
52
app/views/switches.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<div class="col-xs-12 col-sm-6 col-md-4 col-sm-offset-3 col-md-offset-4">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<!-- Default panel contents -->
|
||||||
|
<div class="panel-heading">Material Design Switch Demos</div>
|
||||||
|
|
||||||
|
<!-- List group -->
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
Bootstrap Switch Default
|
||||||
|
<div class="material-switch pull-right">
|
||||||
|
<input id="someSwitchOptionDefault" name="someSwitchOption001" type="checkbox"/>
|
||||||
|
<label for="someSwitchOptionDefault" class="label-default"></label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
Bootstrap Switch Primary
|
||||||
|
<div class="material-switch pull-right">
|
||||||
|
<input id="someSwitchOptionPrimary" name="someSwitchOption001" type="checkbox"/>
|
||||||
|
<label for="someSwitchOptionPrimary" class="label-primary"></label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
Bootstrap Switch Success
|
||||||
|
<div class="material-switch pull-right">
|
||||||
|
<input id="someSwitchOptionSuccess" name="someSwitchOption001" type="checkbox"/>
|
||||||
|
<label for="someSwitchOptionSuccess" class="label-success"></label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
Bootstrap Switch Info
|
||||||
|
<div class="material-switch pull-right">
|
||||||
|
<input id="someSwitchOptionInfo" name="someSwitchOption001" type="checkbox"/>
|
||||||
|
<label for="someSwitchOptionInfo" class="label-info"></label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
Bootstrap Switch Warning
|
||||||
|
<div class="material-switch pull-right">
|
||||||
|
<input id="someSwitchOptionWarning" name="someSwitchOption001" type="checkbox"/>
|
||||||
|
<label for="someSwitchOptionWarning" class="label-warning"></label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
Bootstrap Switch Danger
|
||||||
|
<div class="material-switch pull-right">
|
||||||
|
<input id="someSwitchOptionDanger" name="someSwitchOption001" type="checkbox"/>
|
||||||
|
<label for="someSwitchOptionDanger" class="label-danger"></label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -82,6 +82,10 @@ class TheTempusProject extends Bedrock {
|
|||||||
'text' => '<i class="fa fa-fw fa-reply-all"></i> Contact',
|
'text' => '<i class="fa fa-fw fa-reply-all"></i> Contact',
|
||||||
'url' => '{ROOT_URL}admin/contact',
|
'url' => '{ROOT_URL}admin/contact',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'text' => '<i class="fa fa-fw fa-reply-all"></i> Tokens',
|
||||||
|
'url' => '{ROOT_URL}admin/tokens',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'text' => '<i class="fa fa-fw fa-arrows-v"></i> Modules',
|
'text' => '<i class="fa fa-fw fa-arrows-v"></i> Modules',
|
||||||
'url' => [
|
'url' => [
|
||||||
@ -399,8 +403,7 @@ class TheTempusProject extends Bedrock {
|
|||||||
self::$activePrefs = $user->getDefaultPreferences(); // PREFERENCES_JSON
|
self::$activePrefs = $user->getDefaultPreferences(); // PREFERENCES_JSON
|
||||||
if (
|
if (
|
||||||
!$sessions->checkSession( Session::get( 'SessionID' ) ) &&
|
!$sessions->checkSession( Session::get( 'SessionID' ) ) &&
|
||||||
!$sessions->checkCookie( Cookie::get( 'RememberToken' ), true ) &&
|
!$sessions->checkCookie( Cookie::get( 'RememberToken' ), true )
|
||||||
!$sessions->checkToken( self::getBearerToken(), true )
|
|
||||||
) {
|
) {
|
||||||
Debug::info( 'Sessions->authenticate - Could not authenticate cookie or session' );
|
Debug::info( 'Sessions->authenticate - Could not authenticate cookie or session' );
|
||||||
return false;
|
return false;
|
||||||
@ -527,46 +530,6 @@ class TheTempusProject extends Bedrock {
|
|||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
* Echos useful information about the installation.
|
||||||
*
|
*
|
||||||
|
27
docker/ttp-nginx/cors.conf
Normal file
27
docker/ttp-nginx/cors.conf
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user