diff --git a/action.php b/action.php index c8ec748..ae0b1a7 100644 --- a/action.php +++ b/action.php @@ -32,7 +32,7 @@ function returnToSender($msg, $arg = "") { switch ($VARS['action']) { case "signout": session_destroy(); - header('Location: index.php'); + header('Location: index.php?logout=1'); die("Logged out."); case "savenote": if (!isset($VARS['content']) || empty($VARS['noteid'])) { @@ -81,7 +81,7 @@ switch ($VARS['action']) { echo $note->getHTML(false); break; case "odt": - if (PANDOC_BIN != "") { + if ($SETTINGS['pandoc'] != "") { header("Content-Type: application/vnd.oasis.opendocument.text"); header("Content-disposition: attachment; filename=\"" . $note->getCleanTitle() . "_" . $note->getModified() . ".odt\""); $pandoc = new Pandoc\Pandoc(); diff --git a/api.php b/api.php index 03178ea..d68b923 100644 --- a/api.php +++ b/api.php @@ -4,35 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/** - * Simple JSON API to allow other apps to access data from this app. - * - * Requests can be sent via either GET or POST requests. POST is recommended - * as it has a lower chance of being logged on the server, exposing unencrypted - * user passwords. - */ -require __DIR__ . '/required.php'; -header("Content-Type: application/json"); -$username = $VARS['username']; -$password = $VARS['password']; -$user = User::byUsername($username); -if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) { - header("HTTP/1.1 403 Unauthorized"); - die("\"403 Unauthorized\""); -} - -// query max results -$max = 20; -if (preg_match("/^[0-9]+$/", $VARS['max']) === 1 && $VARS['max'] <= 1000) { - $max = (int) $VARS['max']; -} - -switch ($VARS['action']) { - case "ping": - $out = ["status" => "OK", "maxresults" => $max, "pong" => true]; - exit(json_encode($out)); - default: - header("HTTP/1.1 400 Bad Request"); - die("\"400 Bad Request\""); -} \ No newline at end of file +// Load in new API from legacy location (a.k.a. here) +require __DIR__ . "/api/index.php"; \ No newline at end of file diff --git a/api/.htaccess b/api/.htaccess new file mode 100644 index 0000000..9a4efe4 --- /dev/null +++ b/api/.htaccess @@ -0,0 +1,5 @@ +# Rewrite for Nextcloud Notes API + + RewriteEngine on + RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT] + \ No newline at end of file diff --git a/api/actions/ping.php b/api/actions/ping.php new file mode 100644 index 0000000..c764967 --- /dev/null +++ b/api/actions/ping.php @@ -0,0 +1,9 @@ + [ + "load" => "ping.php", + "vars" => [ + ] + ] +]; diff --git a/api/functions.php b/api/functions.php new file mode 100644 index 0000000..78e84c1 --- /dev/null +++ b/api/functions.php @@ -0,0 +1,123 @@ + 5) { + for ($i = 2; $i < strlen($key) - 2; $i++) { + $resp[$i] = "*"; + } + } + return $resp; +} + +/** + * Check if the request is allowed + * @global type $VARS + * @global type $database + * @return bool true if the request should continue, false if the request is bad + */ +function authenticate(): bool { + global $VARS, $database; + if (empty($VARS['key'])) { + return false; + } else { + $key = $VARS['key']; + if ($database->has('apikeys', ['key' => $key]) !== TRUE) { + engageRateLimit(); + http_response_code(403); + Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key); + return false; + } + } + return true; +} + +function checkVars($vars, $or = false) { + global $VARS; + $ok = []; + foreach ($vars as $key => $val) { + if (strpos($key, "OR") === 0) { + checkVars($vars[$key], true); + continue; + } + + // Only check type of optional variables if they're set, and don't + // mark them as bad if they're not set + if (strpos($key, " (optional)") !== false) { + $key = str_replace(" (optional)", "", $key); + if (empty($VARS[$key])) { + continue; + } + } else { + if (empty($VARS[$key])) { + $ok[$key] = false; + continue; + } + } + $checkmethod = "is_$val"; + if ($checkmethod($VARS[$key]) !== true) { + $ok[$key] = false; + } else { + $ok[$key] = true; + } + } + if ($or) { + $success = false; + $bad = ""; + foreach ($ok as $k => $v) { + if ($v) { + $success = true; + break; + } else { + $bad = $k; + } + } + if (!$success) { + http_response_code(400); + die("400 Bad request: variable $bad is missing or invalid"); + } + } else { + foreach ($ok as $key => $bool) { + if (!$bool) { + http_response_code(400); + die("400 Bad request: variable $key is missing or invalid"); + } + } + } +} diff --git a/api/index.php b/api/index.php new file mode 100644 index 0000000..59d0c2a --- /dev/null +++ b/api/index.php @@ -0,0 +1,77 @@ + 1) { + $VARS["action"] = $route[0]; + } + if (count($route) >= 2 && strpos($route[1], "?") !== 0) { + $VARS["key"] = $route[1]; + + for ($i = 2; $i < count($route); $i++) { + $key = explode("=", $route[$i], 2)[0]; + $val = explode("=", $route[$i], 2)[1]; + $VARS[$key] = $val; + } + } + + if (strpos($route[count($route) - 1], "?") === 0) { + $morevars = explode("&", substr($route[count($route) - 1], 1)); + foreach ($morevars as $var) { + $key = explode("=", $var, 2)[0]; + $val = explode("=", $var, 2)[1]; + $VARS[$key] = $val; + } + } +} + +if (!authenticate()) { + http_response_code(403); + die("403 Unauthorized"); +} + +if (empty($VARS['action'])) { + http_response_code(404); + die("404 No action specified"); +} + +if (!isset($APIS[$VARS['action']])) { + http_response_code(404); + die("404 Action not defined"); +} + +$APIACTION = $APIS[$VARS["action"]]; + +if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) { + http_response_code(404); + die("404 Action not found"); +} + +if (!empty($APIACTION["vars"])) { + checkVars($APIACTION["vars"]); +} + +require_once __DIR__ . "/actions/" . $APIACTION["load"]; diff --git a/app.php b/app.php index c0ad3e0..fa55ca5 100644 --- a/app.php +++ b/app.php @@ -39,7 +39,7 @@ header("Link: ; rel=preload; as=script", fals - + @@ -127,7 +127,7 @@ END; - + @@ -163,7 +163,7 @@ END; - + @@ -183,8 +183,8 @@ END; ?> diff --git a/cron.php b/cron.php index 101ef2f..92b936a 100644 --- a/cron.php +++ b/cron.php @@ -13,20 +13,20 @@ $libs = glob(__DIR__ . "/lib/*.lib.php"); foreach ($libs as $lib) { require_once $lib; } -$Strings = new Strings(LANGUAGE); -date_default_timezone_set(TIMEZONE); +$Strings = new Strings($SETTINGS['language']); +date_default_timezone_set($SETTINGS['timezone']); use Medoo\Medoo; $database; try { $database = new Medoo([ - 'database_type' => DB_TYPE, - 'database_name' => DB_NAME, - 'server' => DB_SERVER, - 'username' => DB_USER, - 'password' => DB_PASS, - 'charset' => DB_CHARSET + 'database_type' => $SETTINGS['database']['type'], + 'database_name' => $SETTINGS['database']['name'], + 'server' => $SETTINGS['database']['server'], + 'username' => $SETTINGS['database']['user'], + 'password' => $SETTINGS['database']['password'], + 'charset' => $SETTINGS['database']['charset'] ]); } catch (Exception $ex) { echo "Database error: $ex\n"; @@ -51,7 +51,7 @@ foreach ($reminders as $r) { $lines = explode("\n", $note->getText(), $linelimit); if (count($lines) == $linelimit) { array_pop($lines); - $lines[] = $Strings->build("Open {app} to read more", ["app" => SITE_TITLE], false); + $lines[] = $Strings->build("Open {app} to read more", ["app" => $SETTINGS['site_title']], false); } $content = implode("\n", $lines); Notifications::add($note->getOwner(), $Strings->get("Note Reminder", false), $content); diff --git a/index.php b/index.php index 1f8f76f..e4cafd8 100644 --- a/index.php +++ b/index.php @@ -1,175 +1,115 @@ get("no access permission", false); -} +if (!empty($_GET['logout'])) { + // Show a logout message instead of immediately redirecting to login flow + ?> + + + + -/* Authenticate user */ -$userpass_ok = false; -$multiauth = false; -if (Login::checkLoginServer()) { - if (empty($VARS['progress'])) { - // Easy way to remove "undefined" warnings. - } else if ($VARS['progress'] == "1") { - if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) { - $autherror = ""; - $user = User::byUsername($VARS['username']); - if ($user->exists()) { - $status = $user->getStatus()->getString(); - switch ($status) { - case "LOCKED_OR_DISABLED": - $alert = $Strings->get("account locked", false); - break; - case "TERMINATED": - $alert = $Strings->get("account terminated", false); - break; - case "CHANGE_PASSWORD": - $alert = $Strings->get("password expired", false); - break; - case "NORMAL": - $username_ok = true; - break; - case "ALERT_ON_ACCESS": - $mail_resp = $user->sendAlertEmail(); - if (DEBUG) { - var_dump($mail_resp); - } - $username_ok = true; - break; - default: - if (!empty($error)) { - $alert = $error; - } else { - $alert = $Strings->get("login error", false); - } - break; - } - if ($username_ok) { - if ($user->checkPassword($VARS['password'])) { - $_SESSION['passok'] = true; // stop logins using only username and authcode - if ($user->has2fa()) { - $multiauth = true; - } else { - Session::start($user); - header('Location: app.php'); - die("Logged in, go to app.php"); - } - } else { - $alert = $Strings->get("login incorrect", false); - } - } - } else { // User does not exist anywhere - $alert = $Strings->get("login incorrect", false); - } - } else { - $alert = $Strings->get("captcha error", false); + + + + + + + + + - - + + - - - - - get("sign in"); ?> - - - - - - - " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /> - " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> - - - - - - - - get("2fa prompt"); ?> - - " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /> - - - - - get("continue"); ?> - - + + + get("You have been logged out.") ?> + + + + + + get("Log in again"); ?> + - - - -