diff --git a/LICENSE.md b/LICENSE.md
index 63a11e3..56351c0 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,19 +1,7 @@
-Copyright (c) 2018 Netsyms Technologies.
+Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved.
-If you modify and redistribute this project, you must replace the branding
-assets with your own.
-
-The branding assets include:
- * the application icon
- * the Netsyms N punchcard logo
- * the Netsyms for Business graph logo
-
-If you are unsure if your usage is allowed, please contact us:
-https://netsyms.com/contact
-legal@netsyms.com
-
-All other portions of this application,
-unless otherwise noted (in comments, headers, etc), are licensed as follows:
+Licensed under the Mozilla Public License Version 2.0. Files without MPL header
+comments, including third party code, may be under a different license.
Mozilla Public License Version 2.0
==================================
diff --git a/README.md b/README.md
index 3ca9a62..eb517fd 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,4 @@ NickelBox
=========
NickelBox is a point of sale app. It integrates with BinStack for inventory
-management.
\ No newline at end of file
+management.
diff --git a/action.php b/action.php
index a71659e..15bb8a6 100644
--- a/action.php
+++ b/action.php
@@ -21,11 +21,11 @@ if ($VARS['action'] !== "signout") {
*/
function returnToSender($msg, $arg = "") {
global $VARS;
- if ($arg == "") {
- header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=" . $msg);
- } else {
- header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg&arg=$arg");
+ $header = "Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg";
+ if ($arg != "") {
+ $header .= "&arg=$arg";
}
+ header($header);
die();
}
@@ -35,7 +35,7 @@ switch ($VARS['action']) {
$error = null;
$oktx = null;
$database->action(function ($database) {
- global $VARS, $binstack, $error, $oktx;
+ global $VARS, $binstack, $Strings, $error, $oktx;
if (empty($VARS['items'])) {
$error = $Strings->get("no items", false);
@@ -215,7 +215,7 @@ switch ($VARS['action']) {
$error = null;
$oktx = null;
$database->action(function ($database) {
- global $VARS, $binstack, $error, $oktx;
+ global $VARS, $binstack, $Strings, $error, $oktx;
$items = $VARS['items'];
$payments = $VARS['payments'];
@@ -438,7 +438,7 @@ switch ($VARS['action']) {
exit(json_encode(["status" => "OK", "transactions" => $transactions]));
case "itemsearch":
header("Content-Type: application/json");
- if (!is_empty($VARS['q'])) {
+ if (!empty($VARS['q'])) {
$where["AND"]["OR"] = [
"name[~]" => $VARS['q'],
"code1[~]" => $VARS['q'],
@@ -499,7 +499,7 @@ switch ($VARS['action']) {
exit(json_encode(["status" => "OK", "items" => $items]));
case "customersearch":
header("Content-Type: application/json");
- if (!is_empty($VARS['q'])) {
+ if (!empty($VARS['q'])) {
$where["AND"]["OR"] = [
"customerid" => $VARS['q'],
"name[~]" => $VARS['q'],
@@ -533,7 +533,7 @@ switch ($VARS['action']) {
break;
case "editcustomer":
$insert = true;
- if (is_empty($VARS['id'])) {
+ if (empty($VARS['id'])) {
$insert = true;
} else {
if ($database->has('customers', ['customerid' => $VARS['id']])) {
@@ -542,7 +542,7 @@ switch ($VARS['action']) {
returnToSender("invalid_customerid");
}
}
- if (is_empty($VARS['name'])) {
+ if (empty($VARS['name'])) {
returnToSender('invalid_parameters');
}
@@ -673,7 +673,7 @@ switch ($VARS['action']) {
returnToSender("invalid_parameters");
}
}
- if (is_empty($VARS['name'])) {
+ if (empty($VARS['name'])) {
returnToSender('invalid_parameters');
}
@@ -742,7 +742,7 @@ switch ($VARS['action']) {
}
}
- if ($insert && (is_empty($code) || $database->has('certificates', ['certcode' => $code]))) {
+ if ($insert && (empty($code) || $database->has('certificates', ['certcode' => $code]))) {
do {
$code = random_int(100000000000, 999999999999);
} while ($database->has('certificates', ['certcode' => $code]));
@@ -775,6 +775,6 @@ switch ($VARS['action']) {
exit(json_encode(["status" => "OK"]));
case "signout":
session_destroy();
- header('Location: index.php');
+ header('Location: index.php?logout=1');
die("Logged out.");
}
\ No newline at end of file
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..9357a53
--- /dev/null
+++ b/api/functions.php
@@ -0,0 +1,149 @@
+ 5) {
+ for ($i = 2; $i < strlen($key) - 2; $i++) {
+ $resp[$i] = "*";
+ }
+ }
+ return $resp;
+}
+
+/**
+ * Check if the request is allowed
+ * @global array $VARS
+ * @return bool true if the request should continue, false if the request is bad
+ */
+function authenticate(): bool {
+ global $VARS, $SETTINGS;
+ // HTTP basic auth
+ if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
+ $username = $_SERVER['PHP_AUTH_USER'];
+ $password = $_SERVER['PHP_AUTH_PW'];
+ } else if (!empty($VARS['username']) && !empty($VARS['password'])) {
+ $username = $VARS['username'];
+ $password = $VARS['password'];
+ } else {
+ return false;
+ }
+ $user = User::byUsername($username);
+ if (!$user->exists()) {
+ return false;
+ }
+ if ($user->checkPassword($password, true)) {
+ // Check that the user has permission to access the app
+ $perms = is_array($SETTINGS['api_permissions']) ? $SETTINGS['api_permissions'] : $SETTINGS['permissions'];
+ foreach ($perms as $perm) {
+ if (!$user->hasPermission($perm)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Get the User whose credentials were used to make the request.
+ */
+function getRequestUser(): User {
+ global $VARS;
+ if (!empty($_SERVER['PHP_AUTH_USER'])) {
+ return User::byUsername($_SERVER['PHP_AUTH_USER']);
+ } else {
+ return User::byUsername($VARS['username']);
+ }
+}
+
+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;
+ }
+ }
+
+ if (strpos($val, "/") === 0) {
+ // regex
+ $ok[$key] = preg_match($val, $VARS[$key]) === 1;
+ } else {
+ $checkmethod = "is_$val";
+ $ok[$key] = !($checkmethod($VARS[$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..23cb28c
--- /dev/null
+++ b/api/index.php
@@ -0,0 +1,81 @@
+= 1) {
+ $VARS["action"] = $route[0];
+ }
+ if (count($route) >= 2 && strpos($route[1], "?") !== 0) {
+ for ($i = 1; $i < count($route); $i++) {
+ if (empty($route[$i]) || strpos($route[$i], "=") === false) {
+ continue;
+ }
+ $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()) {
+ header('WWW-Authenticate: Basic realm="' . $SETTINGS['site_title'] . '"');
+ header('HTTP/1.1 401 Unauthorized');
+ die("401 Unauthorized: you need to supply valid credentials.");
+}
+
+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 51826fe..9fc2388 100644
--- a/app.php
+++ b/app.php
@@ -1,5 +1,4 @@
; rel=preload; as=script", fals
-
+
@@ -66,28 +65,35 @@ header("Link: ; rel=preload; as=script", fals
get(MESSAGES[$_GET['msg']]['string'], false);
+ if (!empty($_GET['msg'])) {
+ if (array_key_exists($_GET['msg'], MESSAGES)) {
+ // optional string generation argument
+ if (empty($_GET['arg'])) {
+ $alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
+ } else {
+ $alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
+ }
+ $alerttype = MESSAGES[$_GET['msg']]['type'];
+ $alerticon = "square-o";
+ switch (MESSAGES[$_GET['msg']]['type']) {
+ case "danger":
+ $alerticon = "times";
+ break;
+ case "warning":
+ $alerticon = "exclamation-triangle";
+ break;
+ case "info":
+ $alerticon = "info-circle";
+ break;
+ case "success":
+ $alerticon = "check";
+ break;
+ }
} else {
- $alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
- }
- $alerttype = MESSAGES[$_GET['msg']]['type'];
- $alerticon = "square-o";
- switch (MESSAGES[$_GET['msg']]['type']) {
- case "danger":
- $alerticon = "times";
- break;
- case "warning":
- $alerticon = "exclamation-triangle";
- break;
- case "info":
- $alerticon = "info-circle";
- break;
- case "success":
- $alerticon = "check";
- break;
+ // We don't have a message for this, so just assume an error and escape stuff.
+ $alertmsg = htmlentities($Strings->get($_GET['msg'], false));
+ $alerticon = "times";
+ $alerttype = "danger";
}
echo <<
@@ -121,7 +127,7 @@ END;
-
+
@@ -157,7 +163,7 @@ END;
-
+
@@ -177,8 +183,8 @@ END;
?>
diff --git a/index.php b/index.php
index 3c992b3..1bdd70d 100644
--- a/index.php
+++ b/index.php
@@ -1,175 +1,131 @@
get("no access permission", false);
-}
+/**
+ * Show a simple HTML page with a line of text and a button. Matches the UI of
+ * the AccountHub login flow.
+ *
+ * @global type $SETTINGS
+ * @global type $SECURE_NONCE
+ * @global type $Strings
+ * @param string $title Text to show, passed through i18n
+ * @param string $button Button text, passed through i18n
+ * @param string $url URL for the button
+ */
+function showHTML(string $title, string $button, string $url) {
+ global $SETTINGS, $SECURE_NONCE, $Strings;
+ ?>
+
+
+
+
-/* 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 (!is_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"); ?>
-
+
+
get($title); ?>
+
+
+
-
-
-
-
-