1
0
mirror of https://github.com/beestat/app.git synced 2025-07-09 03:04:07 -04:00

Fixes API-E. Fixes API-K. Fixes API-J.

Cleaned up some logic related to how tokens and refreshing works. Lots of stuff was broken.
This commit is contained in:
Jon Ziebell 2020-01-20 20:36:28 -05:00
parent 5fb835737f
commit 279d97d72f
9 changed files with 116 additions and 75 deletions

View File

@ -271,6 +271,10 @@ final class cora {
$session = session::get_instance(); $session = session::get_instance();
$session_is_valid = $session->touch($this->api_user['session_key']); $session_is_valid = $session->touch($this->api_user['session_key']);
// Make sure the updated session timestamp doesn't get rolled back on
// exception.
$this->database->commit_transaction();
// Process each request. // Process each request.
foreach($this->api_calls as $api_call) { foreach($this->api_calls as $api_call) {
// Store the currently running API call for tracking if an error occurs. // Store the currently running API call for tracking if an error occurs.
@ -291,7 +295,7 @@ final class cora {
// If the request requires a session, make sure it's valid. // If the request requires a session, make sure it's valid.
if($call_type === 'private') { if($call_type === 'private') {
if($session_is_valid === false) { if($session_is_valid === false) {
throw new \Exception('Session is expired.', 1004); throw new exception('Session is expired.', 1004, false);
} }
} }
@ -638,7 +642,8 @@ final class cora {
$error_code, $error_code,
$error_file, $error_file,
$error_line, $error_line,
debug_backtrace(false) debug_backtrace(false),
true
); );
die(); // Do not continue execution; shutdown handler will now run. die(); // Do not continue execution; shutdown handler will now run.
} }
@ -655,7 +660,8 @@ final class cora {
$e->getCode(), $e->getCode(),
$e->getFile(), $e->getFile(),
$e->getLine(), $e->getLine(),
$e->getTrace() $e->getTrace(),
(method_exists($e, 'getReportable') === true ? $e->getReportable() : true)
); );
die(); // Do not continue execution; shutdown handler will now run. die(); // Do not continue execution; shutdown handler will now run.
} }
@ -671,7 +677,7 @@ final class cora {
* @param int $error_line The line of the file the error happened on. * @param int $error_line The line of the file the error happened on.
* @param array $error_trace The stack trace for the error. * @param array $error_trace The stack trace for the error.
*/ */
public function set_error_response($error_message, $error_code, $error_file, $error_line, $error_trace) { public function set_error_response($error_message, $error_code, $error_file, $error_line, $error_trace, $reportable) {
// There are a few places that call this function to set an error response, // There are a few places that call this function to set an error response,
// so this can't just be done in the exception handler alone. If an error // so this can't just be done in the exception handler alone. If an error
// occurs, rollback the current transaction. Also only attempt to roll back // occurs, rollback the current transaction. Also only attempt to roll back
@ -706,43 +712,45 @@ final class cora {
// Send data to Sentry for error logging. // Send data to Sentry for error logging.
// https://docs.sentry.io/development/sdk-dev/event-payloads/ // https://docs.sentry.io/development/sdk-dev/event-payloads/
$data = [ if ($reportable === true) {
'event_id' => str_replace('-', '', exec('uuidgen -r')), $data = [
'timestamp' => date('c'), 'event_id' => str_replace('-', '', exec('uuidgen -r')),
'logger' => 'cora', 'timestamp' => date('c'),
'platform' => 'php', 'logger' => 'cora',
'level' => 'error', 'platform' => 'php',
'tags' => [ 'level' => 'error',
'error_code' => $error_code 'tags' => [
], 'error_code' => $error_code
'extra' => [ ],
'api_user_id' => $api_user_id, 'extra' => [
'error_file' => $error_file, 'api_user_id' => $api_user_id,
'error_line' => $error_line, 'error_file' => $error_file,
'error_trace' => $error_trace 'error_line' => $error_line,
], 'error_trace' => $error_trace
'exception' => [ ],
'type' => 'Exception', 'exception' => [
'value' => $error_message, 'type' => 'Exception',
'handled' => false 'value' => $error_message,
], 'handled' => false
'user' => [ ],
'id' => $user_id, 'user' => [
'ip_address' => $_SERVER['REMOTE_ADDR'] 'id' => $user_id,
] 'ip_address' => $_SERVER['REMOTE_ADDR']
]; ]
];
exec( exec(
'curl ' . 'curl ' .
'-H "Content-Type: application/json" ' . '-H "Content-Type: application/json" ' .
'-H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=' . $this->setting->get('sentry_key') . '" ' . '-H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=' . $this->setting->get('sentry_key') . '" ' .
'--silent ' . // silent; keeps logs out of stderr '--silent ' . // silent; keeps logs out of stderr
'--show-error ' . // override silent on failure '--show-error ' . // override silent on failure
'--max-time 10 ' . '--max-time 10 ' .
'--connect-timeout 5 ' . '--connect-timeout 5 ' .
'--data \'' . json_encode($data) . '\' ' . '--data \'' . json_encode($data) . '\' ' .
'"https://sentry.io/api/' . $this->setting->get('sentry_project_id') . '/store/" > /dev/null &' '"https://sentry.io/api/' . $this->setting->get('sentry_project_id') . '/store/" > /dev/null &'
); );
}
} }
/** /**
@ -779,7 +787,8 @@ final class cora {
$error['type'], $error['type'],
$error['file'], $error['file'],
$error['line'], $error['line'],
debug_backtrace(false) debug_backtrace(false),
true
); );
} }
@ -824,7 +833,8 @@ final class cora {
$e->getCode(), $e->getCode(),
$e->getFile(), $e->getFile(),
$e->getLine(), $e->getLine(),
$e->getTrace() $e->getTrace(),
(method_exists($e, 'getReportable') === true ? $e->getReportable() : true)
); );
$this->set_default_headers(); $this->set_default_headers();
$this->output_headers(); $this->output_headers();

View File

@ -695,18 +695,21 @@ final class database extends \mysqli {
} }
/** /**
* Actually delete a row from a table by the primary key. * Set deleted = 1 on the database row.
* *
* @param string $table The table to delete from. * @param string $resource The table to delete from.
* @param int $id The value of the primary key to delete. * @param int $id The value of the primary key to delete.
* *
* @return int The number of rows affected by the delete (could be 0). * @return int The number of rows affected by the delete (could be 0).
*/ */
public function delete($table, $id) { public function delete($resource, $id) {
$query = 'delete from ' . $this->escape_identifier($table) . $table = $this->get_table($resource);
' where ' . $this->escape_identifier($table . '_id') . ' = ' .
$this->escape($id); $attributes = [];
$this->query($query); $attributes[$table . '_id'] = $id;
$attributes['deleted'] = true;
$this->update($resource, $attributes);
return $this->affected_rows; return $this->affected_rows;
} }

25
api/cora/exception.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace cora;
/**
* Custom exception class. Requires message, code, and replaces $previous with
* $reportable to indicate if the exception should be reported to a logging
* service or not.
*
* The class name was made lowercase to simplify autoincludes, but the
* interface was otherwise left alone because I still need to support catching
* regular exceptions.
*
* @author Jon Ziebell
*/
final class exception extends \Exception {
public function __construct($message, $code, $reportable = true) {
$this->reportable = $reportable;
return parent::__construct($message, $code, null);
}
public function getReportable() {
return $this->reportable;
}
}

View File

@ -225,13 +225,7 @@ final class session {
$sessions = $database->read('cora\session', ['session_key' => $session_key]); $sessions = $database->read('cora\session', ['session_key' => $session_key]);
if(count($sessions) === 1) { if(count($sessions) === 1) {
$database->update( $database->delete('cora\session', $sessions[0]['session_id']);
'cora\session',
[
'session_id' => $sessions[0]['session_id'],
'deleted' => 1
]
);
// Remove these if the current session got logged out. // Remove these if the current session got logged out.
if($session_key === $this->session_key) { if($session_key === $this->session_key) {
$this->session_key = null; $this->session_key = null;

View File

@ -187,7 +187,8 @@ class ecobee extends external_api {
[] []
); );
if(count($ecobee_tokens) !== 1) { if(count($ecobee_tokens) !== 1) {
throw new Exception('No token for this user'); $this->api('user', 'log_out', ['all' => true]);
throw new cora\exception('No ecobee access for this user.', 10501, false);
} }
$ecobee_token = $ecobee_tokens[0]; $ecobee_token = $ecobee_tokens[0];
} }
@ -236,6 +237,15 @@ class ecobee extends external_api {
throw new Exception($response['status']['message']); throw new Exception($response['status']['message']);
} }
} }
else if (isset($response['status']) === true && $response['status']['code'] === 16) {
// Token has been deauthorized by user. You must re-request authorization.
if($this::$log_mysql !== 'all') {
$this->log_mysql($curl_response);
}
$this->api('ecobee_token', 'delete', $ecobee_token['ecobee_token_id']);
$this->api('user', 'log_out', ['all' => true]);
throw new cora\exception('Ecobee access was revoked by user.', 10500, false);
}
else if (isset($response['status']) === true && $response['status']['code'] !== 0) { else if (isset($response['status']) === true && $response['status']['code'] !== 0) {
// Any other error // Any other error
if($this::$log_mysql !== 'all') { if($this::$log_mysql !== 'all') {

View File

@ -68,7 +68,8 @@ class ecobee_token extends cora\crud {
$ecobee_tokens = $database->read( $ecobee_tokens = $database->read(
'ecobee_token', 'ecobee_token',
[ [
'user_id' => $this->session->get_user_id() 'user_id' => $this->session->get_user_id(),
'deleted' => false
] ]
); );
if(count($ecobee_tokens) === 0) { if(count($ecobee_tokens) === 0) {
@ -94,6 +95,7 @@ class ecobee_token extends cora\crud {
isset($response['refresh_token']) === false isset($response['refresh_token']) === false
) { ) {
$this->delete($ecobee_token['ecobee_token_id']); $this->delete($ecobee_token['ecobee_token_id']);
$this->api('user', 'log_out', ['all' => true]);
$database->release_lock($lock_name); $database->release_lock($lock_name);
throw new Exception('Could not refresh ecobee token; ecobee returned no token.', 10002); throw new Exception('Could not refresh ecobee token; ecobee returned no token.', 10002);
} }
@ -125,10 +127,7 @@ class ecobee_token extends cora\crud {
$database = cora\database::get_transactionless_instance(); $database = cora\database::get_transactionless_instance();
// Need to delete the token before logging out or else the delete fails. // Need to delete the token before logging out or else the delete fails.
$return = $database->delete('ecobee_token', $id); $return = $database->delete($this->resource, $id);
// Log out
$this->api('user', 'log_out', ['all' => true]);
return $return; return $return;
} }

View File

@ -74,11 +74,11 @@ class patreon_token extends cora\crud {
$lock_name = 'patreon_token->refresh(' . $this->session->get_user_id() . ')'; $lock_name = 'patreon_token->refresh(' . $this->session->get_user_id() . ')';
$database->get_lock($lock_name, 3); $database->get_lock($lock_name, 3);
// $patreon_tokens = $this->read();
$patreon_tokens = $database->read( $patreon_tokens = $database->read(
'patreon_token', 'patreon_token',
[ [
'user_id' => $this->session->get_user_id() 'user_id' => $this->session->get_user_id(),
'deleted' => false
] ]
); );
if(count($patreon_tokens) === 0) { if(count($patreon_tokens) === 0) {
@ -130,7 +130,7 @@ class patreon_token extends cora\crud {
*/ */
public function delete($id) { public function delete($id) {
$database = cora\database::get_transactionless_instance(); $database = cora\database::get_transactionless_instance();
$return = $database->delete('patreon_token', $id); $return = $database->delete($this->resource, $id);
return $return; return $return;
} }

View File

@ -144,7 +144,9 @@ class user extends cora\crud {
} }
if($all === true) { if($all === true) {
$database = cora\database::get_instance(); // Sometimes I need to log out and then throw an exception. Using the
// transactionless instance makes sure that actually works.
$database = cora\database::get_transactionless_instance();
$sessions = $database->read( $sessions = $database->read(
'cora\session', 'cora\session',
[ [
@ -154,7 +156,7 @@ class user extends cora\crud {
); );
$success = true; $success = true;
foreach($sessions as $session) { foreach($sessions as $session) {
$success &= $this->session->delete($session['session_key']); $success &= $database->delete('cora\session', $session['session_id']);
} }
return $success; return $success;
} }

View File

@ -46,10 +46,6 @@ beestat.api.prototype.send = function(opt_api_call) {
self.load_(this.responseText); self.load_(this.responseText);
}); });
// var endpoint = (window.environment === 'live')
// ? 'https://api.beestat.io/'
// : 'http://' + window.environment + '.api.beestat.io/';
// this.xhr_.open('POST', endpoint + '?' + query_string);
this.xhr_.open('POST', 'api/?' + query_string); this.xhr_.open('POST', 'api/?' + query_string);
this.xhr_.send(); this.xhr_.send();
} else { } else {
@ -172,10 +168,12 @@ beestat.api.prototype.load_ = function(response_text) {
if ( if (
response.data && response.data &&
( (
response.data.error_code === 1004 || // Session is expired. response.data.error_code === 1004 || // Session is expired.
response.data.error_code === 10001 || // Could not get first token. response.data.error_code === 10000 || // Could not get first token.
response.data.error_code === 10002 || // Could not refresh ecobee token; no token found. response.data.error_code === 10001 || // Could not refresh ecobee token; no token found.
response.data.error_code === 10003 // Could not refresh ecobee token; ecobee returned no token. response.data.error_code === 10002 || // Could not refresh ecobee token; ecobee returned no token.
response.data.error_code === 10500 || // Ecobee access was revoked by user.
response.data.error_code === 10501 // No ecobee access for this user.
) )
) { ) {
window.location.href = '/'; window.location.href = '/';