Compare commits

..

No commits in common. "master" and "v1.0.1" have entirely different histories.

173 changed files with 5867 additions and 16536 deletions

View File

@ -1,7 +1,19 @@
Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved.
Copyright (c) 2017 Netsyms Technologies.
Licensed under the Mozilla Public License Version 2.0. Files without MPL header
comments, including third party code, may be under a different license.
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:
Mozilla Public License Version 2.0
==================================

View File

@ -10,24 +10,19 @@ https://netsyms.biz/apps/accounthub
Installing
----------
0. Setup a LAMP server with PHP 7.2, including PHP extensions mbstring, zip, gd, and imagick
*We're working on a nice script to install it automatically, but until then...*
0. Setup a LAMP server with additional PHP extensions mbstring, zip, ldap, gd, imagick
1. Copy `settings.template.php` to `settings.php`
2. Import `database.sql` into your database server
3. Edit `settings.php` and fill in your DB info
4. Set the URL of the install
5. Setup "EXTERNAL_APPS" with specifics for your install.
6. Setup the email settings to receive alerts you configure later in ManagePanel
7. Run `composer install` (or `composer.phar install`) to install dependency libraries
8. Edit the database table `apikeys` and add some API keys for the other apps to use
9. From a web browser, visit `http://apps/url` (or whatever your setup is). If you did everything right, you should see a login screen.
10. Now go to `http://apps/url/setup.php` and create an admin account.
11. Install [ManagePanel](https://source.netsyms.com/Business/ManagePanel) to setup additional user accounts.
Upgrading
---------
1. Run `git pull` or otherwise update the code
2. Run `composer install` to update dependencies
3. Execute the SQL scripts in `database_upgrade` to take you from your current version to the latest version
4. Rewrite your `settings.php` based on the new `settings.template.php`
4. Setup LDAP auth, or set "LDAP_ENABLED" to FALSE
5. Set the URL of the install
6. Set the API and HOME values for TaskFloor, Inventory (BinStack), QwikClock, and in the "EXTERNAL_APPS" setting
7. Remove any apps you aren't installing from "EXTERNAL_APPS"
8. Setup the email settings to receive alerts you configure later in ManagePanel
9. Run `composer install` (or `composer.phar install`) to install dependency libraries
10. Edit the database table `apikeys` and add some API keys for the other apps to use
11. From a web browser, visit `http://apps/url` (or whatever your setup is). If you did everything right, you should see a login screen.
12. Now go to `http://apps/url/setup.php` and create an admin account.
13. Install [ManagePanel](https://source.netsyms.com/Business/ManagePanel) to setup additional user accounts.

View File

@ -21,100 +21,55 @@ if ($VARS['action'] == 'signout' && $_SESSION['loggedin'] != true) {
dieifnotloggedin();
engageRateLimit();
require_once __DIR__ . "/lib/login.php";
function returnToSender($msg, $arg = "") {
global $VARS;
$header = "Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg";
if ($arg != "") {
$header .= "&arg=$arg";
if ($arg == "") {
header("Location: home.php?page=" . urlencode($VARS['source']) . "&msg=$msg");
} else {
header("Location: home.php?page=" . urlencode($VARS['source']) . "&msg=$msg&arg=" . urlencode($arg));
}
header($header);
die();
}
switch ($VARS['action']) {
case "signout":
Log::insert(LogType::LOGOUT, $_SESSION['uid']);
insertAuthLog(11, $_SESSION['uid']);
session_destroy();
header('Location: index.php?logout=1');
header('Location: index.php');
die("Logged out.");
case "chpasswd":
engageRateLimit();
$error = [];
$user = new User($_SESSION['uid']);
try {
$result = $user->changePassword($VARS['oldpass'], $VARS['newpass'], $VARS['conpass']);
if ($result === TRUE) {
returnToSender("password_updated");
}
} catch (PasswordMatchException $e) {
returnToSender("passwords_same");
} catch (PasswordMismatchException $e) {
returnToSender("new_password_mismatch");
} catch (IncorrectPasswordException $e) {
returnToSender("old_password_mismatch");
} catch (WeakPasswordException $e) {
returnToSender("weak_password");
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
if ($result === TRUE) {
returnToSender("password_updated");
}
break;
case "chpin":
engageRateLimit();
$error = [];
if (!($VARS['newpin'] == "" || (is_numeric($VARS['newpin']) && strlen($VARS['newpin']) >= 1 && strlen($VARS['newpin']) <= 8))) {
returnToSender("invalid_pin_format");
switch (count($error)) {
case 1:
returnToSender($error[0]);
case 2:
returnToSender($error[0], $error[1]);
default:
returnToSender("generic_op_error");
}
if ($VARS['newpin'] == $VARS['conpin']) {
$database->update('accounts', ['pin' => ($VARS['newpin'] == "" ? null : $VARS['newpin'])], ['uid' => $_SESSION['uid']]);
returnToSender("pin_updated");
}
returnToSender("new_pin_mismatch");
break;
case "add2fa":
if (empty($VARS['secret'])) {
if (is_empty($VARS['secret'])) {
returnToSender("invalid_parameters");
}
$user = new User($_SESSION['uid']);
$totp = new TOTP(null, $VARS['secret']);
if (!$totp->verify($VARS["totpcode"])) {
returnToSender("2fa_wrong_code");
}
$user->save2fa($VARS['secret']);
Log::insert(LogType::ADDED_2FA, $user);
$database->update('accounts', ['authsecret' => $VARS['secret']], ['uid' => $_SESSION['uid']]);
insertAuthLog(9, $_SESSION['uid']);
returnToSender("2fa_enabled");
case "rm2fa":
engageRateLimit();
(new User($_SESSION['uid']))->save2fa("");
Log::insert(LogType::REMOVED_2FA, $_SESSION['uid']);
$database->update('accounts', ['authsecret' => ""], ['uid' => $_SESSION['uid']]);
insertAuthLog(10, $_SESSION['uid']);
returnToSender("2fa_removed");
break;
case "readnotification":
$user = new User($_SESSION['uid']);
if (empty($VARS['id'])) {
returnToSender("invalid_parameters#notifications");
}
try {
Notifications::read($user, $VARS['id']);
returnToSender("#notifications");
} catch (Exception $ex) {
returnToSender("invalid_parameters#notifications");
}
break;
case "deletenotification":
$user = new User($_SESSION['uid']);
if (empty($VARS['id'])) {
returnToSender("invalid_parameters#notifications");
}
try {
Notifications::delete($user, $VARS['id']);
returnToSender("notification_deleted#notifications");
} catch (Exception $ex) {
returnToSender("invalid_parameters#notifications");
}
break;
case "resetfeedkey":
$database->delete('userkeys', ['AND' => ['uid' => $_SESSION['uid'], 'typeid' => 1]]);
returnToSender("feed_key_reset");
break;
}

293
api.php
View File

@ -4,6 +4,295 @@
* 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 accounts in this system.
*
* 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';
require_once __DIR__ . '/lib/login.php';
header("Content-Type: application/json");
// Load in new API from legacy location (a.k.a. here)
require __DIR__ . "/api/index.php";
//try {
$key = $VARS['key'];
if ($database->has('apikeys', ['key' => $key]) !== TRUE) {
engageRateLimit();
http_response_code(403);
insertAuthLog(14, null, "Key: " . $key);
die("\"403 Unauthorized\"");
}
/**
* Get the API key with most of the characters replaced with *s.
* @global string $key
* @return string
*/
function getCensoredKey() {
global $key;
$resp = $key;
if (strlen($key) > 5) {
for ($i = 2; $i < strlen($key) - 2; $i++) {
$resp[$i] = "*";
}
}
return $resp;
}
switch ($VARS['action']) {
case "ping":
exit(json_encode(["status" => "OK"]));
break;
case "auth":
$errmsg = "";
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
insertAuthLog(12, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "OK", "msg" => lang("login successful", false)]));
} else {
insertAuthLog(13, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
if (!is_empty($errmsg)) {
exit(json_encode(["status" => "ERROR", "msg" => lang2("ldap error", ['error' => $errmsg], false)]));
}
if (user_exists($VARS['username'])) {
switch (get_account_status($VARS['username'])) {
case "LOCKED_OR_DISABLED":
exit(json_encode(["status" => "ERROR", "msg" => lang("account locked", false)]));
case "TERMINATED":
exit(json_encode(["status" => "ERROR", "msg" => lang("account terminated", false)]));
case "CHANGE_PASSWORD":
exit(json_encode(["status" => "ERROR", "msg" => lang("password expired", false)]));
case "NORMAL":
break;
default:
exit(json_encode(["status" => "ERROR", "msg" => lang("account state error", false)]));
}
}
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
}
break;
case "userinfo":
if (!is_empty($VARS['username'])) {
if (user_exists_local($VARS['username'])) {
$data = $database->select("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"]], ["username" => strtolower($VARS['username'])])[0];
exit(json_encode(["status" => "OK", "data" => $data]));
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
}
} else if (!is_empty($VARS['uid'])) {
if ($database->has('accounts', ['uid' => $VARS['uid']])) {
$data = $database->select("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"]], ["uid" => $VARS['uid']])[0];
exit(json_encode(["status" => "OK", "data" => $data]));
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
}
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
break;
case "userexists":
if (!is_empty($VARS['uid'])) {
if ($database->has('accounts', ['uid' => $VARS['uid']])) {
exit(json_encode(["status" => "OK", "exists" => true]));
} else {
exit(json_encode(["status" => "OK", "exists" => false]));
}
}
if (user_exists_local($VARS['username'])) {
exit(json_encode(["status" => "OK", "exists" => true]));
} else {
exit(json_encode(["status" => "OK", "exists" => false]));
}
break;
case "hastotp":
if (userHasTOTP($VARS['username'])) {
exit(json_encode(["status" => "OK", "otp" => true]));
} else {
exit(json_encode(["status" => "OK", "otp" => false]));
}
break;
case "verifytotp":
if (verifyTOTP($VARS['username'], $VARS['code'])) {
exit(json_encode(["status" => "OK", "valid" => true]));
} else {
insertAuthLog(7, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "ERROR", "msg" => lang("2fa incorrect", false), "valid" => false]));
}
break;
case "acctstatus":
exit(json_encode(["status" => "OK", "account" => get_account_status($VARS['username'])]));
case "login":
engageRateLimit();
// simulate a login, checking account status and alerts
$errmsg = "";
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
$uid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])])[0];
switch (get_account_status($VARS['username'])) {
case "LOCKED_OR_DISABLED":
insertAuthLog(5, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "ERROR", "msg" => lang("account locked", false)]));
case "TERMINATED":
insertAuthLog(5, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "ERROR", "msg" => lang("account terminated", false)]));
case "CHANGE_PASSWORD":
insertAuthLog(5, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "ERROR", "msg" => lang("password expired", false)]));
case "NORMAL":
insertAuthLog(4, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "OK"]));
case "ALERT_ON_ACCESS":
sendLoginAlertEmail($VARS['username']);
insertAuthLog(4, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "OK", "alert" => true]));
default:
insertAuthLog(5, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exit(json_encode(["status" => "ERROR", "msg" => lang("account state error", false)]));
}
} else {
insertAuthLog(5, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
if (!is_empty($errmsg)) {
exit(json_encode(["status" => "ERROR", "msg" => lang2("ldap error", ['error' => $errmsg], false)]));
}
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
}
break;
case "ismanagerof":
if ($VARS['uid'] === 1) {
if ($database->has("accounts", ['uid' => $VARS['manager']])) {
if ($database->has("accounts", ['uid' => $VARS['employee']])) {
$managerid = $VARS['manager'];
$employeeid = $VARS['employee'];
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['employee']]));
}
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['manager']]));
}
} else {
if (user_exists_local($VARS['manager'])) {
if (user_exists_local($VARS['employee'])) {
$managerid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['manager'])]);
$employeeid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['employee'])]);
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => strtolower($VARS['employee'])]));
}
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => strtolower($VARS['manager'])]));
}
}
if ($database->has('managers', ['AND' => ['managerid' => $managerid, 'employeeid' => $employeeid]])) {
exit(json_encode(["status" => "OK", "managerof" => true]));
} else {
exit(json_encode(["status" => "OK", "managerof" => false]));
}
break;
case "getmanaged":
if ($VARS['uid']) {
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
$managerid = $VARS['uid'];
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
}
} else if ($VARS['username']) {
if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) {
$managerid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])]);
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
}
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
if ($VARS['get'] == "username") {
$managed = $database->select('managers', ['[>]accounts' => ['employeeid' => 'uid']], 'username', ['managerid' => $managerid]);
} else {
$managed = $database->select('managers', 'employeeid', ['managerid' => $managerid]);
}
exit(json_encode(["status" => "OK", "employees" => $managed]));
break;
case "getmanagers":
if ($VARS['uid']) {
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
$empid = $VARS['uid'];
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
}
} else if ($VARS['username']) {
if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) {
$empid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])]);
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
}
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
$managers = $database->select('managers', 'managerid', ['employeeid' => $empid]);
exit(json_encode(["status" => "OK", "managers" => $managers]));
break;
case "usersearch":
if (is_empty($VARS['search']) || strlen($VARS['search']) < 3) {
exit(json_encode(["status" => "OK", "result" => []]));
}
$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]);
exit(json_encode(["status" => "OK", "result" => $data]));
break;
case "permission":
if (is_empty($VARS['code'])) {
http_response_code(400);
die("\"400 Bad Request\"");
}
$perm = $VARS['code'];
if ($VARS['uid']) {
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
$user = $database->select('accounts', ['username'], ['uid' => $VARS['uid']])[0]['username'];
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
}
} else if ($VARS['username']) {
if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) {
$user = $VARS['username'];
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)]));
}
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
$hasperm = account_has_permission($user, $perm);
exit(json_encode(["status" => "OK", "has_permission" => $hasperm]));
break;
case "mobileenabled":
exit(json_encode(["status" => "OK", "mobile" => MOBILE_ENABLED]));
case "mobilevalid":
if (is_empty($VARS['username']) || is_empty($VARS['code'])) {
http_response_code(400);
die("\"400 Bad Request\"");
}
$code = strtoupper($VARS['code']);
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $code, 'accounts.username' => strtolower($VARS['username'])]]);
exit(json_encode(["status" => "OK", "valid" => $user_key_valid]));
case "alertemail":
engageRateLimit();
if (is_empty($VARS['username']) || !user_exists($VARS['username'])) {
http_response_code(400);
die("\"400 Bad Request\"");
}
$appname = "???";
if (!is_empty($VARS['appname'])) {
$appname = $VARS['appname'];
}
$result = sendLoginAlertEmail($VARS['username'], $appname);
if ($result === TRUE) {
exit(json_encode(["status" => "OK"]));
}
exit(json_encode(["status" => "ERROR", "msg" => $result]));
default:
http_response_code(404);
die(json_encode("404 Not Found: the requested action is not available."));
}
/* } catch (Exception $e) {
header("HTTP/1.1 500 Internal Server Error");
die("\"500 Internal Server Error\"");
} */

View File

@ -1,5 +0,0 @@
# Rewrite for Nextcloud Notes API
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT]
</IfModule>

View File

@ -1,9 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
sendJsonResp(null, "OK", ["account" => User::byUsername($VARS['username'])->getStatus()->getString()]);

View File

@ -1,14 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
$desc = htmlspecialchars($VARS['desc']);
$chunk_code = str_replace(" ", "-", trim(chunk_split($code, 5, ' ')));
$database->insert('apppasswords', ['uid' => User::byUsername($VARS['username'])->getUID(), 'hash' => password_hash($chunk_code, PASSWORD_DEFAULT), 'description' => $desc]);
sendJsonResp("", "OK", ["pass" => $chunk_code]);

View File

@ -1,29 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
}
try {
$timestamp = "";
if (!empty($VARS['timestamp'])) {
$timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
}
$url = "";
if (!empty($VARS['url'])) {
$url = $VARS['url'];
}
$nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));
exitWithJson(["status" => "OK", "id" => $nid]);
} catch (Exception $ex) {
sendJsonResp($ex->getMessage(), "ERROR");
}

View File

@ -1,18 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
engageRateLimit();
$appname = "???";
if (!empty($VARS['appname'])) {
$appname = $VARS['appname'];
}
$result = User::byUsername($VARS['username'])->sendAlertEmail($appname);
if ($result === TRUE) {
sendJsonResp();
}
sendJsonResp($result, "ERROR");

View File

@ -1,39 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$user = User::byUsername($VARS['username']);
$ok = false;
if (empty($VARS['apppass']) && ($user->checkPassword($VARS['password']) || $user->checkAppPassword($VARS['password']))) {
$ok = true;
} else {
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
$ok = true;
}
}
if ($ok) {
Log::insert(LogType::API_AUTH_OK, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
sendJsonResp($Strings->get("login successful", false), "OK");
} else {
Log::insert(LogType::API_AUTH_FAILED, $user->getUID(), "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
if ($user->exists()) {
switch ($user->getStatus()->get()) {
case AccountStatus::LOCKED_OR_DISABLED:
sendJsonResp($Strings->get("account locked", false), "ERROR");
case AccountStatus::TERMINATED:
sendJsonResp($Strings->get("account terminated", false), "ERROR");
case AccountStatus::CHANGE_PASSWORD:
sendJsonResp($Strings->get("password expired", false), "ERROR");
case AccountStatus::NORMAL:
break;
default:
sendJsonResp($Strings->get("account state error", false), "ERROR");
}
}
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
}

View File

@ -1,15 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
try {
$uid = LoginKey::getuid($VARS['code']);
exitWithJson(["status" => "OK", "uid" => $uid]);
} catch (Exception $ex) {
sendJsonResp("", "ERROR");
}

View File

@ -1,24 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$pin = "";
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
}
if ($user->exists()) {
$pin = $database->get("accounts", "pin", ["uid" => $user->getUID()]);
} else {
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
}
if (is_null($pin) || $pin == "") {
exitWithJson(["status" => "ERROR", "pinvalid" => false, "nopinset" => true]);
}
exitWithJson(["status" => "OK", "pinvalid" => ($pin == $VARS['pin'])]);

View File

@ -1,15 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
if ($database->has("onetimekeys", ["key" => $VARS['code'], "expires[>]" => date("Y-m-d H:i:s")])) {
$user = $database->get("onetimekeys", ["[>]accounts" => ["uid" => "uid"]], ["username", "realname", "accounts.uid"], ["key" => $VARS['code']]);
exitWithJson(["status" => "OK", "user" => $user]);
} else {
sendJsonResp($Strings->get("no such code or code expired", false), "ERROR");
}

View File

@ -1,20 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
}
try {
Notifications::delete($user, $VARS['id']);
sendJsonResp();
} catch (Exception $ex) {
sendJsonResp($ex->getMessage(), "ERROR");
}

View File

@ -1,10 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$groups = $database->select('groups', ['groupid (id)', 'groupname (name)']);
exitWithJson(["status" => "OK", "groups" => $groups]);

View File

@ -1,23 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['uid'])) {
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
$empid = $VARS['uid'];
} else {
sendJsonResp($Strings->get("user does not exist", false), "ERROR");
}
} else if (!empty($VARS['username'])) {
if ($database->has("accounts", ['username' => strtolower($VARS['username'])])) {
$empid = $database->select('accounts', 'uid', ['username' => strtolower($VARS['username'])]);
} else {
sendJsonResp($Strings->get("user does not exist", false), "ERROR");
}
}
$groups = $database->select('assigned_groups', ["[>]groups" => ["groupid" => "groupid"]], ['groups.groupid (id)', 'groups.groupname (name)'], ['uid' => $empid]);
exitWithJson(["status" => "OK", "groups" => $groups]);

View File

@ -1,22 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$appicon = null;
if (!empty($VARS['appicon'])) {
$appicon = $VARS['appicon'];
}
$code = LoginKey::generate($VARS['appname'], $appicon);
if (strpos($SETTINGS['url'], "http") === 0) {
$url = $SETTINGS['url'] . "login/";
} else {
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url'] . "login/";
}
exitWithJson(["status" => "OK", "code" => $code, "loginurl" => $url]);

View File

@ -1,23 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['uid'])) {
$manager = new User($VARS['uid']);
} else if (!empty($VARS['username'])) {
$manager = User::byUsername($VARS['username']);
}
if (!$manager->exists()) {
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
}
if (!empty($VARS['get']) && $VARS['get'] == "username") {
$managed = $database->select('managers', ['[>]accounts' => ['employeeid' => 'uid']], 'username', ['managerid' => $manager->getUID()]);
} else {
$managed = $database->select('managers', 'employeeid', ['managerid' => $manager->getUID()]);
}
exitWithJson(["status" => "OK", "employees" => $managed]);

View File

@ -1,19 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['uid'])) {
$emp = new User($VARS['uid']);
} else if (!empty($VARS['username'])) {
$emp = User::byUsername($VARS['username']);
}
if (!$emp->exists()) {
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
}
$managers = $database->select('managers', 'managerid', ['employeeid' => $emp->getUID()]);
exitWithJson(["status" => "OK", "managers" => $managers]);

View File

@ -1,20 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
}
try {
$notifications = Notifications::get($user);
exitWithJson(["status" => "OK", "notifications" => $notifications]);
} catch (Exception $ex) {
sendJsonResp($ex->getMessage(), "ERROR");
}

View File

@ -1,29 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if ($database->has("groups", ['groupid' => $VARS['gid']])) {
$groupid = $VARS['gid'];
} else {
sendJsonResp($Strings->get("group does not exist", false), "ERROR");
}
if (!empty($VARS["get"]) && $VARS['get'] == "username") {
$users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], 'username', ['groupid' => $groupid, "ORDER" => "username"]);
} else if (!empty($VARS["get"]) && $VARS['get'] == "detail") {
$users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], ['username', 'realname (name)', 'accounts.uid', 'pin'], ['groupid' => $groupid, "ORDER" => "realname"]);
for ($i = 0; $i < count($users); $i++) {
if (is_null($users[$i]['pin']) || $users[$i]['pin'] == "") {
$users[$i]['pin'] = false;
} else {
$users[$i]['pin'] = true;
}
}
} else {
$users = $database->select('assigned_groups', 'uid', ['groupid' => $groupid]);
}
exitWithJson(["status" => "OK", "users" => $users]);

View File

@ -1,13 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (strlen($VARS['search']) < 2) {
exitWithJson(["status" => "OK", "result" => []]);
}
$data = $database->select('groups', ['groupid (id)', 'groupname (name)'], ['groupname[~]' => $VARS['search'], "LIMIT" => 10]);
exitWithJson(["status" => "OK", "result" => $data]);

View File

@ -1,9 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
sendJsonResp(null, "OK", ["otp" => User::byUsername($VARS['username'])->has2fa()]);

View File

@ -1,27 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['uid']) && $VARS['uid'] == "1") {
$manager = new User($VARS['manager']);
$employee = new User($VARS['employee']);
} else {
$manager = User::byUsername($VARS['manager']);
$employee = User::byUsername($VARS['employee']);
}
if (!$manager->exists()) {
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['manager']]);
}
if (!$employee->exists()) {
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['employee']]);
}
if ($database->has('managers', ['AND' => ['managerid' => $manager->getUID(), 'employeeid' => $employee->getUID()]])) {
exitWithJson(["status" => "OK", "managerof" => true]);
} else {
exitWithJson(["status" => "OK", "managerof" => false]);
}

View File

@ -1,16 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$apps = $SETTINGS['apps'];
// Format paths as absolute URLs
foreach ($apps as $k => $v) {
if (strpos($apps[$k]['url'], "http") === FALSE) {
$apps[$k]['url'] = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443 ? ":" . $_SERVER['SERVER_PORT'] : "") . $apps[$k]['url'];
}
}
exitWithJson(["status" => "OK", "apps" => $apps]);

View File

@ -1,46 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
engageRateLimit();
$user = User::byUsername($VARS['username']);
$ok = false;
if (empty($VARS['apppass']) && ($user->checkPassword($VARS['password']) || $user->checkAppPassword($VARS['password']))) {
$ok = true;
} else {
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
$ok = true;
}
}
if ($ok) {
switch ($user->getStatus()->getString()) {
case "LOCKED_OR_DISABLED":
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account locked", false)]);
case "TERMINATED":
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)]);
case "CHANGE_PASSWORD":
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("password expired", false)]);
case "NORMAL":
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exitWithJson(["status" => "OK"]);
case "ALERT_ON_ACCESS":
$user->sendAlertEmail();
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exitWithJson(["status" => "OK", "alert" => true]);
default:
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("account state error", false)]);
}
} else {
Log::insert(LogType::API_LOGIN_FAILED, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]);
}

View File

@ -1,9 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
exitWithJson(["status" => "OK", "mobile" => $SETTINGS['mobile_enabled']]);

View File

@ -1,15 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (empty($VARS['username']) || empty($VARS['code'])) {
http_response_code(400);
die("\"400 Bad Request\"");
}
$code = strtoupper($VARS['code']);
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $code, 'accounts.username' => strtolower($VARS['username'])]]);
exitWithJson(["status" => "OK", "valid" => $user_key_valid]);

View File

@ -1,19 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$perm = $VARS['code'];
if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
}
if (!$user->exists()) {
exitWithJson(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]);
}
exitWithJson(["status" => "OK", "has_permission" => $user->hasPermission($perm)]);

View File

@ -1,9 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
sendJsonResp();

View File

@ -1,25 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
if (empty($VARS['id'])) {
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
}
try {
Notifications::read($user, $VARS['id']);
sendJsonResp();
} catch (Exception $ex) {
sendJsonResp($ex->getMessage(), "ERROR");
}

View File

@ -1,15 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
}
sendJsonResp(null, "OK", ["exists" => $user->exists()]);

View File

@ -1,20 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
}
if ($user->exists()) {
$data = $database->get("accounts", ["uid", "username", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"], 'pin'], ["uid" => $user->getUID()]);
$data['pin'] = (is_null($data['pin']) || $data['pin'] == "" ? false : true);
sendJsonResp(null, "OK", ["data" => $data]);
} else {
sendJsonResp($Strings->get("login incorrect", false), "ERROR");
}

View File

@ -1,13 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
if (strlen($VARS['search']) < 3) {
exitWithJson(["status" => "OK", "result" => []]);
}
$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['search'], 'realname[~]' => $VARS['search']], "LIMIT" => 10]);
exitWithJson(["status" => "OK", "result" => $data]);

View File

@ -1,15 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$user = User::byUsername($VARS['username']);
if ($user->check2fa($VARS['code'])) {
sendJsonResp(null, "OK", ["valid" => true]);
} else {
Log::insert(LogType::API_BAD_2FA, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
sendJsonResp($Strings->get("2fa incorrect", false), "ERROR", ["valid" => false]);
}

View File

@ -1,267 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$APIS = [
"ping" => [
"load" => "ping.php",
"vars" => [
],
"permission" => [
],
"keytype" => "NONE"
],
"auth" => [
"load" => "auth.php",
"vars" => [
"username" => "string",
"password" => "string",
"apppass (optional)" => "/[0-1]/"
],
"keytype" => "AUTH"
],
"userinfo" => [
"load" => "userinfo.php",
"vars" => [
"OR" => [
"username" => "string",
"uid" => "numeric"
]
],
"keytype" => "READ"
],
"userexists" => [
"load" => "userexists.php",
"vars" => [
"OR" => [
"username" => "string",
"uid" => "numeric"
]
],
"keytype" => "AUTH"
],
"hastotp" => [
"load" => "hastotp.php",
"vars" => [
"username" => "string"
],
"keytype" => "AUTH"
],
"verifytotp" => [
"load" => "verifytotp.php",
"vars" => [
"username" => "string",
"code" => "string"
],
"keytype" => "AUTH"
],
"acctstatus" => [
"load" => "acctstatus.php",
"vars" => [
"username" => "string"
],
"keytype" => "AUTH"
],
"login" => [
"load" => "login.php",
"vars" => [
"username" => "string",
"password" => "string",
"apppass (optional)" => "/[0-1]/"
],
"keytype" => "AUTH"
],
"ismanagerof" => [
"load" => "ismanagerof.php",
"vars" => [
"manager" => "string",
"employee" => "string",
"uid (optional)" => "numeric"
],
"keytype" => "READ"
],
"getmanaged" => [
"load" => "getmanaged.php",
"vars" => [
"OR" => [
"username" => "string",
"uid" => "numeric"
],
"get (optional)" => "string"
],
"keytype" => "READ"
],
"getmanagers" => [
"load" => "getmanagers.php",
"vars" => [
"OR" => [
"username" => "string",
"uid" => "numeric"
]
],
"keytype" => "READ"
],
"usersearch" => [
"load" => "usersearch.php",
"vars" => [
"search" => "string"
],
"keytype" => "READ"
],
"permission" => [
"load" => "permission.php",
"vars" => [
"OR" => [
"username" => "string",
"uid" => "numeric"
],
"code" => "string"
],
"keytype" => "READ"
],
"mobileenabled" => [
"load" => "mobileenabled.php",
"keytype" => "NONE"
],
"mobilevalid" => [
"load" => "mobilevalid.php",
"vars" => [
"username" => "string",
"code" => "string"
],
"keytype" => "AUTH"
],
"alertemail" => [
"load" => "alertemail.php",
"vars" => [
"username" => "string",
"appname (optional)" => "string"
],
"keytype" => "FULL"
],
"codelogin" => [
"load" => "codelogin.php",
"vars" => [
"code" => "string"
],
"keytype" => "AUTH"
],
"listapps" => [
"load" => "listapps.php",
"keytype" => "NONE"
],
"getusersbygroup" => [
"load" => "getusersbygroup.php",
"vars" => [
"gid" => "numeric",
"get (optional)" => "string"
],
"keytype" => "READ"
],
"getgroupsbyuser" => [
"load" => "getgroupsbyuser.php",
"vars" => [
"OR" => [
"uid" => "numeric",
"username" => "string"
]
],
"keytype" => "READ"
],
"getgroups" => [
"load" => "getgroups.php",
"keytype" => "READ"
],
"groupsearch" => [
"load" => "groupsearch.php",
"vars" => [
"search" => "string"
],
"keytype" => "READ"
],
"checkpin" => [
"load" => "checkpin.php",
"vars" => [
"pin" => "string",
"OR" => [
"uid" => "numeric",
"username" => "string"
]
],
"keytype" => "AUTH"
],
"getnotifications" => [
"load" => "getnotifications.php",
"vars" => [
"OR" => [
"uid" => "numeric",
"username" => "string"
]
],
"keytype" => "READ"
],
"readnotification" => [
"load" => "readnotification.php",
"vars" => [
"OR" => [
"uid" => "numeric",
"username" => "string"
],
"id" => "numeric"
],
"keytype" => "FULL"
],
"addnotification" => [
"load" => "addnotification.php",
"vars" => [
"OR" => [
"uid" => "numeric",
"username" => "string"
],
"title" => "string",
"content" => "string",
"timestamp (optional)" => "string",
"url (optional)" => "string",
"sensitive (optional)" => "string"
],
"keytype" => "FULL"
],
"deletenotification" => [
"load" => "deletenotification.php",
"vars" => [
"OR" => [
"uid" => "numeric",
"username" => "string"
],
"id" => "numeric"
],
"keytype" => "FULL"
],
"getloginkey" => [
"load" => "getloginkey.php",
"vars" => [
"appname" => "string",
"appicon (optional)" => "string"
],
"keytype" => "AUTH"
],
"checkloginkey" => [
"load" => "checkloginkey.php",
"vars" => [
"code" => "string"
],
"keytype" => "AUTH"
],
"addapppassword" => [
"load" => "addapppassword.php",
"vars" => [
"desc" => "string",
"username" => "string"
],
"keytype" => "FULL"
],
];

View File

@ -1,164 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
/**
* Build and send a simple JSON response.
* @param string $msg A message
* @param string $status "OK" or "ERROR"
* @param array $data More JSON data
*/
function sendJsonResp(string $msg = null, string $status = "OK", array $data = null) {
$resp = [];
if (!is_null($data)) {
$resp = $data;
}
if (!is_null($msg)) {
$resp["msg"] = $msg;
}
$resp["status"] = $status;
header("Content-Type: application/json");
exit(json_encode($resp));
}
function exitWithJson(array $json) {
header("Content-Type: application/json");
exit(json_encode($json));
}
/**
* Get the API key with most of the characters replaced with *s.
* @global string $key
* @return string
*/
function getCensoredKey() {
global $key;
$resp = $key;
if (strlen($key) > 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;
}
return false;
}
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");
}
}
}
}
/**
* Check if the client API key is allowed to access API functions that require the
* specified API key type.
* @global type $VARS
* @global type $database
* @param string $type The required key type: "NONE", "AUTH", "READ", or "FULL"
* @return bool
*/
function checkkeytype(string $type): bool {
global $VARS, $database;
if (empty($VARS['key'])) {
return false;
} else {
$key = $VARS['key'];
$keytype = $database->get('apikeys', 'type', ['key' => $key]);
$allowedtypes = [];
switch ($type) {
case "NONE":
$allowedtypes = ["NONE", "AUTH", "READ", "FULL"];
break;
case "AUTH":
$allowedtypes = ["AUTH", "READ", "FULL"];
break;
case "READ":
$allowedtypes = ["READ", "FULL"];
break;
case "FULL":
$allowedtypes = ["FULL"];
}
if (!in_array($type, $allowedtypes)) {
http_response_code(403);
Log::insert(LogType::API_BAD_KEY, null, "Key: " . $key);
return false;
}
}
return true;
}

View File

@ -1,88 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
require __DIR__ . '/../required.php';
require __DIR__ . '/functions.php';
require __DIR__ . '/apisettings.php';
header("Access-Control-Allow-Origin: *");
$VARS = $_GET;
if ($_SERVER['REQUEST_METHOD'] != "GET") {
$VARS = array_merge($VARS, $_POST);
}
$requestbody = file_get_contents('php://input');
$requestjson = json_decode($requestbody, TRUE);
if (json_last_error() == JSON_ERROR_NONE) {
$VARS = array_merge($VARS, $requestjson);
}
// If we're not using the old api.php file, allow more flexible requests
if (strpos($_SERVER['REQUEST_URI'], "/api.php") === FALSE) {
$route = explode("/", substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], "api/") + 4));
if (count($route) > 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"]);
}
// Assume we need full API access
if (empty($APIACTION["keytype"])) {
$APIACTION["keytype"] = "FULL";
}
if (!checkkeytype($APIACTION["keytype"])) {
die("403 Unauthorized");
}
require_once __DIR__ . "/actions/" . $APIACTION["load"];

222
app.php
View File

@ -1,222 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
require_once __DIR__ . "/required.php";
// If the SVG/JavaScript version of FontAwesome is needed
// Increases overhead by a notable amount
define("FONTAWESOME_USEJS", true);
if ($_SESSION['loggedin'] != true) {
header('Location: index.php');
die("Session expired. Log in again to continue.");
}
require_once __DIR__ . "/pages.php";
$pageid = "home";
if (!empty($_GET['page'])) {
$pg = strtolower($_GET['page']);
$pg = preg_replace('/[^0-9a-z_]/', "", $pg);
if (array_key_exists($pg, PAGES) && file_exists(__DIR__ . "/pages/" . $pg . ".php")) {
$pageid = $pg;
} else {
$pageid = "404";
}
}
header("Link: <static/img/logo.svg>; rel=preload; as=image", false);
header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
header("Link: <static/css/app.css>; rel=preload; as=style", false);
if (FONTAWESOME_USEJS) {
header("Link: <static/css/svg-with-js.min.css>; rel=preload; as=style", false);
header("Link: <static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
} else {
header("Link: <static/css/fontawesome-all.min.css>; rel=preload; as=style", false);
}
header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
header("Link: <static/js/bootstrap.bundle.min.js>; rel=preload; as=script", false);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo $SETTINGS['site_title']; ?></title>
<link rel="icon" href="static/img/logo.svg" type="image/svg+xml">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
<link href="static/css/app.css" rel="stylesheet">
<?php
if (FONTAWESOME_USEJS) {
?>
<link href="static/css/svg-with-js.min.css" rel="stylesheet">
<script nonce="<?php echo $SECURE_NONCE; ?>">
FontAwesomeConfig = {autoAddCss: false}
</script>
<script src="static/js/fontawesome-all.min.js"></script>
<?php
} else {
?>
<link href="static/css/fontawesome-all.min.css" rel="stylesheet">
<?php
}
?>
<?php
// custom page styles
if (isset(PAGES[$pageid]['styles'])) {
foreach (PAGES[$pageid]['styles'] as $style) {
echo "<link href=\"$style\" rel=\"stylesheet\">\n";
header("Link: <$style>; rel=preload; as=style", false);
}
}
?>
</head>
<body>
<?php
// Alert messages
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 {
// 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 <<<END
<div class="row justify-content-center" id="msg-alert-box">
<div class="col-11 col-sm-6 col-md-5 col-lg-4 col-xl-4">
<div class="alert alert-dismissible alert-$alerttype mt-2 p-0 border-0 shadow">
<div class="p-2 pl-3">
<button type="button" class="close">&times;</button>
<i class="fas fa-$alerticon"></i> $alertmsg
</div>
<div class="progress">
<div class="progress-bar bg-$alerttype w-0" id="msg-alert-timeout-bar"></div>
</div>
</div>
</div>
</div>
END;
}
?>
<?php
// Adjust as needed
$navbar_breakpoint = "md";
// For mobile app
echo "<script nonce=\"$SECURE_NONCE\">var navbar_breakpoint = \"$navbar_breakpoint\";</script>"
?>
<nav class="navbar navbar-expand-<?php echo $navbar_breakpoint; ?> navbar-light bg-orange fixed-top">
<button class="navbar-toggler my-0 py-0" type="button" data-toggle="collapse" data-target="#navbar-collapse" aria-controls="navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
<!--<i class="fas fa-bars"></i>-->
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand py-0 mr-auto" href="app.php">
<img src="static/img/logo.svg" alt="" class="d-none d-<?php echo $navbar_breakpoint; ?>-inline brand-img py-0" />
<?php echo $SETTINGS['site_title']; ?>
</a>
<div class="collapse navbar-collapse py-0" id="navbar-collapse">
<div class="navbar-nav mr-auto py-0">
<?php
$curpagefound = false;
foreach (PAGES as $id => $pg) {
if (isset($pg['navbar']) && $pg['navbar'] === TRUE) {
if ($pageid == $id) {
$curpagefound = true;
?>
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0 active">
<?php
} else {
?>
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
<?php
}
?>
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="app.php?page=<?php echo $id; ?>">
<?php
if (isset($pg['icon'])) {
?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
}
$Strings->get($pg['title']);
?>
</a>
</span>
<?php
}
}
?>
</div>
<div class="navbar-nav ml-auto py-0" id="navbar-right">
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="app.php">
<i class="fas fa-user fa-fw"></i><span>&nbsp;<?php echo $_SESSION['realname'] ?></span>
</a>
</span>
<span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="action.php?action=signout">
<i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php $Strings->get("sign out") ?></span>
</a>
</span>
</div>
</div>
</nav>
<div class="container" id="main-content">
<div>
<?php
include_once __DIR__ . '/pages/' . $pageid . ".php";
?>
</div>
<div class="footer">
<?php echo $SETTINGS['footer_text']; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo $SETTINGS['copyright']; ?>
</div>
</div>
<script src="static/js/jquery-3.3.1.min.js"></script>
<script src="static/js/bootstrap.bundle.min.js"></script>
<script src="static/js/app.js"></script>
<?php
// custom page scripts
if (isset(PAGES[$pageid]['scripts'])) {
foreach (PAGES[$pageid]['scripts'] as $script) {
echo "<script src=\"$script\"></script>\n";
header("Link: <$script>; rel=preload; as=script", false);
}
}
?>
</body>
</html>

13
apps/404_error.php Normal file
View File

@ -0,0 +1,13 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
$APPS["404_error"]["title"] = lang("404 error", false);
$APPS["404_error"]["icon"] = "times";
$APPS["404_error"]["type"] = "warning";
$APPS["404_error"]["content"] = "<h4>" . lang("page not found", false) . "</h4>";
?>

24
apps/account_security.php Normal file
View File

@ -0,0 +1,24 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
addMultiLangStrings(["en_us" => [
"manage account security" => "Manage account security",
"manage security description" => "Review security features or change your password."
]
]);
$APPS["account_security"]["i18n"] = TRUE;
$APPS["account_security"]["title"] = "account security";
$APPS["account_security"]["icon"] = "lock";
$APPS["account_security"]["type"] = "brown";
$content = "<p>"
. lang("manage security description", false)
. '</p> '
. '<a href="home.php?page=security" class="btn btn-primary btn-block">'
. lang("manage account security", false)
. '</a>';
$APPS["account_security"]["content"] = $content;
?>

21
apps/change_password.php Normal file
View File

@ -0,0 +1,21 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
$APPS["change_password"]["title"] = "Change Password";
$APPS["change_password"]["icon"] = "key";
$APPS["change_password"]["content"] = <<<CONTENTEND
<form action="action.php" method="POST">
<input type="password" class="form-control" name="oldpass" placeholder="Current password" />
<input type="password" class="form-control" name="newpass" placeholder="New password" />
<input type="password" class="form-control" name="conpass" placeholder="New password (again)" />
<input type="hidden" name="action" value="chpasswd" />
<input type="hidden" name="source" value="security" />
<br />
<button type="submit" class="btn btn-success btn-sm btn-block">Change Password</button>
</form>
CONTENTEND;

24
apps/inventory_link.php Normal file
View File

@ -0,0 +1,24 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
addMultiLangStrings(["en_us" => [
"inventory" => "Inventory",
"open inventory system" => "Open the inventory system"
]
]);
$APPS["inventory_link"]["i18n"] = TRUE;
$APPS["inventory_link"]["title"] = "inventory";
$APPS["inventory_link"]["icon"] = "cubes";
$APPS["inventory_link"]["type"] = "teal";
$content = "<p class='mobile-app-hide'>" . lang("open inventory system", false) . '</p><a href="' . INVENTORY_HOME . '" class="btn btn-primary btn-block mobile-app-hide">' . lang("open app", false) . ' &nbsp;<i class="fa fa-external-link-square"></i></a>';
$APPS["inventory_link"]["content"] = $content;
require_once __DIR__ . "/../lib/login.php";
if (account_has_permission($_SESSION['username'], "INV_VIEW") !== true) {
unset($APPS['inventory_link']);
}
?>

61
apps/qwikclock_inout.php Normal file
View File

@ -0,0 +1,61 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
require_once __DIR__ . "/../lib/login.php";
addMultiLangStrings(["en_us" => [
"qwikclock" => "QwikClock",
"punch in" => "Punch in",
"punch out" => "Punch out",
"permission denied" => "You do not have permission to do that."
]
]);
$APPS["qwikclock_inout"]["i18n"] = TRUE;
$APPS["qwikclock_inout"]["title"] = "qwikclock";
$APPS["qwikclock_inout"]["icon"] = "clock-o";
$APPS["qwikclock_inout"]["type"] = "blue";
$content = "";
use GuzzleHttp\Exception\ClientException;
if (!is_empty($_GET['qwikclock']) && ($_GET['qwikclock'] === "punchin" || $_GET['qwikclock'] === "punchout")) {
try {
$client = new GuzzleHttp\Client();
$response = $client->request('POST', QWIKCLOCK_API, ['form_params' => [
'action' => $_GET['qwikclock'],
'username' => $_SESSION['username'],
'password' => $_SESSION['password']
]]);
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
$content = "<div class=\"alert alert-success alert-dismissable\"><button type=\"button\" class=\"close\">&times;</button>" . $resp['msg'] . "</div>";
} else {
$content = "<div class=\"alert alert-danger alert-dismissable\"><button type=\"button\" class=\"close\">&times;</button>" . $resp['msg'] . "</div>";
}
} catch (ClientException $e) {
if ($e->getResponse()->getStatusCode() == 403) {
$content = "<div class=\"alert alert-danger alert-dismissable\"><button type=\"button\" class=\"close\">&times;</button>" . lang("permission denied", false) . "</div>";
}
} catch (Exception $e) {
$content = "<div class=\"alert alert-danger alert-dismissable\"><button type=\"button\" class=\"close\">&times;</button>" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
}
}
$lang_punchin = lang("punch in", false);
$lang_punchout = lang("punch out", false);
$content .= <<<END
<a href="home.php?&qwikclock=punchin" class="btn btn-block btn-success btn-lg"><i class="fa fa-play"></i> $lang_punchin</a>
<a href="home.php?qwikclock=punchout" class="btn btn-block btn-danger btn-lg"><i class="fa fa-stop"></i> $lang_punchout</a>
END;
$content .= '<br /><a href="' . QWIKCLOCK_HOME . '" class="btn btn-primary btn-block mobile-app-hide">' . lang("open app", false) . ' &nbsp;<i class="fa fa-external-link-square"></i></a>';
$APPS["qwikclock_inout"]["content"] = $content;
if (account_has_permission($_SESSION['username'], "QWIKCLOCK") !== true) {
unset($APPS['qwikclock_inout']);
}
?>

34
apps/sample_app.php Normal file
View File

@ -0,0 +1,34 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
// Additional i18n strings
addMultiLangStrings(["en_us" => [
"sample app" => "Sample Application",
]
]);
// Set to true to automatically parse the app title as a language string.
$APPS["sample_app"]["i18n"] = TRUE;
// App title.
$APPS["sample_app"]["title"] = "sample app";
// App icon, from FontAwesome.
$APPS["sample_app"]["icon"] = "rocket";
// App content.
$APPS["sample_app"]["content"] = <<<'CONTENTEND'
<div class="list-group">
<div class="list-group-item">
Item 1
</div>
<div class="list-group-item">
Item 2
</div>
<div class="list-group-item">
Item 3
</div>
</div>
CONTENTEND;
?>

77
apps/setup_2fa.php Normal file
View File

@ -0,0 +1,77 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
use OTPHP\Factory;
use Endroid\QrCode\QrCode;
// extra login utils
require_once __DIR__ . "/../lib/login.php";
$APPS["setup_2fa"]["title"] = lang("setup 2fa", false);
$APPS["setup_2fa"]["icon"] = "lock";
if (userHasTOTP($_SESSION['username'])) {
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa active", false) . '</div>'
. '<a href="action.php?action=rm2fa&source=security" class="btn btn-warning btn-sm btn-block">'
. lang("remove 2fa", false) . '</a>';
} else if ($_GET['2fa'] == "generate") {
$codeuri = newTOTP($_SESSION['username']);
$userdata = $database->select('accounts', ['email', 'authsecret', 'realname'], ['username' => $_SESSION['username']])[0];
$label = SYSTEM_NAME . ":" . is_null($userdata['email']) ? $userdata['realname'] : $userdata['email'];
$issuer = SYSTEM_NAME;
$qrCode = new QrCode($codeuri);
$qrCode->setSize(200);
$qrCode->setErrorCorrection("H");
$qrcode = $qrCode->getDataUri();
$totp = Factory::loadFromProvisioningUri($codeuri);
$codesecret = $totp->getSecret();
$chunk_secret = trim(chunk_split($codesecret, 4, ' '));
$lang_manualsetup = lang("manual setup", false);
$lang_secretkey = lang("secret key", false);
$lang_label = lang("label", false);
$lang_issuer = lang("issuer", false);
$lang_entercode = lang("enter otp code", false);
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("scan 2fa qrcode", false) . '</div>' . <<<END
<style nonce="$SECURE_NONCE">
.margintop-15px {
margin-top: 15px;
}
.mono-chunk {
text-align: center;
font-size: 110%;
font-family: monospace;
}
</style>
<img src="$qrcode" class="img-responsive qrcode" />
<form action="action.php" method="POST" class="margintop-15px">
<input type="text" name="totpcode" class="form-control" placeholder="$lang_entercode" minlength=6 maxlength=6 required />
<br />
<input type="hidden" name="action" value="add2fa" />
<input type="hidden" name="source" value="security" />
<input type="hidden" name="secret" value="$codesecret" />
<button type="submit" class="btn btn-success btn-sm btn-block">
END
. lang("confirm 2fa", false) . <<<END
</button>
</form>
<div class="panel panel-default margintop-15px">
<div class="panel-body">
<b>$lang_manualsetup</b>
<br /><label>$lang_secretkey:</label>
<div class="well well-sm mono-chunk">$chunk_secret</div>
<br /><label>$lang_label:</label>
<div class="well well-sm mono-chunk">$label</div>
<br /><label>$lang_issuer:</label>
<div class="well well-sm mono-chunk">$issuer</div>
</div>
</div>
END;
} else {
$APPS["setup_2fa"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("2fa explained", false) . '</div>'
. '<a class="btn btn-success btn-sm btn-block" href="home.php?page=security&2fa=generate">'
. lang("enable 2fa", false) . '</a>';
}

116
apps/sync_mobile.php Normal file
View File

@ -0,0 +1,116 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
use Endroid\QrCode\QrCode;
if (MOBILE_ENABLED) {
addMultiLangStrings(["en_us" => [
"sync mobile" => "Sync Mobile App",
"scan sync qrcode" => "Scan this code with the mobile app or enter the code manually.",
"sync explained" => "Access your account and apps on the go. Use a sync code to securely connect your phone or tablet to AccountHub with the Netsyms Business mobile app.",
"generate sync" => "Create new sync code",
"active sync codes" => "Active codes",
"no active codes" => "No active codes.",
"done adding sync code" => "Done adding code",
"manual setup" => "Manual Setup:",
"sync key" => "Sync key:",
"url" => "URL:",
]
]);
$APPS["sync_mobile"]["title"] = lang("sync mobile", false);
$APPS["sync_mobile"]["icon"] = "mobile";
if (!is_empty($_GET['delsynccode'])) {
if ($database->has("mobile_codes", ["AND" => ["uid" => $_SESSION['uid'], "codeid" => $_GET['delsynccode']]])) {
$database->delete("mobile_codes", ["AND" => ["uid" => $_SESSION['uid'], "codeid" => $_GET['delsynccode']]]);
}
}
if ($_GET['mobilecode'] == "generate") {
if (!is_empty($_GET['showsynccode']) && $database->has("mobile_codes", ["AND" => ["uid" => $_SESSION['uid'], "codeid" => $_GET['showsynccode']]])) {
$code = $database->get("mobile_codes", 'code', ["AND" => ["uid" => $_SESSION['uid'], "codeid" => $_GET['showsynccode']]]);
} else {
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
$database->insert('mobile_codes', ['uid' => $_SESSION['uid'], 'code' => $code]);
}
if (strpos(URL, "http") !== FALSE) {
$url = URL . "mobile/index.php";
} else {
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . URL . "mobile/index.php";
}
$encodedurl = str_replace("/", "\\", $url);
$codeuri = "bizsync://" . $encodedurl . "/" . $_SESSION['username'] . "/" . $code;
$qrCode = new QrCode($codeuri);
$qrCode->setSize(200);
$qrCode->setErrorCorrection("H");
$qrcode = $qrCode->getDataUri();
$chunk_code = trim(chunk_split($code, 5, ' '));
$lang_done = lang("done adding sync code", false);
$APPS["sync_mobile"]["content"] = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> '
. lang("scan sync qrcode", false)
. '</div>'
. <<<END
<style nonce="$SECURE_NONCE">
.margintop-15px {
margin-top: 15px;
}
.mono-chunk {
text-align: center;
font-size: 110%;
font-family: monospace;
}
</style>
<img src="$qrcode" class="img-responsive qrcode" />
<div class="panel panel-default margintop-15px">
<div class="panel-body">
END
. "<b>" . lang("manual setup", false) . "</b><br /><label>" . lang("username", false) . ":</label>"
. '<div class="well well-sm mono-chunk">' . $_SESSION['username'] . '</div>'
. "<label>" . lang("sync key", false) . "</label>"
. <<<END
<div class="well well-sm mono-chunk">$chunk_code</div>
END
. "<label>" . lang("url", false) . "</label>"
. <<<END
<div class="well well-sm mono-chunk">$url</div>
</div>
</div>
<a class="btn btn-success btn-sm btn-block" href="home.php?page=security">$lang_done</a>
END;
} else {
$activecodes = $database->select("mobile_codes", ["codeid", "code"], ["uid" => $_SESSION['uid']]);
$content = '<div class="alert alert-info"><i class="fa fa-info-circle"></i> ' . lang("sync explained", false) . '</div>'
. '<a class="btn btn-success btn-sm btn-block" href="home.php?page=security&mobilecode=generate">'
. lang("generate sync", false) . '</a>';
$content .= "<br /><b>" . lang("active sync codes", false) . ":</b><br />";
$content .= "<div class='list-group'>";
if (count($activecodes) > 0) {
foreach ($activecodes as $c) {
$content .= "<div class='list-group-item mobilekey'><span id=\"mobilecode\">" . trim(chunk_split($c['code'], 5, ' ')) . "</span> <span class='tinybuttons'><a class='btn btn-primary btn-sm' href='home.php?page=security&mobilecode=generate&showsynccode=" . $c['codeid'] . "'><i class='fa fa-qrcode'></i></a> <a class='btn btn-danger btn-sm' href='home.php?page=security&delsynccode=" . $c['codeid'] . "'><i class='fa fa-trash'></i></a></span></div>";
}
} else {
$content .= "<div class='list-group-item'>" . lang("no active codes", false) . "</div>";
}
$content .= "</div>";
$content .= <<<END
<style nonce="$SECURE_NONCE">
.mobilekey {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.mobilekey #mobilecode {
font-family: Ubuntu Mono,monospace;
flex-shrink: 0;
}
</style>
END;
$APPS["sync_mobile"]["content"] = $content;
}
}

View File

@ -0,0 +1,67 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
addMultiLangStrings(["en_us" => [
"messages" => "Messages",
"no messages" => "No messages found."
]
]);
$APPS["taskfloor_messages"]["i18n"] = TRUE;
$APPS["taskfloor_messages"]["title"] = "messages";
$APPS["taskfloor_messages"]["icon"] = "comments";
$APPS["taskfloor_messages"]["type"] = "deep-purple";
$content = "";
use GuzzleHttp\Exception\ClientException;
try {
$client = new GuzzleHttp\Client();
$response = $client->request('POST', TASKFLOOR_API, ['form_params' => [
'action' => "getmsgs",
'username' => $_SESSION['username'],
'password' => $_SESSION['password'],
'max' => 5
]]);
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
if (count($resp['messages']) > 0) {
$content = '<div class="list-group">';
foreach ($resp['messages'] as $msg) {
$content .= '<div class="list-group-item">';
$content .= $msg['text'];
$fromuser = $msg['from']['username'];
$fromname = $msg['from']['name'];
$touser = $msg['to']['username'];
$toname = $msg['to']['name'];
$content .= <<<END
<br />
<span class="small">
<span data-toggle="tooltip" title="$fromuser">$fromname</span>
<i class="fa fa-caret-right"></i>
<span data-toggle="tooltip" title="$touser">$toname</span>
</span>
END;
$content .= '</div>';
}
$content .= "</div>";
} else {
$content = "<div class=\"alert alert-info\">" . lang("no messages", false) . "</div>";
}
}
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block mobile-app-hide">' . lang("open app", false) . ' &nbsp;<i class="fa fa-external-link-square"></i></a>';
$APPS["taskfloor_messages"]["content"] = $content;
} catch (ClientException $e) {
if ($e->getResponse()->getStatusCode() == 403) {
unset($APPS['taskfloor_messages']);
}
} catch (Exception $e) {
$content = "<div class=\"alert alert-danger\">" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block mobile-app-hide">' . lang("open app", false) . ' &nbsp;<i class="fa fa-external-link-square"></i></a>';
$APPS["taskfloor_messages"]["content"] = $content;
}
?>

56
apps/taskfloor_tasks.php Normal file
View File

@ -0,0 +1,56 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
dieifnotloggedin();
addMultiLangStrings(["en_us" => [
"tasks" => "Tasks",
"no tasks found" => "No tasks found."
]
]);
$APPS["taskfloor_tasks"]["i18n"] = TRUE;
$APPS["taskfloor_tasks"]["title"] = "tasks";
$APPS["taskfloor_tasks"]["icon"] = "tasks";
$APPS["taskfloor_tasks"]["type"] = "blue-grey";
$content = "";
use GuzzleHttp\Exception\ClientException;
try {
$client = new GuzzleHttp\Client();
$response = $client->request('POST', TASKFLOOR_API, ['form_params' => [
'action' => "gettasks",
'username' => $_SESSION['username'],
'password' => $_SESSION['password'],
'max' => 5
]]);
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
if (count($resp['tasks']) > 0) {
$content = '<div class="list-group">';
foreach ($resp['tasks'] as $task) {
$content .= '<div class="list-group-item">';
$content .= '<i class="fa fa-fw fa-' . $task['icon'] . '"></i> ' . $task['title'] . '';
$content .= '</div>';
}
$content .= "</div>";
} else {
$content = "<div class=\"alert alert-success\">" . lang("no tasks found", false) . "</div>";
}
}
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block mobile-app-hide">' . lang("open app", false) . ' &nbsp;<i class="fa fa-external-link-square"></i></a>';
$APPS["taskfloor_tasks"]["content"] = $content;
} catch (ClientException $e) {
if ($e->getResponse()->getStatusCode() == 403) {
unset($APPS['taskfloor_tasks']);
}
} catch (Exception $e) {
$content = "<div class=\"alert alert-danger\">" . lang("error loading widget", false) . " " . $e->getMessage() . "</div>";
$content .= '<a href="' . TASKFLOOR_HOME . '" class="btn btn-primary btn-block mobile-app-hide">' . lang("open app", false) . ' &nbsp;<i class="fa fa-external-link-square"></i></a>';
$APPS["taskfloor_tasks"]["content"] = $content;
}
?>

View File

@ -3,13 +3,13 @@
"description": "Single-sign-on system and dashboard for Netsyms Business Apps",
"type": "project",
"require": {
"catfan/medoo": "^1.7",
"guzzlehttp/guzzle": "^6.5",
"catfan/medoo": "^1.2",
"spomky-labs/otphp": "^8.3",
"endroid/qr-code": "^3.2",
"endroid/qrcode": "^1.9",
"ldaptools/ldaptools": "^0.24.0",
"guzzlehttp/guzzle": "^6.2",
"phpmailer/phpmailer": "^5.2",
"christian-riesen/base32": "^1.3",
"mibe/feedwriter": "^1.1"
"christian-riesen/base32": "^1.3"
},
"license": "MPL-2.0",
"authors": [

885
composer.lock generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,5 +1,5 @@
-- MySQL Script generated by MySQL Workbench
-- Mon 11 Feb 2019 04:07:57 PM MST
-- Mon 20 Nov 2017 08:36:18 PM MST
-- Model: New Model Version: 1.0
-- MySQL Workbench Forward Engineering
@ -12,9 +12,15 @@ SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
-- -----------------------------------------------------
-- -----------------------------------------------------
-- Table `acctstatus`
-- Schema accounthub
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `acctstatus` (
CREATE SCHEMA IF NOT EXISTS `accounthub` DEFAULT CHARACTER SET utf8 ;
USE `accounthub` ;
-- -----------------------------------------------------
-- Table `accounthub`.`acctstatus`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `accounthub`.`acctstatus` (
`statusid` INT NOT NULL AUTO_INCREMENT,
`statuscode` VARCHAR(45) NOT NULL,
PRIMARY KEY (`statusid`),
@ -24,9 +30,9 @@ ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `accttypes`
-- Table `accounthub`.`accttypes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `accttypes` (
CREATE TABLE IF NOT EXISTS `accounthub`.`accttypes` (
`typeid` INT NOT NULL AUTO_INCREMENT,
`typecode` VARCHAR(45) NOT NULL,
PRIMARY KEY (`typeid`),
@ -36,16 +42,15 @@ ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `accounts`
-- Table `accounthub`.`accounts`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `accounts` (
CREATE TABLE IF NOT EXISTS `accounthub`.`accounts` (
`uid` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(190) NOT NULL,
`password` VARCHAR(255) NULL,
`realname` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NULL DEFAULT 'NOEMAIL@EXAMPLE.COM',
`authsecret` VARCHAR(100) NULL,
`pin` VARCHAR(10) NULL,
`phone1` VARCHAR(45) NOT NULL,
`phone2` VARCHAR(45) NOT NULL,
`acctstatus` INT NOT NULL DEFAULT 0,
@ -59,33 +64,66 @@ CREATE TABLE IF NOT EXISTS `accounts` (
INDEX `fk_accounts_accttypes1_idx` (`accttype` ASC),
CONSTRAINT `fk_accounts_acctstatus`
FOREIGN KEY (`acctstatus`)
REFERENCES `acctstatus` (`statusid`)
REFERENCES `accounthub`.`acctstatus` (`statusid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_accounts_accttypes1`
FOREIGN KEY (`accttype`)
REFERENCES `accttypes` (`typeid`)
REFERENCES `accounthub`.`accttypes` (`typeid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `apikeys`
-- Table `accounthub`.`apps`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `apikeys` (
CREATE TABLE IF NOT EXISTS `accounthub`.`apps` (
`appid` INT NOT NULL AUTO_INCREMENT,
`appname` VARCHAR(45) NULL,
`appcode` VARCHAR(45) NULL,
PRIMARY KEY (`appid`),
UNIQUE INDEX `appid_UNIQUE` (`appid` ASC))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `accounthub`.`available_apps`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `accounthub`.`available_apps` (
`appid` INT NOT NULL,
`uid` INT NOT NULL,
PRIMARY KEY (`appid`, `uid`),
INDEX `fk_apps_has_accounts_accounts1_idx` (`uid` ASC),
INDEX `fk_apps_has_accounts_apps1_idx` (`appid` ASC),
CONSTRAINT `fk_apps_has_accounts_apps1`
FOREIGN KEY (`appid`)
REFERENCES `accounthub`.`apps` (`appid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_apps_has_accounts_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `accounthub`.`apikeys`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `accounthub`.`apikeys` (
`key` VARCHAR(60) NOT NULL,
`notes` TEXT NULL,
`type` VARCHAR(45) NOT NULL DEFAULT 'FULL',
PRIMARY KEY (`key`))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `groups`
-- Table `accounthub`.`groups`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `groups` (
`groupid` INT NOT NULL AUTO_INCREMENT,
CREATE TABLE IF NOT EXISTS `accounthub`.`groups` (
`groupid` INT NOT NULL,
`groupname` VARCHAR(45) NOT NULL,
PRIMARY KEY (`groupid`),
UNIQUE INDEX `groupid_UNIQUE` (`groupid` ASC),
@ -94,9 +132,9 @@ ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `assigned_groups`
-- Table `accounthub`.`assigned_groups`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `assigned_groups` (
CREATE TABLE IF NOT EXISTS `accounthub`.`assigned_groups` (
`groupid` INT NOT NULL,
`uid` INT NOT NULL,
PRIMARY KEY (`groupid`, `uid`),
@ -104,42 +142,42 @@ CREATE TABLE IF NOT EXISTS `assigned_groups` (
INDEX `fk_groups_has_accounts_groups1_idx` (`groupid` ASC),
CONSTRAINT `fk_groups_has_accounts_groups1`
FOREIGN KEY (`groupid`)
REFERENCES `groups` (`groupid`)
REFERENCES `accounthub`.`groups` (`groupid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_groups_has_accounts_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `managers`
-- Table `accounthub`.`managers`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `managers` (
CREATE TABLE IF NOT EXISTS `accounthub`.`managers` (
`managerid` INT NOT NULL,
`employeeid` INT NOT NULL,
PRIMARY KEY (`managerid`, `employeeid`),
INDEX `fk_managers_accounts2_idx` (`employeeid` ASC),
CONSTRAINT `fk_managers_accounts1`
FOREIGN KEY (`managerid`)
REFERENCES `accounts` (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_managers_accounts2`
FOREIGN KEY (`employeeid`)
REFERENCES `accounts` (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `logtypes`
-- Table `accounthub`.`logtypes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `logtypes` (
CREATE TABLE IF NOT EXISTS `accounthub`.`logtypes` (
`logtype` INT NOT NULL,
`typename` VARCHAR(45) NULL,
PRIMARY KEY (`logtype`),
@ -148,9 +186,9 @@ ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `authlog`
-- Table `accounthub`.`authlog`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `authlog` (
CREATE TABLE IF NOT EXISTS `accounthub`.`authlog` (
`logid` INT NOT NULL AUTO_INCREMENT,
`logtime` DATETIME NOT NULL,
`logtype` INT NOT NULL,
@ -163,21 +201,21 @@ CREATE TABLE IF NOT EXISTS `authlog` (
INDEX `fk_authlog_accounts1_idx` (`uid` ASC),
CONSTRAINT `fk_authlog_logtypes1`
FOREIGN KEY (`logtype`)
REFERENCES `logtypes` (`logtype`)
REFERENCES `accounthub`.`logtypes` (`logtype`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_authlog_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `permissions`
-- Table `accounthub`.`permissions`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `permissions` (
CREATE TABLE IF NOT EXISTS `accounthub`.`permissions` (
`permid` INT NOT NULL AUTO_INCREMENT,
`permcode` VARCHAR(45) NOT NULL,
`perminfo` VARCHAR(200) NULL,
@ -187,9 +225,9 @@ ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `assigned_permissions`
-- Table `accounthub`.`assigned_permissions`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `assigned_permissions` (
CREATE TABLE IF NOT EXISTS `accounthub`.`assigned_permissions` (
`uid` INT NOT NULL,
`permid` INT NOT NULL,
PRIMARY KEY (`uid`, `permid`),
@ -197,246 +235,119 @@ CREATE TABLE IF NOT EXISTS `assigned_permissions` (
INDEX `fk_permissions_has_accounts_permissions1_idx` (`permid` ASC),
CONSTRAINT `fk_permissions_has_accounts_permissions1`
FOREIGN KEY (`permid`)
REFERENCES `permissions` (`permid`)
REFERENCES `accounthub`.`permissions` (`permid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_permissions_has_accounts_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `mobile_codes`
-- Table `accounthub`.`mobile_codes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mobile_codes` (
CREATE TABLE IF NOT EXISTS `accounthub`.`mobile_codes` (
`codeid` INT NOT NULL AUTO_INCREMENT,
`uid` INT NOT NULL,
`code` VARCHAR(45) NOT NULL DEFAULT '',
`description` VARCHAR(255) NOT NULL DEFAULT '',
`code` VARCHAR(45) NULL,
PRIMARY KEY (`codeid`),
UNIQUE INDEX `codeid_UNIQUE` (`codeid` ASC),
INDEX `fk_mobile_codes_accounts1_idx` (`uid` ASC),
UNIQUE INDEX `code_UNIQUE` (`code` ASC),
CONSTRAINT `fk_mobile_codes_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `rate_limit`
-- Table `accounthub`.`rate_limit`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `rate_limit` (
CREATE TABLE IF NOT EXISTS `accounthub`.`rate_limit` (
`ipaddr` VARCHAR(45) NOT NULL,
`lastaction` DATETIME NULL,
PRIMARY KEY (`ipaddr`))
ENGINE = MEMORY;
-- -----------------------------------------------------
-- Table `onetimekeys`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `onetimekeys` (
`key` VARCHAR(10) NOT NULL,
`uid` INT NOT NULL,
`expires` DATETIME NOT NULL,
INDEX `fk_onetimekeys_accounts1_idx` (`uid` ASC),
PRIMARY KEY (`key`),
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
CONSTRAINT `fk_onetimekeys_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `notifications`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `notifications` (
`notificationid` INT NOT NULL AUTO_INCREMENT,
`uid` INT NOT NULL,
`timestamp` DATETIME NOT NULL,
`title` VARCHAR(255) NOT NULL,
`content` TINYTEXT NOT NULL,
`url` VARCHAR(255) NOT NULL,
`seen` TINYINT(1) NOT NULL DEFAULT 0,
`sensitive` TINYINT(1) NOT NULL,
`appid` VARCHAR(255) NULL,
PRIMARY KEY (`notificationid`, `uid`),
UNIQUE INDEX `notificationid_UNIQUE` (`notificationid` ASC),
INDEX `fk_notifications_accounts1_idx` (`uid` ASC),
CONSTRAINT `fk_notifications_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `userkeytypes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `userkeytypes` (
`typeid` INT NOT NULL,
`typename` VARCHAR(45) NOT NULL,
PRIMARY KEY (`typeid`, `typename`))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `userkeys`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `userkeys` (
`uid` INT NOT NULL,
`key` VARCHAR(100) NOT NULL,
`created` DATETIME NULL,
`typeid` INT NOT NULL,
PRIMARY KEY (`uid`),
INDEX `fk_userkeys_userkeytypes1_idx` (`typeid` ASC),
CONSTRAINT `fk_userkeys_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_userkeys_userkeytypes1`
FOREIGN KEY (`typeid`)
REFERENCES `userkeytypes` (`typeid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `userloginkeys`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `userloginkeys` (
`id` INT NOT NULL AUTO_INCREMENT,
`key` VARCHAR(255) NOT NULL,
`expires` DATETIME NULL,
`uid` INT NULL,
`appname` VARCHAR(255) NOT NULL,
`appicon` TINYTEXT NULL,
PRIMARY KEY (`id`, `key`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
INDEX `fk_userloginkeys_accounts1_idx` (`uid` ASC),
CONSTRAINT `fk_userloginkeys_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `apppasswords`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `apppasswords` (
`passid` INT NOT NULL AUTO_INCREMENT,
`hash` VARCHAR(255) NOT NULL,
`uid` INT NOT NULL,
`description` VARCHAR(255) NOT NULL,
PRIMARY KEY (`passid`, `uid`),
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
CONSTRAINT `fk_apppasswords_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
-- -----------------------------------------------------
-- Data for table `acctstatus`
-- Data for table `accounthub`.`acctstatus`
-- -----------------------------------------------------
START TRANSACTION;
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (1, 'NORMAL');
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (2, 'LOCKED_OR_DISABLED');
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (3, 'CHANGE_PASSWORD');
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (4, 'TERMINATED');
INSERT INTO `acctstatus` (`statusid`, `statuscode`) VALUES (5, 'ALERT_ON_ACCESS');
USE `accounthub`;
INSERT INTO `accounthub`.`acctstatus` (`statusid`, `statuscode`) VALUES (1, 'NORMAL');
INSERT INTO `accounthub`.`acctstatus` (`statusid`, `statuscode`) VALUES (2, 'LOCKED_OR_DISABLED');
INSERT INTO `accounthub`.`acctstatus` (`statusid`, `statuscode`) VALUES (3, 'CHANGE_PASSWORD');
INSERT INTO `accounthub`.`acctstatus` (`statusid`, `statuscode`) VALUES (4, 'TERMINATED');
INSERT INTO `accounthub`.`acctstatus` (`statusid`, `statuscode`) VALUES (5, 'ALERT_ON_ACCESS');
COMMIT;
-- -----------------------------------------------------
-- Data for table `accttypes`
-- Data for table `accounthub`.`accttypes`
-- -----------------------------------------------------
START TRANSACTION;
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (1, 'LOCAL');
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (2, 'EXTERNAL');
USE `accounthub`;
INSERT INTO `accounthub`.`accttypes` (`typeid`, `typecode`) VALUES (1, 'LOCAL');
INSERT INTO `accounthub`.`accttypes` (`typeid`, `typecode`) VALUES (2, 'LDAP');
INSERT INTO `accounthub`.`accttypes` (`typeid`, `typecode`) VALUES (3, 'LIGHT');
COMMIT;
-- -----------------------------------------------------
-- Data for table `logtypes`
-- Data for table `accounthub`.`logtypes`
-- -----------------------------------------------------
START TRANSACTION;
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (1, 'PORTAL_LOGIN_OK');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (2, 'PORTAL_LOGIN_FAILED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (3, 'PASSWORD_CHANGED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (4, 'API_LOGIN_OK');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (5, 'API_LOGIN_FAILED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (6, 'PORTAL_BAD_AUTHCODE');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (7, 'API_BAD_AUTHCODE');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (8, 'BAD_CAPTCHA');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (9, '2FA_ADDED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (10, '2FA_REMOVED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (11, 'PORTAL_LOGOUT');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (12, 'API_AUTH_OK');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (13, 'API_AUTH_FAILED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (14, 'API_BAD_KEY');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (15, 'LOG_CLEARED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (16, 'USER_REMOVED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (17, 'USER_ADDED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (18, 'USER_EDITED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (19, 'MOBILE_LOGIN_OK');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (20, 'MOBILE_LOGIN_FAILED');
INSERT INTO `logtypes` (`logtype`, `typename`) VALUES (21, 'MOBILE_BAD_KEY');
USE `accounthub`;
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (1, 'PORTAL_LOGIN_OK');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (2, 'PORTAL_LOGIN_FAILED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (3, 'PASSWORD_CHANGED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (4, 'API_LOGIN_OK');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (5, 'API_LOGIN_FAILED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (6, 'PORTAL_BAD_AUTHCODE');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (7, 'API_BAD_AUTHCODE');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (8, 'BAD_CAPTCHA');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (9, '2FA_ADDED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (10, '2FA_REMOVED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (11, 'PORTAL_LOGOUT');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (12, 'API_AUTH_OK');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (13, 'API_AUTH_FAILED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (14, 'API_BAD_KEY');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (15, 'LOG_CLEARED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (16, 'USER_REMOVED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (17, 'USER_ADDED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (18, 'USER_EDITED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (19, 'MOBILE_LOGIN_OK');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (20, 'MOBILE_LOGIN_FAILED');
INSERT INTO `accounthub`.`logtypes` (`logtype`, `typename`) VALUES (21, 'MOBILE_BAD_KEY');
COMMIT;
-- -----------------------------------------------------
-- Data for table `permissions`
-- Data for table `accounthub`.`permissions`
-- -----------------------------------------------------
START TRANSACTION;
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (1, 'ADMIN', 'System administrator');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (100, 'INV_VIEW', 'Access inventory system');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (101, 'INV_EDIT', 'Edit inventory system');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (200, 'TASKFLOOR', 'Access TaskFloor');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (300, 'QWIKCLOCK', 'Access QwikClock and punch in/out');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (301, 'QWIKCLOCK_MANAGE', 'Edit punches and other data for managed users');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (302, 'QWIKCLOCK_EDITSELF', 'Edit own punches and other data');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (303, 'QWIKCLOCK_ADMIN', 'Add and edit shifts and other data for all users');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (400, 'SITEWRITER', 'Manage and edit websites, messages, and analytics');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (401, 'SITEWRITER_CONTACT', 'Manage messages sent via website contact forms');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (402, 'SITEWRITER_ANALYTICS', 'View website analytics');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (403, 'SITEWRITER_EDIT', 'Edit website content');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (404, 'SITEWRITER_FILES', 'Manage and upload files');
COMMIT;
-- -----------------------------------------------------
-- Data for table `userkeytypes`
-- -----------------------------------------------------
START TRANSACTION;
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (1, 'RSSAtomFeed');
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (2, 'Other');
USE `accounthub`;
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (1, 'ADMIN', 'System administrator');
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (100, 'INV_VIEW', 'Access inventory system');
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (101, 'INV_EDIT', 'Edit inventory system');
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (200, 'TASKFLOOR', 'Access TaskFloor');
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (300, 'QWIKCLOCK', 'Access QwikClock and punch in/out');
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (301, 'QWIKCLOCK_MANAGE', 'Edit punches and other data for managed users');
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (302, 'QWIKCLOCK_EDITSELF', 'Edit own punches and other data');
INSERT INTO `accounthub`.`permissions` (`permid`, `permcode`, `perminfo`) VALUES (303, 'QWIKCLOCK_ADMIN', 'Add and edit shifts and other data for all users');
COMMIT;

View File

@ -1,54 +0,0 @@
CREATE TABLE IF NOT EXISTS `onetimekeys` (
`key` VARCHAR(10) NOT NULL,
`uid` INT(11) NOT NULL,
`expires` DATETIME NOT NULL,
INDEX `fk_onetimekeys_accounts1_idx` (`uid` ASC),
PRIMARY KEY (`key`),
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
CONSTRAINT `fk_onetimekeys_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
CREATE TABLE IF NOT EXISTS `notifications` (
`notificationid` INT NOT NULL AUTO_INCREMENT,
`uid` INT NOT NULL,
`timestamp` DATETIME NOT NULL,
`title` VARCHAR(255) NOT NULL,
`content` TINYTEXT NOT NULL,
`url` VARCHAR(255) NOT NULL,
`seen` TINYINT(1) NOT NULL DEFAULT 0,
`sensitive` TINYINT(1) NOT NULL,
`appid` VARCHAR(255) NULL,
PRIMARY KEY (`notificationid`, `uid`),
UNIQUE INDEX `notificationid_UNIQUE` (`notificationid` ASC),
INDEX `fk_notifications_accounts1_idx` (`uid` ASC),
CONSTRAINT `fk_notifications_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE `groups`
CHANGE COLUMN `groupid` `groupid` INT(11) NOT NULL AUTO_INCREMENT;
SET FOREIGN_KEY_CHECKS = 1;
ALTER TABLE `accounts`
ADD COLUMN `pin` VARCHAR(10) NULL DEFAULT NULL AFTER `authsecret`;
ALTER TABLE `mobile_codes`
CHANGE COLUMN `code` `code` VARCHAR(45) NOT NULL DEFAULT '',
ADD COLUMN `description` VARCHAR(255) NOT NULL DEFAULT '' AFTER `code`;
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (400, 'SITEWRITER', 'Manage and edit websites, messages, and analytics');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (401, 'SITEWRITER_CONTACT', 'Manage messages sent via website contact forms');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (402, 'SITEWRITER_ANALYTICS', 'View website analytics');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (403, 'SITEWRITER_EDIT', 'Edit website content');
INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (404, 'SITEWRITER_FILES', 'Manage and upload files');

View File

@ -1,35 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
CREATE TABLE IF NOT EXISTS `userkeys` (
`uid` INT(11) NOT NULL,
`key` VARCHAR(100) NOT NULL,
`created` DATETIME NULL DEFAULT NULL,
`typeid` INT(11) NOT NULL,
PRIMARY KEY (`uid`),
INDEX `fk_userkeys_userkeytypes1_idx` (`typeid` ASC),
CONSTRAINT `fk_userkeys_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounthub`.`accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_userkeys_userkeytypes1`
FOREIGN KEY (`typeid`)
REFERENCES `accounthub`.`userkeytypes` (`typeid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
CREATE TABLE IF NOT EXISTS `userkeytypes` (
`typeid` INT(11) NOT NULL,
`typename` VARCHAR(45) NOT NULL,
PRIMARY KEY (`typeid`, `typename`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (1, 'RSSAtomFeed');
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (2, 'Other');

View File

@ -1,48 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
DROP TABLE IF EXISTS `available_apps`;
DROP TABLE IF EXISTS `apps`;
CREATE TABLE IF NOT EXISTS `userloginkeys` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`key` VARCHAR(255) NOT NULL,
`expires` DATETIME NULL DEFAULT NULL,
`uid` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`, `key`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
UNIQUE INDEX `key_UNIQUE` (`key` ASC),
INDEX `fk_userloginkeys_accounts1_idx` (`uid` ASC),
CONSTRAINT `fk_userloginkeys_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
ALTER TABLE `userloginkeys`
ADD COLUMN `appname` VARCHAR(255) NOT NULL AFTER `uid`;
ALTER TABLE `userloginkeys`
ADD COLUMN `appicon` TINYTEXT NULL DEFAULT NULL AFTER `appname`;
ALTER TABLE `apikeys`
ADD COLUMN `type` VARCHAR(45) NOT NULL DEFAULT 'FULL' AFTER `notes`;
CREATE TABLE IF NOT EXISTS `apppasswords` (
`passid` INT(11) NOT NULL AUTO_INCREMENT,
`hash` VARCHAR(255) NOT NULL,
`uid` INT(11) NOT NULL,
`description` VARCHAR(255) NOT NULL,
PRIMARY KEY (`passid`, `uid`),
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
CONSTRAINT `fk_apppasswords_accounts1`
FOREIGN KEY (`uid`)
REFERENCES `accounts` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

View File

@ -1,89 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
require __DIR__ . "/required.php";
date_default_timezone_set('UTC');
use \FeedWriter\RSS1;
use \FeedWriter\RSS2;
use \FeedWriter\ATOM;
if (empty($_GET['key']) || empty($_GET['type'])) {
http_response_code(400);
die("400 Bad Request: please send a user key and a feed type");
}
if (!$database->has('userkeys', ['key' => $_GET['key']])) {
http_response_code(403);
die("403 Forbidden: provide valid key");
}
$uid = $database->get('userkeys', 'uid', ['key' => $_GET['key']]);
$user = new User($uid);
switch ($user->getStatus()->get()) {
case AccountStatus::NORMAL:
case AccountStatus::CHANGE_PASSWORD:
case AccountStatus::ALERT_ON_ACCESS:
break;
default:
http_response_code(403);
die("403 Forbidden: user account not active");
}
$notifications = Notifications::get($user);
switch ($_GET['type']) {
case "rss1":
$feed = new RSS1();
break;
case "rss":
case "rss2":
$feed = new RSS2();
break;
case "atom":
$feed = new ATOM();
break;
default:
http_response_code(400);
die("400 Bad Request: feed parameter must have a value of \"rss\", \"rss1\", \"rss2\" or \"atom\".");
}
$feed->setTitle($Strings->build("Notifications from server for user", ['server' => $SETTINGS['site_title'], 'user' => $user->getName()], false));
if (strpos($SETTINGS['url'], "http") === 0) {
$url = $SETTINGS['url'];
} else {
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "") . $SETTINGS['url'];
}
$feed->setLink($url);
foreach ($notifications as $n) {
$item = $feed->createNewItem();
$item->setTitle($n['title']);
if (empty($n['url'])) {
$item->setLink($url);
} else {
$item->setLink($n['url']);
}
$item->setDate(strtotime($n['timestamp']));
if ($n['sensitive']) {
$content = $Strings->get("Sensitive content hidden", false);
} else {
$content = $n['content'];
}
if ($_GET['type'] == "atom") {
$item->setContent($content);
} else {
$item->setDescription($content);
}
$feed->addItem($item);
}
$feed->printFeed();

249
home.php
View File

@ -1,6 +1,253 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
header("Location: app.php");
require_once __DIR__ . "/required.php";
if ($_SESSION['loggedin'] != true) {
header('Location: index.php');
die("Session expired. Log in again to continue.");
} else if (is_empty($_SESSION['password'])) {
header('Location: index.php');
die("You need to log in again.");
}
require_once __DIR__ . "/pages.php";
$pageid = "home";
if (!is_empty($_GET['page'])) {
if (array_key_exists($_GET['page'], PAGES)) {
$pageid = $_GET['page'];
} else {
$pageid = "404";
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo SITE_TITLE; ?></title>
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/font-awesome.min.css" rel="stylesheet">
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
<link href="static/css/app.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
<?php
if ((SHOW_ICON == "both" || SHOW_ICON == "app") && ICON_POSITION != "menu") {
if (MENU_BAR_STYLE != "fixed") {
?>
<img class="img-responsive banner-image" src="static/img/logo.svg" />
<?php
}
}
?>
</div>
</div>
<nav class="navbar navbar-default navbar-orange navbar-<?php echo MENU_BAR_STYLE; ?>-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<?php
if (SHOW_ICON == "both" || SHOW_ICON == "app") {
if (MENU_BAR_STYLE == "fixed" || ICON_POSITION == "menu") {
$src = "static/img/logo.svg";
if ($pageid != "home") {
$src = "static/img/up-arrow-black.png";
}
?>
<a class="navbar-brand" href="home.php">
<img src="<?php echo $src; ?>" />
</a>
<?php
}
}
?>
<a class="navbar-brand" href="home.php">
<?php
echo SITE_TITLE;
?>
</a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav">
<?php
foreach (PAGES as $id => $pg) {
if ($pg['navbar'] === TRUE) {
if ($pageid == $id) {
?>
<li class="active">
<?php
} else {
?>
<li>
<?php } ?>
<a href="home.php?page=<?php echo $id; ?>">
<?php
if (isset($pg['icon'])) {
?>
<i class="fa fa-<?php echo $pg['icon']; ?> fa-fw"></i>
<?php } ?>
<?php lang($pg['title']) ?>
</a>
</li>
<?php
}
}
?>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="home.php"><i class="fa fa-user fa-fw"></i> <?php echo $_SESSION['realname'] ?></a></li>
<li><a href="action.php?action=signout"><i class="fa fa-sign-out fa-fw"></i> <?php lang("sign out") ?></a></li>
</ul>
</div>
</nav>
<?php
if (MENU_BAR_STYLE == "fixed") {
?>
<div class="pad-75px"></div>
<?php
}
?>
<div class="app-dock-container mobile-app-hide">
<div class="app-dock">
<?php
foreach (EXTERNAL_APPS as $a) {
?>
<div class="app-dock-item">
<p>
<a href="<?php echo $a['url']; ?>">
<img class="img-responsive app-icon" src="<?php
if (strpos($a['icon'], "http") !== 0) {
echo $a['url'] . $a['icon'];
} else {
echo $a['icon'];
}
?>"/>
<span><?php echo $a['title']; ?></span>
</a>
</p>
</div>
<?php
}
?>
</div>
</div>
<?php
// Alert messages
if (!is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
// optional string generation argument
if (is_empty($_GET['arg'])) {
$alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
} else {
$alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => $_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;
}
echo <<<END
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
<div class="alert alert-dismissible alert-$alerttype">
<button type="button" class="close">&times;</button>
<i class="fa fa-$alerticon"></i> $alertmsg
</div>
</div>
</div>
END;
}
?>
<div class="row widget-box">
<?php
// Center the widgets horizontally on the screen
$appcount = 0;
foreach (APPS[$pageid] as $app) {
if (file_exists(__DIR__ . "/apps/" . $app . ".php")) {
include_once __DIR__ . "/apps/" . $app . ".php";
if (isset($APPS[$app])) {
$appcount++;
}
}
}
if ($appcount == 1) {
?>
<div class="hidden-xs col-sm-3 col-md-4 col-lg-4">
<!-- Empty placeholder column for nice center-align -->
</div>
<?php
} else if ($appcount == 2) {
?>
<div class="hidden-xs hidden-sm col-md-2 col-lg-2">
<!-- Empty placeholder column for nice center-align -->
</div>
<?php
}
// Load app widgets
foreach (APPS[$pageid] as $app) {
if (file_exists(__DIR__ . "/apps/" . $app . ".php")) {
include_once __DIR__ . "/apps/" . $app . ".php";
if (!isset($APPS[$app])) {
continue;
}
$apptitle = ($APPS[$app]['i18n'] === TRUE ? lang($APPS[$app]['title'], false) : $APPS[$app]['title']);
$appicon = (is_empty($APPS[$app]['icon']) ? "" : "fa fa-fw fa-" . $APPS[$app]['icon']);
$apptype = (is_empty($APPS[$app]['type']) ? "default" : $APPS[$app]['type']);
$appcontent = $APPS[$app]['content'];
echo <<<END
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
<div class="panel panel-$apptype apppanel">
<div class="panel-heading">
<h3 class="panel-title"><i class="$appicon"></i> $apptitle </h3>
</div>
<div class="panel-body">
$appcontent
</div>
</div>
</div>
END;
}
}
?>
</div>
<div class="footer">
<?php echo FOOTER_TEXT; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
</div>
</div>
<script src="static/js/jquery-3.2.1.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
<script src="static/js/app.js"></script>
</body>
</html>

324
index.php
View File

@ -1,113 +1,245 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
require_once __DIR__ . "/required.php";
// if we're logged in, we don't need to be here.
if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
header('Location: app.php');
require_once __DIR__ . "/lib/login.php";
// If we're logged in, we don't need to be here.
if ($_SESSION['loggedin'] && !is_empty($_SESSION['password'])) {
header('Location: home.php');
die();
// This branch will likely run if the user signed in from a different app.
} else if ($_SESSION['loggedin'] && is_empty($_SESSION['password'])) {
$alert = lang("sign in again", false);
$alerttype = "info";
}
/**
* 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;
?>
<!DOCTYPE html>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo $SETTINGS['site_title']; ?></title>
<link rel="icon" href="static/img/logo.svg">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<style nonce="<?php echo $SECURE_NONCE; ?>">
.display-5 {
font-size: 2.5rem;
font-weight: 300;
line-height: 1.2;
/* Authenticate user */
$username_ok = false;
$multiauth = false;
$change_password = false;
if ($VARS['progress'] == "1") {
engageRateLimit();
if (!RECAPTCHA_ENABLED || (RECAPTCHA_ENABLED && verifyReCaptcha($VARS['g-recaptcha-response']))) {
$autherror = "";
if (user_exists($VARS['username'])) {
$status = get_account_status($VARS['username'], $error);
switch ($status) {
case "LOCKED_OR_DISABLED":
$alert = lang("account locked", false);
break;
case "TERMINATED":
$alert = lang("account terminated", false);
break;
case "CHANGE_PASSWORD":
$alert = lang("password expired", false);
$alerttype = "info";
$_SESSION['username'] = strtolower($VARS['username']);
$_SESSION['uid'] = $database->get('accounts', 'uid', ['username' => strtolower($VARS['username'])]);
$change_password = true;
break;
case "NORMAL":
$username_ok = true;
break;
case "ALERT_ON_ACCESS":
$mail_resp = sendLoginAlertEmail($VARS['username']);
if (DEBUG) {
var_dump($mail_resp);
}
$username_ok = true;
break;
default:
if (!is_empty($error)) {
$alert = $error;
} else {
$alert = lang("login error", false);
}
break;
}
if ($username_ok) {
if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
$_SESSION['passok'] = true; // stop logins using only username and authcode
if (userHasTOTP($VARS['username'])) {
$multiauth = true;
$_SESSION['password'] = $VARS['password'];
} else {
doLoginUser($VARS['username'], $VARS['password']);
insertAuthLog(1, $_SESSION['uid']);
header('Location: home.php');
die("Logged in, go to home.php");
}
} else {
if (!is_empty($autherror)) {
$alert = $autherror;
insertAuthLog(2, null, "Username: " . $VARS['username']);
} else {
$alert = lang("login incorrect", false);
insertAuthLog(2, null, "Username: " . $VARS['username']);
}
}
}
} else { // User does not exist anywhere
$alert = lang("login incorrect", false);
insertAuthLog(2, null, "Username: " . $VARS['username']);
}
.banner-image {
max-height: 100px;
margin: 2em auto;
border: 1px solid grey;
border-radius: 15%;
} else {
$alert = lang("captcha error", false);
insertAuthLog(8, null, "Username: " . $VARS['username']);
}
} else if ($VARS['progress'] == "2") {
engageRateLimit();
if ($_SESSION['passok'] !== true) {
// stop logins using only username and authcode
sendError("Password integrity check failed!");
}
if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
doLoginUser($VARS['username'], $VARS['password']);
insertAuthLog(1, $_SESSION['uid']);
header('Location: home.php');
die("Logged in, go to home.php");
} else {
$alert = lang("2fa incorrect", false);
insertAuthLog(6, null, "Username: " . $VARS['username']);
}
} else if ($VARS['progress'] == "chpasswd") {
engageRateLimit();
if (!is_empty($_SESSION['username'])) {
$error = [];
$result = change_password($VARS['oldpass'], $VARS['newpass'], $VARS['conpass'], $error);
if ($result === TRUE) {
$alert = lang(MESSAGES["password_updated"]["string"], false);
$alerttype = MESSAGES["password_updated"]["type"];
}
</style>
switch (count($error)) {
case 0:
break;
case 1:
$alert = lang(MESSAGES[$error[0]]["string"], false);
$alerttype = MESSAGES[$error[0]]["type"];
break;
case 2:
$alert = lang2(MESSAGES[$error[0]]["string"], ["arg" => $error[1]], false);
$alerttype = MESSAGES[$error[0]]["type"];
break;
default:
$alert = lang(MESSAGES["generic_op_error"]["string"], false);
$alerttype = MESSAGES["generic_op_error"]["type"];
}
} else {
session_destroy();
header('Location: index.php');
die();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 text-center">
<img class="banner-image" src="./static/img/logo.svg" />
</div>
<title><?php echo SITE_TITLE; ?></title>
<div class="col-12 text-center">
<h1 class="display-5 mb-4"><?php $Strings->get($title); ?></h1>
</div>
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/font-awesome.min.css" rel="stylesheet">
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
<link href="static/css/app.css" rel="stylesheet">
<?php if (RECAPTCHA_ENABLED) { ?>
<script src='https://www.google.com/recaptcha/api.js'></script>
<?php } ?>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
<div>
<?php
if (SHOW_ICON == "both" || SHOW_ICON == "index") {
?>
<img class="img-responsive banner-image" src="static/img/logo.svg" />
<?php } ?>
</div>
<div class="panel panel-orange">
<div class="panel-heading">
<h3 class="panel-title"><?php lang("sign in"); ?></h3>
</div>
<div class="panel-body">
<form action="" method="POST">
<?php
if (!is_empty($alert)) {
$alerttype = isset($alerttype) ? $alerttype : "danger";
?>
<div class="alert alert-<?php echo $alerttype ?>">
<?php
switch ($alerttype) {
case "danger":
$alerticon = "times";
break;
case "warning":
$alerticon = "exclamation-triangle";
break;
case "info":
$alerticon = "info-circle";
break;
case "success":
$alerticon = "check";
break;
default:
$alerticon = "square-o";
}
?>
<i class="fa fa-fw fa-<?php echo $alerticon ?>"></i> <?php echo $alert ?>
</div>
<?php
}
<div class="col-12 col-sm-8 col-lg-6">
<div class="card mt-4">
<div class="card-body">
<a href="<?php echo $url; ?>" class="btn btn-primary btn-block"><?php $Strings->get($button); ?></a>
if (!$multiauth && !$change_password) {
?>
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
<?php if (RECAPTCHA_ENABLED) { ?>
<div class="g-recaptcha" data-sitekey="<?php echo RECAPTCHA_SITE_KEY; ?>"></div>
<br />
<?php } ?>
<input type="hidden" name="progress" value="1" />
<?php
} else if ($multiauth) {
?>
<div class="alert alert-info">
<?php lang("2fa prompt"); ?>
</div>
<input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
<input type="hidden" name="progress" value="2" />
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
<?php
} else if ($change_password) {
?>
<input type="password" class="form-control" name="oldpass" placeholder="Current password" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
<input type="password" class="form-control" name="newpass" placeholder="New password" required="required" autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
<input type="password" class="form-control" name="conpass" placeholder="New password (again)" required="required" autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
<input type="hidden" name="progress" value="chpasswd" />
<?php
}
?>
<button type="submit" class="btn btn-primary">
<?php lang("continue"); ?>
</button>
</form>
</div>
</div>
</div>
</div>
<div class="footer">
<?php echo FOOTER_TEXT; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
</div>
</div>
</div>
<?php
}
if (!empty($_GET['logout'])) {
showHTML("You have been logged out.", "Log in again", "./index.php");
die();
}
if (empty($_SESSION["login_code"])) {
$redirecttologin = true;
} else {
try {
$uid = LoginKey::getuid($_SESSION["login_code"]);
$user = new User($uid);
Session::start($user);
$_SESSION["login_code"] = null;
header('Location: app.php');
showHTML("Logged in", "Continue", "./app.php");
die();
} catch (Exception $ex) {
$redirecttologin = true;
}
}
if ($redirecttologin) {
try {
$code = LoginKey::generate($SETTINGS["site_title"], "../static/img/logo.svg");
$_SESSION["login_code"] = $code;
$loginurl = "./login/?code=" . htmlentities($code) . "&redirect=" . htmlentities($_SERVER["REQUEST_URI"]);
header("Location: $loginurl");
showHTML("Continue", "Continue", $loginurl);
die();
} catch (Exception $ex) {
sendError($ex->getMessage());
}
}
<script src="static/js/jquery-3.2.1.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
</body>
</html>

98
lang/en_us.php Normal file
View File

@ -0,0 +1,98 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
$STRINGS = [
"sign in" => "Sign In",
"username" => "Username",
"password" => "Password",
"continue" => "Continue",
"authcode" => "Authentication code",
"2fa prompt" => "Enter the six-digit code from your mobile authenticator "
. "app.",
"2fa incorrect" => "Authentication code incorrect.",
"login incorrect" => "Login incorrect.",
"login successful" => "Login successful.",
"login error" => "There was a server problem. Try again later.",
"account locked" => "This account has been disabled. Contact technical "
. "support.",
"password expired" => "You must change your password before continuing.",
"account terminated" => "Account terminated. Access denied.",
"account state error" => "Your account state is not stable. Log out, "
. "restart your browser, and try again.",
"password on 500 list" => "The given password is ranked number {arg} out "
. "of the 500 most common passwords. Try a different one.",
"welcome user" => "Welcome, {user}!",
"change password" => "Change password",
"account security" => "Account security",
"security options" => "Security options",
"account options" => "Account options",
"options" => "Options",
"sign out" => "Sign out",
"settings" => "Settings",
"account" => "Account",
"404 error" => "404 Error",
"page not found" => "Page not found.",
"current password incorrect" => "The current password is incorrect. "
. "Try again.",
"new password mismatch" => "The new passwords did not match. Try again.",
"weak password" => "Password does not meet requirements.",
"password updated" => "Password updated successfully.",
"setup 2fa" => "Setup 2-factor authentication",
"2fa removed" => "2-factor authentication disabled.",
"2fa enabled" => "2-factor authentication activated.",
"remove 2fa" => "Disable 2-factor authentication",
"2fa explained" => "2-factor authentication adds more security to your "
. "account. You can use the Auth Keys (key icon) feature of the Netsyms "
. "Business Mobile app, or another TOTP-enabled app (Authy, FreeOTP, etc) on your "
. "smartphone. When you have the app installed, you can enable 2-factor "
. "authentication by clicking the button below and scanning a QR code with "
. "the app. Whenever you sign in in the future, you'll need to input a "
. "six-digit code from your phone into the login page when prompted. You "
. "can disable 2-factor authentication from this page if you change your "
. "mind.",
"2fa active" => "2-factor authentication is active on your account. To "
. "remove 2fa, reset your authentication secret, or change to a new "
. "security device, click the button below.",
"enable 2fa" => "Enable 2-factor authentication",
"scan 2fa qrcode" => "Scan the QR Code with the authenticator app, or enter"
. " the information manually. Then type in the six-digit code the app gives you and press Finish Setup.",
"confirm 2fa" => "Finish setup",
"invalid parameters" => "Invalid request parameters.",
"ldap server error" => "The LDAP server returned an error: {arg}",
"user does not exist" => "User does not exist.",
"captcha error" => "There was a problem with the CAPTCHA (robot test). "
. "Try again.",
"home" => "Home",
"ldap error" => "LDAP error: {error}",
"old and new passwords match" => "Your current and new passwords are the "
. "same.",
"generic op error" => "An unknown error occurred. Try again later.",
"password complexity insufficent" => "The new password does not meet the "
. "minumum requirements defined by your system administrator.",
"error loading widget" => "There was a problem loading this app.",
"open app" => "Open App",
"sign in again" => "Please sign in again to continue.",
"login failed try on web" => "There is a problem with your account. Visit "
. "AccountHub via a web browser for more information.",
"mobile login disabled" => "Mobile login has been disabled by your system "
. "administrator. Contact technical support for more information.",
"admin alert email subject" => "Alert: User login notification",
"admin alert email message" => "You (or another administrator) requested to"
. " be notified when user \"{username}\" logged in, an event which happened"
. " just now."
. "\r\n"
. "\r\nUsername: \t{username}"
. "\r\nApplication: \t{appname}"
. "\r\nDate/Time: \t{datetime}"
. "\r\nIP address: \t{ipaddr}"
. "\r\n"
. "\r\nThese notifications can be disabled by editing the user in "
. "ManagePanel.",
"enter otp code" => "Enter 6-digit code",
"secret key" => "Secret key",
"label" => "Label",
"issuer" => "Issuer",
];

View File

@ -45,6 +45,10 @@ define("MESSAGES", [
"string" => "account state error",
"type" => "danger"
],
"ldap_error" => [
"string" => "ldap server error",
"type" => "danger"
],
"passwords_same" => [
"string" => "old and new passwords match",
"type" => "danger"
@ -56,25 +60,5 @@ define("MESSAGES", [
"generic_op_error" => [
"string" => "generic op error",
"type" => "danger"
],
"pin_updated" => [
"string" => "pin updated",
"type" => "success"
],
"new_pin_mismatch" => [
"string" => "new pin mismatch",
"type" => "danger"
],
"invalid_pin_format" => [
"string" => "invalid pin format",
"type" => "danger"
],
"notification_deleted" => [
"string" => "Notification deleted.",
"type" => "success"
],
"feed_key_reset" => [
"string" => "Feed key reset.",
"type" => "success"
]
]);

View File

@ -1,17 +0,0 @@
{
"setup 2fa": "Setup 2-factor authentication",
"2fa removed": "2-factor authentication disabled.",
"2fa enabled": "2-factor authentication activated.",
"remove 2fa": "Disable 2-factor authentication",
"2fa explained": "2-factor authentication adds more security to your account. You can use the Auth Keys (key icon) feature of the Netsyms mobile app, or another TOTP-enabled app (Authy, FreeOTP, etc) on your smartphone. When you have the app installed, you can enable 2-factor authentication by clicking the button below and scanning a QR code with the app. Whenever you sign in in the future, you'll need to input a six-digit code from your phone into the login page when prompted. You can disable 2-factor authentication from this page if you change your mind.",
"2fa active": "2-factor authentication is active on your account. To remove 2fa, reset your authentication secret, or change to a new security device, click the button below.",
"enable 2fa": "Enable 2-factor authentication",
"scan 2fa qrcode": "Scan the QR Code with the authenticator app, or enter the information manually. Then type in the six-digit code the app gives you and press Finish Setup.",
"confirm 2fa": "Finish setup",
"enter otp code": "Enter 6-digit code",
"secret key": "Secret key",
"label": "Label",
"issuer": "Issuer",
"no such code or code expired": "That code is incorrect or expired.",
"2-factor is enabled, you need to use the QR code or manual setup for security reasons": "2-factor is enabled, you need to use the QR code or manual setup for security reasons."
}

View File

@ -1,5 +0,0 @@
{
"user does not exist": "User does not exist.",
"group does not exist": "Group does not exist.",
"login successful": "Login successful."
}

View File

@ -1,11 +0,0 @@
{
"App Passwords": "App Passwords",
"app passwords explained": "Use app passwords instead of your actual password when logging into apps with your {site_name} login. App passwords are required in some places when you have 2-factor authentication enabled.",
"app password setup instructions": "Use the username and password below to log in to {app_name}. You'll only be shown this password one time.",
"App name": "App name",
"Generate password": "Generate password",
"Revoke password": "Revoke password",
"You don't have any app passwords.": "You don't have any app passwords.",
"Done": "Done",
"App passwords are not allowed here.": "App passwords are not allowed here."
}

View File

@ -1,13 +0,0 @@
{
"Create virtual notes and lists": "Create virtual notes and lists",
"Punch in and check work schedule": "Punch in and check work schedule",
"Manage physical items": "Manage physical items",
"Create and publish e-newsletters": "Create and publish e-newsletters",
"Manage users, permissions, and security": "Manage users, permissions, and security",
"Checkout customers and manage online orders": "Checkout customers and manage online orders",
"Build websites and manage contact form messages": "Build websites and manage contact form messages",
"Track jobs and assigned tasks": "Track jobs and assigned tasks",
"Change password, setup 2-factor, and add app passwords": "Change password, setup 2-factor, and add app passwords",
"Change password, setup 2-factor, add app passwords, and change PIN": "Change password, setup 2-factor, add app passwords, and change PIN",
"Connect mobile devices to {name} and get notifications": "Connect mobile devices to {name} and get notifications"
}

View File

@ -1,7 +0,0 @@
{
"sign out": "Sign out",
"404 error": "404 Error",
"page not found": "Page not found.",
"invalid parameters": "Invalid request parameters.",
"login server error": "The login server returned an error: {arg}"
}

View File

@ -1,7 +0,0 @@
{
"You have been logged out.": "You have been logged out.",
"Log in again": "Log in again",
"login server unavailable": "Login server unavailable. Try again later or contact technical support.",
"no access permission": "You do not have permission to access this system.",
"Logged in": "Logged in"
}

View File

@ -1,4 +0,0 @@
{
"ldap server error": "The LDAP server returned an error: {arg}",
"ldap error": "LDAP error: {error}"
}

View File

@ -1,25 +0,0 @@
{
"Login to {app}": "Login to {app}",
"Username not found.": "Username not found.",
"Password for {user}": "Password for {user}",
"Password incorrect.": "Password incorrect.",
"Two-factor code": "Two-factor code",
"Code incorrect.": "Code incorrect.",
"Current password for {user}": "Current password for {user}",
"New password": "New password",
"New password (again)": "New password (again)",
"Fill in all three boxes.": "Fill in all three boxes.",
"sign in": "Sign In",
"username": "Username",
"password": "Password",
"continue": "Continue",
"authcode": "Authentication code",
"2fa prompt": "Enter the six-digit code from your mobile authenticator app.",
"2fa incorrect": "Authentication code incorrect.",
"login incorrect": "Login incorrect.",
"account locked": "This account has been disabled. Contact technical support.",
"password expired": "You must change your password before continuing.",
"account terminated": "Account terminated. Access denied.",
"account state error": "Your account state is not stable. Log out, restart your browser, and try again.",
"Back": "Back"
}

View File

@ -1,9 +0,0 @@
{
"Notifications": "Notifications",
"Notification deleted.": "Notification deleted.",
"Mark as read": "Mark as read",
"Delete": "Delete",
"All caught up!": "All caught up!",
"Notifications from server for user": "Notifications from {server} for {user}",
"Sensitive content hidden": "Sensitive content hidden"
}

View File

@ -1,13 +0,0 @@
{
"password on 500 list": "The given password is ranked number {arg} out of the 500 most common passwords. Try a different one.",
"change password": "Change password",
"current password incorrect": "The current password is incorrect. Try again.",
"new password mismatch": "The new passwords did not match. Try again.",
"weak password": "Password does not meet requirements.",
"password updated": "Password updated successfully.",
"current password": "Current password",
"new password": "New password",
"confirm password": "New password (again)",
"password complexity insufficent": "The new password does not meet the minumum requirements defined by your system administrator.",
"old and new passwords match": "Your current and new passwords are the same."
}

View File

@ -1,9 +0,0 @@
{
"pin explanation": "Change or set a login PIN for the Station kiosk Quick Access. PIN codes must be between one and eight digits.",
"change pin": "Change PIN",
"new pin": "New PIN",
"confirm pin": "New PIN (again)",
"pin updated": "PIN updated.",
"new pin mismatch": "The new PINs don't match each other.",
"invalid pin format": "PIN codes must be numeric and between one and eight digits in length."
}

View File

@ -1,7 +0,0 @@
{
"sign in again": "Please sign in again to continue.",
"login failed try on web": "There is a problem with your account. Visit AccountHub via a web browser for more information.",
"mobile login disabled": "Mobile login has been disabled by your system administrator. Contact technical support for more information.",
"admin alert email subject": "Alert: User login notification",
"admin alert email message": "You (or another administrator) requested to be notified when user \"{username}\" logged in, an event which happened just now.\r\n\r\nUsername: \t{username}\r\nApplication: \t{appname}\r\nDate\/Time: \t{datetime}\r\nIP address: \t{ipaddr}\r\n\r\nThese notifications can be disabled by editing the user in ManagePanel."
}

View File

@ -1,15 +0,0 @@
{
"Create Account": "Create Account",
"Account Created": "Account Created",
"Choose a username.": "Choose a username.",
"Choose a password.": "Choose a password.",
"Enter your name.": "Enter your name.",
"Username already taken, pick another.": "Username already taken, pick another.",
"Your password must be at least {n} characters long.": "Your password must be at least {n} characters long.",
"That email address doesn't look right.": "That email address doesn't look right.",
"Please enter your username (4-100 characters, alphanumeric).": "Please enter your username (4-100 characters, alphanumeric).",
"That password is one of the most popular and insecure ever, make a better one.": "That password is one of the most popular and insecure ever, make a better one.",
"Account creation not allowed. Contact the site administrator for an account.": "Account creation not allowed. Contact the site administrator for an account.",
"CAPTCHA answer incorrect.": "CAPTCHA answer incorrect.",
"That email address is already in use.": "That email address is already in use."
}

View File

@ -1,17 +0,0 @@
{
"sync mobile": "Sync Mobile App",
"scan sync qrcode": "Scan this code with the mobile app or enter the code manually.",
"sync explained": "Access your account and apps on the go. Use a sync code to securely connect your phone or tablet to {site_name} with the Netsyms mobile app.",
"generate sync": "Create new sync code",
"active sync codes": "Active codes",
"no active codes": "No active codes.",
"done adding sync code": "Done adding code",
"manual setup": "Manual Setup:",
"sync key": "Sync key:",
"url": "URL:",
"sync code name": "Device nickname",
"notification feed explained": "You can receive notifications via a RSS or ATOM news reader by clicking one of the buttons or manually adding a URL. Click the Reset button if you think someone else might know your feed URL (you'll need to delete and re-add the feed on all your devices).",
"Reset": "Reset",
"Feed key reset.": "Feed key reset.",
"Revoke key": "Revoke key"
}

View File

@ -1,9 +0,0 @@
{
"account security": "Account security",
"security options": "Security options",
"account options": "Account options",
"sync": "Sync settings",
"settings": "Settings",
"account": "Account",
"Home": "Home"
}

View File

@ -1,35 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class IncorrectPasswordException extends Exception {
public function __construct(string $message = "Incorrect password.", int $code = 0, \Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}
class WeakPasswordException extends Exception {
public function __construct(string $message = "Password is weak or compromised.", int $code = 0, \Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}
class PasswordMatchException extends Exception {
public function __construct(string $message = "Old and new passwords are identical", int $code = 0, \Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}
class PasswordMismatchException extends Exception {
public function __construct(string $message = "Passwords do not match", int $code = 0, \Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}

View File

@ -1,326 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class FormBuilder {
private $items = [];
private $hiddenitems = [];
private $title = "";
private $icon = "";
private $buttons = [];
private $action = "action.php";
private $method = "POST";
private $id = "editform";
/**
* Create a form with autogenerated HTML.
*
* @param string $title Form title/heading
* @param string $icon FontAwesone icon next to the title.
* @param string $action URL to submit the form to.
* @param string $method Form submission method (POST, GET, etc.)
*/
public function __construct(string $title = "Untitled Form", string $icon = "fas fa-file-alt", string $action = "action.php", string $method = "POST") {
$this->title = $title;
$this->icon = $icon;
$this->action = $action;
$this->method = $method;
}
/**
* Set the title of the form.
* @param string $title
*/
public function setTitle(string $title) {
$this->title = $title;
}
/**
* Set the icon for the form.
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
*/
public function setIcon(string $icon) {
$this->icon = $icon;
}
/**
* Set the URL the form will submit to.
* @param string $action
*/
public function setAction(string $action) {
$this->action = $action;
}
/**
* Set the form submission method (GET, POST, etc)
* @param string $method
*/
public function setMethod(string $method = "POST") {
$this->method = $method;
}
/**
* Set the form ID.
* @param string $id
*/
public function setID(string $id = "editform") {
$this->id = $id;
}
/**
* Add an input to the form.
*
* @param string $name Element name
* @param string $value Element value
* @param string $type Input type (text, number, date, select, tel...)
* @param bool $required If the element is required for form submission.
* @param string $id Element ID
* @param array $options Array of [value => text] pairs for a select element
* @param string $label Text label to display near the input
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
* @param int $width Bootstrap column width for the input, out of 12.
* @param int $minlength Minimum number of characters for the input.
* @param int $maxlength Maximum number of characters for the input.
* @param string $pattern Regex pattern for custom client-side validation.
* @param string $error Message to show if the input doesn't validate.
*/
public function addInput(string $name, string $value = "", string $type = "text", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
$item = [
"name" => $name,
"value" => $value,
"type" => $type,
"required" => $required,
"label" => $label,
"icon" => $icon,
"width" => $width,
"minlength" => $minlength,
"maxlength" => $maxlength
];
if (!empty($id)) {
$item["id"] = $id;
}
if (!empty($options) && $type == "select") {
$item["options"] = $options;
}
if (!empty($pattern)) {
$item["pattern"] = $pattern;
}
if (!empty($error)) {
$item["error"] = $error;
}
$this->items[] = $item;
}
/**
* Add a text input.
*
* @param string $name Element name
* @param string $value Element value
* @param bool $required If the element is required for form submission.
* @param string $id Element ID
* @param string $label Text label to display near the input
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
* @param int $width Bootstrap column width for the input, out of 12.
* @param int $minlength Minimum number of characters for the input.
* @param int $maxlength Maximum number of characters for the input.
* @param string $pattern Regex pattern for custom client-side validation.
* @param string $error Message to show if the input doesn't validate.
*/
public function addTextInput(string $name, string $value = "", bool $required = true, string $id = "", string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
$this->addInput($name, $value, "text", $required, $id, null, $label, $icon, $width, $minlength, $maxlength, $pattern, $error);
}
/**
* Add a select dropdown.
*
* @param string $name Element name
* @param string $value Element value
* @param bool $required If the element is required for form submission.
* @param string $id Element ID
* @param array $options Array of [value => text] pairs for a select element
* @param string $label Text label to display near the input
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
* @param int $width Bootstrap column width for the input, out of 12.
*/
public function addSelect(string $name, string $value = "", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4) {
$this->addInput($name, $value, "select", $required, $id, $options, $label, $icon, $width);
}
/**
* Add a button to the form.
*
* @param string $text Text string to show on the button.
* @param string $icon FontAwesome icon to show next to the text.
* @param string $href If not null, the button will actually be a hyperlink.
* @param string $type Usually "button" or "submit". Ignored if $href is set.
* @param string $id The element ID.
* @param string $name The element name for the button.
* @param string $value The form value for the button. Ignored if $name is null.
* @param string $class The CSS classes for the button, if a standard success-colored one isn't right.
*/
public function addButton(string $text, string $icon = "", string $href = null, string $type = "button", string $id = null, string $name = null, string $value = "", string $class = "btn btn-success") {
$button = [
"text" => $text,
"icon" => $icon,
"class" => $class,
"type" => $type,
"id" => $id,
"href" => $href,
"name" => $name,
"value" => $value
];
$this->buttons[] = $button;
}
/**
* Add a hidden input.
* @param string $name
* @param string $value
*/
public function addHiddenInput(string $name, string $value) {
$this->hiddenitems[$name] = $value;
}
/**
* Generate the form HTML.
* @param bool $echo If false, returns HTML string instead of outputting it.
*/
public function generate(bool $echo = true) {
$html = <<<HTMLTOP
<form action="$this->action" method="$this->method" id="$this->id">
<div class="card">
<h3 class="card-header d-flex">
<div>
<i class="$this->icon"></i> $this->title
</div>
</h3>
<div class="card-body">
<div class="row">
HTMLTOP;
foreach ($this->items as $item) {
$required = $item["required"] ? "required" : "";
$id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
$pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
if (empty($item['type'])) {
$item['type'] = "text";
}
$itemhtml = "";
$itemlabel = "";
if ($item['type'] == "textarea") {
$itemlabel = "<label class=\"mb-0\"><i class=\"$item[icon]\"></i> $item[label]:</label>";
} else if ($item['type'] != "checkbox") {
$itemlabel = "<label class=\"mb-0\">$item[label]:</label>";
}
$strippedlabel = strip_tags($item['label']);
$itemhtml .= <<<ITEMTOP
\n\n <div class="col-12 col-md-$item[width]">
<div class="form-group mb-3">
$itemlabel
ITEMTOP;
$inputgrouptop = <<<INPUTG
\n <div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="$item[icon]"></i></span>
</div>
INPUTG;
switch ($item['type']) {
case "select":
$itemhtml .= $inputgrouptop;
$itemhtml .= <<<SELECT
\n <select class="form-control" name="$item[name]" aria-label="$strippedlabel" $required>
SELECT;
foreach ($item['options'] as $value => $label) {
$selected = "";
if (!empty($item['value']) && $value == $item['value']) {
$selected = " selected";
}
$itemhtml .= "\n <option value=\"$value\"$selected>$label</option>";
}
$itemhtml .= "\n </select>";
break;
case "checkbox":
$itemhtml .= $inputgrouptop;
$itemhtml .= <<<CHECKBOX
\n <div class="form-group form-check">
<input type="checkbox" name="$item[name]" $id class="form-check-input" value="$item[value]" $required aria-label="$strippedlabel">
<label class="form-check-label">$item[label]</label>
</div>
CHECKBOX;
break;
case "textarea":
$val = htmlentities($item['value']);
$itemhtml .= <<<TEXTAREA
\n <textarea class="form-control" id="info" name="$item[name]" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $required>$val</textarea>
TEXTAREA;
break;
default:
$itemhtml .= $inputgrouptop;
$itemhtml .= <<<INPUT
\n <input type="$item[type]" name="$item[name]" $id class="form-control" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $pattern value="$item[value]" $required />
INPUT;
break;
}
if (!empty($item["error"])) {
$itemhtml .= <<<ERROR
\n <div class="invalid-feedback">
$item[error]
</div>
ERROR;
}
if ($item["type"] != "textarea") {
$itemhtml .= "\n </div>";
}
$itemhtml .= <<<ITEMBOTTOM
\n </div>
</div>\n
ITEMBOTTOM;
$html .= $itemhtml;
}
$html .= <<<HTMLBOTTOM
</div>
</div>
HTMLBOTTOM;
if (!empty($this->buttons)) {
$html .= "\n <div class=\"card-footer d-flex\">";
foreach ($this->buttons as $btn) {
$btnhtml = "";
$inner = "<i class=\"$btn[icon]\"></i> $btn[text]";
$id = empty($btn['id']) ? "" : "id=\"$btn[id]\"";
if (!empty($btn['href'])) {
$btnhtml = "<a href=\"$btn[href]\" class=\"$btn[class]\" $id>$inner</a>";
} else {
$name = empty($btn['name']) ? "" : "name=\"$btn[name]\"";
$value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : "";
$btnhtml = "<button type=\"$btn[type]\" class=\"$btn[class]\" $id $name $value>$inner</button>";
}
$html .= "\n $btnhtml";
}
$html .= "\n </div>";
}
$html .= "\n </div>";
foreach ($this->hiddenitems as $name => $value) {
$value = htmlentities($value);
$html .= "\n <input type=\"hidden\" name=\"$name\" value=\"$value\" />";
}
$html .= "\n</form>\n";
if ($echo) {
echo $html;
}
return $html;
}
}

View File

@ -1,135 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
class IPUtils {
/**
* Check if a given ipv4 address is in a given cidr
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return boolean true if the ip is in this range / false if not.
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
*/
public static function ip4_in_cidr($ip, $cidr) {
if (strpos($cidr, '/') == false) {
$cidr .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
list( $cidr, $netmask ) = explode('/', $cidr, 2);
$range_decimal = ip2long($cidr);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
}
/**
* Check if a given ipv6 address is in a given cidr
* @param string $ip IP to check in IPV6 format
* @param string $cidr CIDR netmask
* @return boolean true if the IP is in this range, false otherwise.
* @author MW. <https://stackoverflow.com/a/7952169>
*/
public static function ip6_in_cidr($ip, $cidr) {
$address = inet_pton($ip);
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
$subnetMask = explode("/", $cidr)[1];
$addr = str_repeat("f", $subnetMask / 4);
switch ($subnetMask % 4) {
case 0:
break;
case 1:
$addr .= "8";
break;
case 2:
$addr .= "c";
break;
case 3:
$addr .= "e";
break;
}
$addr = str_pad($addr, 32, '0');
$addr = pack("H*", $addr);
$binMask = $addr;
return ($address & $binMask) == $subnetAddress;
}
/**
* Check if the REMOTE_ADDR is on Cloudflare's network.
* @return boolean true if it is, otherwise false
*/
public static function validateCloudflare() {
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// Using IPv6
$cloudflare_ips_v6 = [
"2400:cb00::/32",
"2405:8100::/32",
"2405:b500::/32",
"2606:4700::/32",
"2803:f800::/32",
"2c0f:f248::/32",
"2a06:98c0::/29"
];
$valid = false;
foreach ($cloudflare_ips_v6 as $cidr) {
if ($this::ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
} else {
// Using IPv4
$cloudflare_ips_v4 = [
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"104.16.0.0/12",
"108.162.192.0/18",
"131.0.72.0/22",
"141.101.64.0/18",
"162.158.0.0/15",
"172.64.0.0/13",
"173.245.48.0/20",
"188.114.96.0/20",
"190.93.240.0/20",
"197.234.240.0/22",
"198.41.128.0/17"
];
$valid = false;
foreach ($cloudflare_ips_v4 as $cidr) {
if ($this::ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
}
return $valid;
}
/**
* Makes a good guess at the client's real IP address.
*
* @return string Client IP or `0.0.0.0` if we can't find anything
*/
public static function getClientIP() {
// If CloudFlare is in the mix, we should use it.
// Check if the request is actually from CloudFlare before trusting it.
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
if ($this::validateCloudflare()) {
return $_SERVER["HTTP_CF_CONNECTING_IP"];
}
}
if (isset($_SERVER["REMOTE_ADDR"])) {
return $_SERVER["REMOTE_ADDR"];
}
return "0.0.0.0"; // This will not happen unless we aren't a web server
}
}

View File

@ -1,72 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class Log {
/**
*
* @global $database
* @param int/LogType $type Either an integer (as defined by the constants in class LogType) or a LogType object.
* @param int/User $user Either a UID number or a User object.
* @param string $data Extra data to include in the log, in addition to the timestamp, log type, user, and IP address.
*/
public static function insert($type, $user, string $data = "") {
global $database;
// find IP address
$ip = IPUtils::getClientIP();
if (gettype($type) == "object" && is_a($type, "LogType")) {
$type = $type->getType();
}
if (is_a($user, "User")) {
$uid = $user->getUID();
} else if (gettype($user) == "integer") {
$uid = $user;
} else {
$uid = null;
}
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
}
}
class LogType {
const LOGIN_OK = 1;
const LOGIN_FAILED = 2;
const PASSWORD_CHANGED = 3;
const API_LOGIN_OK = 4;
const API_LOGIN_FAILED = 5;
const BAD_2FA = 6;
const API_BAD_2FA = 7;
const BAD_CAPTCHA = 8;
const ADDED_2FA = 9;
const REMOVED_2FA = 10;
const LOGOUT = 11;
const API_AUTH_OK = 12;
const API_AUTH_FAILED = 13;
const API_BAD_KEY = 14;
const LOG_CLEARED = 15;
const USER_REMOVED = 16;
const USER_ADDED = 17;
const USER_EDITED = 18;
const MOBILE_LOGIN_OK = 19;
const MOBILE_LOGIN_FAILED = 20;
const MOBILE_BAD_KEY = 21;
private $type;
function __construct(int $type) {
$this->type = $type;
}
public function getType(): int {
return $type;
}
}

View File

@ -1,71 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class Login {
const BAD_USERPASS = 1;
const BAD_2FA = 2;
const ACCOUNT_DISABLED = 3;
const LOGIN_OK = 4;
public static function auth(string $username, string $password, string $twofa = ""): int {
global $database;
$username = strtolower($username);
$user = User::byUsername($username);
if (!$user->exists()) {
return Login::BAD_USERPASS;
}
if (!$user->checkPassword($password)) {
return Login::BAD_USERPASS;
}
if ($user->has2fa()) {
if (!$user->check2fa($twofa)) {
return Login::BAD_2FA;
}
}
switch ($user->getStatus()->get()) {
case AccountStatus::TERMINATED:
return Login::BAD_USERPASS;
case AccountStatus::LOCKED_OR_DISABLED:
return Login::ACCOUNT_DISABLED;
case AccountStatus::NORMAL:
default:
return Login::LOGIN_OK;
}
return Login::LOGIN_OK;
}
public static function verifyCaptcha(string $session, string $answer, string $url): bool {
$data = [
'session_id' => $session,
'answer_id' => $answer,
'action' => "verify"
];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
$resp = json_decode($result, TRUE);
if (!$resp['result']) {
return false;
} else {
return true;
}
}
}

View File

@ -1,33 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class LoginKey {
public static function generate(string $appname, $appicon = null): string {
global $database;
do {
$code = base64_encode(random_bytes(32));
} while ($database->has('userloginkeys', ['key' => $code]));
$database->insert('userloginkeys', ['key' => $code, 'expires' => date("Y-m-d H:i:s", time() + 600), 'appname' => $appname, 'appicon' => $appicon]);
return $code;
}
public static function getuid(string $code): int {
global $database;
if (!$database->has('userloginkeys', ["AND" => ['key' => $code, 'uid[!]' => null]])) {
throw new Exception();
}
$uid = $database->get('userloginkeys', 'uid', ['key' => $code]);
return $uid;
}
}

View File

@ -1,99 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class Notifications {
/**
* Add a new notification.
* @global $database
* @param User $user
* @param string $title
* @param string $content
* @param string $timestamp If left empty, the current date and time will be used.
* @param string $url
* @param bool $sensitive If true, the notification is marked as containing sensitive content, and the $content might be hidden on lockscreens and other non-secure places.
* @return int The newly-created notification ID.
* @throws Exception
*/
public static function add(User $user, string $title, string $content, string $timestamp = "", string $url = "", bool $sensitive = false): int {
global $database, $Strings;
if ($user->exists()) {
if (empty($title) || empty($content)) {
throw new Exception($Strings->get("invalid parameters", false));
}
$timestamp = date("Y-m-d H:i:s");
if (!empty($timestamp)) {
$timestamp = date("Y-m-d H:i:s", strtotime($timestamp));
}
$database->insert('notifications', ['uid' => $user->getUID(), 'timestamp' => $timestamp, 'title' => $title, 'content' => $content, 'url' => $url, 'seen' => 0, 'sensitive' => $sensitive]);
return $database->id() * 1;
}
throw new Exception($Strings->get("user does not exist", false));
}
/**
* Fetch all notifications for a user.
* @global $database
* @param User $user
* @param bool $all If false, only returns unseen notifications.
* @return array
* @throws Exception
*/
public static function get(User $user, bool $all = true) {
global $database, $Strings;
if ($user->exists()) {
if ($all) {
$notifications = $database->select('notifications', ['notificationid (id)', 'timestamp', 'title', 'content', 'url', 'seen', 'sensitive'], ['uid' => $user->getUID(), 'ORDER' => ['seen', 'timestamp' => 'DESC']]);
} else {
$notifications = $database->select('notifications', ['notificationid (id)', 'timestamp', 'title', 'content', 'url', 'seen', 'sensitive'], ["AND" => ['uid' => $user->getUID(), 'seen' => 0], 'ORDER' => ['timestamp' => 'DESC']]);
}
for ($i = 0; $i < count($notifications); $i++) {
$notifications[$i]['id'] = $notifications[$i]['id'] * 1;
$notifications[$i]['seen'] = ($notifications[$i]['seen'] == "1" ? true : false);
$notifications[$i]['sensitive'] = ($notifications[$i]['sensitive'] == "1" ? true : false);
}
return $notifications;
}
throw new Exception($Strings->get("user does not exist", false));
}
/**
* Mark the notification identified by $id as read.
* @global $database
* @global $Strings
* @param User $user
* @param int $id
* @throws Exception
*/
public static function read(User $user, int $id) {
global $database, $Strings;
if ($user->exists()) {
if ($database->has('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]])) {
$database->update('notifications', ['seen' => 1], ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]]);
return true;
}
throw new Exception($Strings->get("invalid parameters", false));
}
throw new Exception($Strings->get("user does not exist", false));
}
public static function delete(User $user, int $id) {
global $database, $Strings;
if ($user->exists()) {
if ($database->has('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]])) {
$database->delete('notifications', ['AND' => ['uid' => $user->getUID(), 'notificationid' => $id]]);
return true;
}
throw new Exception($Strings->get("invalid parameters", false));
}
throw new Exception($Strings->get("user does not exist", false));
}
}

View File

@ -1,31 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class RandomString {
/**
* Generate a random string, using a cryptographically secure
* pseudorandom number generator (random_int)
*
* From https://stackoverflow.com/a/31107425
*
* @param int $length How many characters do we want?
* @param string $keyspace A string of all possible characters
* to select from
* @return string
*/
public static function generate(int $length, string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string {
$pieces = [];
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < $length; ++$i) {
$pieces [] = $keyspace[random_int(0, $max)];
}
return implode('', $pieces);
}
}

View File

@ -1,19 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
class Session {
public static function start(User $user) {
$_SESSION['username'] = $user->getUsername();
$_SESSION['uid'] = $user->getUID();
$_SESSION['email'] = $user->getEmail();
$_SESSION['realname'] = $user->getName();
$_SESSION['loggedin'] = true;
}
}

View File

@ -1,122 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
/**
* Provides translated language strings.
*/
class Strings {
private $language = "en";
private $strings = [];
public function __construct($language = "en") {
if (!preg_match("/[a-zA-Z\_\-]+/", $language)) {
throw new Exception("Invalid language code $language");
}
$this->load("en");
if ($language == "en") {
return;
}
if (file_exists(__DIR__ . "/../langs/$language/")) {
$this->language = $language;
$this->load($language);
} else {
trigger_error("Language $language could not be found.", E_USER_WARNING);
}
}
/**
* Load all JSON files for the specified language.
* @param string $language
*/
private function load(string $language) {
$files = glob(__DIR__ . "/../langs/$language/*.json");
foreach ($files as $file) {
$strings = json_decode(file_get_contents($file), true);
foreach ($strings as $key => $val) {
if (array_key_exists($key, $this->strings)) {
trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING);
}
$this->strings[$key] = $val;
}
}
}
/**
* Add language strings dynamically.
* @param array $strings ["key" => "value", ...]
*/
public function addStrings(array $strings) {
foreach ($strings as $key => $val) {
$this->strings[$key] = $val;
}
}
/**
* I18N string getter. If the key isn't found, it outputs the key itself.
* @param string $key
* @param bool $echo True to echo the result, false to return it. Default is true.
* @return string
*/
public function get(string $key, bool $echo = true): string {
$str = $key;
if (array_key_exists($key, $this->strings)) {
$str = $this->strings[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
}
if ($echo) {
echo $str;
}
return $str;
}
/**
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
* @param string $key
* @param array $replace key-value array of replacements.
* If the string value is "hello {abc}" and you give ["abc" => "123"], the
* result will be "hello 123".
* @param bool $echo True to echo the result, false to return it. Default is true.
* @return string
*/
public function build(string $key, array $replace, bool $echo = true): string {
$str = $key;
if (array_key_exists($key, $this->strings)) {
$str = $this->strings[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
}
foreach ($replace as $find => $repl) {
$str = str_replace("{" . $find . "}", $repl, $str);
}
if ($echo) {
echo $str;
}
return $str;
}
/**
* Builds and returns a JSON key:value string for the supplied array of keys.
* @param array $keys ["key1", "key2", ...]
*/
public function getJSON(array $keys): string {
$strings = [];
foreach ($keys as $k) {
$strings[$k] = $this->get($k, false);
}
return json_encode($strings);
}
}

View File

@ -1,329 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
use Base32\Base32;
use OTPHP\TOTP;
class User {
private $uid = null;
private $username;
private $passhash;
private $email;
private $realname;
private $authsecret;
private $has2fa = false;
private $exists = false;
private $apppasswords = [];
public function __construct(int $uid, string $username = "") {
global $database;
if ($database->has('accounts', ['AND' => ['uid' => $uid, 'deleted' => false]])) {
$this->uid = $uid;
$user = $database->get('accounts', ['username', 'password', 'email', 'realname', 'authsecret'], ['uid' => $uid]);
$this->username = $user['username'];
$this->passhash = $user['password'];
$this->email = $user['email'];
$this->realname = $user['realname'];
$this->authsecret = $user['authsecret'];
$this->has2fa = !empty($user['authsecret']);
$this->exists = true;
$this->apppasswords = $database->select('apppasswords', 'hash', ['uid' => $this->uid]);
} else {
$this->uid = $uid;
$this->username = $username;
}
}
public static function byUsername(string $username): User {
global $database;
$username = strtolower($username);
if ($database->has('accounts', ['AND' => ['username' => $username, 'deleted' => false]])) {
$uid = $database->get('accounts', 'uid', ['username' => $username]);
return new self($uid * 1);
}
return new self(-1, $username);
}
/**
* Add a user to the system. /!\ Assumes input is OK /!\
* @param string $username Username, saved in lowercase.
* @param string $password Password, will be hashed before saving.
* @param string $realname User's real legal name
* @param string $email User's email address.
* @param string $phone1 Phone number #1
* @param string $phone2 Phone number #2
* @param int $type Account type
* @return int The new user's ID number in the database.
*/
public static function add(string $username, string $password, string $realname, string $email = null, string $phone1 = "", string $phone2 = "", int $type = 1): int {
global $database;
$database->insert('accounts', [
'username' => strtolower($username),
'password' => (is_null($password) ? null : password_hash($password, PASSWORD_BCRYPT)),
'realname' => $realname,
'email' => $email,
'phone1' => $phone1,
'phone2' => $phone2,
'acctstatus' => 1,
'accttype' => $type
]);
return $database->id();
}
public function exists(): bool {
return $this->exists;
}
public function has2fa(): bool {
return $this->has2fa;
}
function getUsername() {
return $this->username;
}
function getUID() {
return $this->uid;
}
function getEmail() {
return $this->email;
}
function getName() {
return $this->realname;
}
/**
* Check the given plaintext password against the stored hash.
* @param string $password
* @return bool
*/
function checkPassword(string $password): bool {
return password_verify($password, $this->passhash);
}
/**
* Check the given password against the user's app passwords.
* @param string $apppassword
* @return bool
*/
function checkAppPassword(string $apppassword): bool {
foreach ($this->apppasswords as $hash) {
if (password_verify($apppassword, $hash)) {
return true;
}
}
return false;
}
/**
* Change the user's password.
* @global $database $database
* @param string $old The current password
* @param string $new The new password
* @param string $new2 New password again
* @throws PasswordMatchException
* @throws PasswordMismatchException
* @throws IncorrectPasswordException
* @throws WeakPasswordException
*/
function changePassword(string $old, string $new, string $new2) {
global $database, $SETTINGS;
if ($old == $new) {
throw new PasswordMatchException();
}
if ($new != $new2) {
throw new PasswordMismatchException();
}
if (!$this->checkPassword($old)) {
throw new IncorrectPasswordException();
}
require_once __DIR__ . "/worst_passwords.php";
$passrank = checkWorst500List($new);
if ($passrank !== FALSE) {
throw new WeakPasswordException();
}
if (strlen($new) < $SETTINGS['min_password_length']) {
throw new WeakPasswordException();
}
$database->update('accounts', ['password' => password_hash($new, PASSWORD_DEFAULT), 'acctstatus' => 1], ['uid' => $this->uid]);
Log::insert(LogType::PASSWORD_CHANGED, $this);
return true;
}
function check2fa(string $code): bool {
if (!$this->has2fa) {
return true;
}
$totp = new TOTP(null, $this->authsecret);
$time = time();
if ($totp->verify($code, $time)) {
return true;
}
if ($totp->verify($code, $time - 30)) {
return true;
}
if ($totp->verify($code, $time + 30)) {
return true;
}
return false;
}
/**
* Generate a TOTP secret for the given user.
* @return string OTP provisioning URI (for generating a QR code)
*/
function generate2fa(): string {
global $SETTINGS;
$secret = random_bytes(20);
$encoded_secret = Base32::encode($secret);
$totp = new TOTP((empty($this->email) ? $this->realname : $this->email), $encoded_secret);
$totp->setIssuer($SETTINGS['system_name']);
return $totp->getProvisioningUri();
}
/**
* Save a TOTP secret for the user.
* @global $database $database
* @param string $username
* @param string $secret
*/
function save2fa(string $secret) {
global $database;
$database->update('accounts', ['authsecret' => $secret], ['username' => $this->username]);
}
/**
* Check if the given username has the given permission (or admin access)
* @global $database $database
* @param string $code
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
*/
function hasPermission(string $code): bool {
global $database;
return $database->has('assigned_permissions', [
'[>]permissions' => [
'permid' => 'permid'
]
], ['AND' => ['OR' => ['permcode #code' => $code, 'permcode #admin' => 'ADMIN'], 'uid' => $this->uid]]) === TRUE;
}
/**
* Get the account status.
* @return \AccountStatus
*/
function getStatus(): AccountStatus {
global $database;
$statuscode = $database->get('accounts', 'acctstatus', ['uid' => $this->uid]);
return new AccountStatus($statuscode);
}
function sendAlertEmail(string $appname = null) {
global $SETTINGS, $Strings;
if (is_null($appname)) {
$appname = $SETTINGS['site_title'];
}
if (empty($SETTINGS["email"]["admin_email"]) || filter_var($SETTINGS["email"]["admin_email"], FILTER_VALIDATE_EMAIL) === FALSE) {
return "invalid_to_email";
}
if (empty($SETTINGS["email"]["from"]) || filter_var($SETTINGS["email"]["from"], FILTER_VALIDATE_EMAIL) === FALSE) {
return "invalid_from_email";
}
$mail = new PHPMailer;
if ($SETTINGS['debug']) {
$mail->SMTPDebug = 2;
}
if ($SETTINGS['email']['use_smtp']) {
$mail->isSMTP();
$mail->Host = $SETTINGS['email']['host'];
$mail->SMTPAuth = $SETTINGS['email']['auth'];
$mail->Username = $SETTINGS['email']['user'];
$mail->Password = $SETTINGS['email']['password'];
$mail->SMTPSecure = $SETTINGS['email']['secure'];
$mail->Port = $SETTINGS['email']['port'];
if ($SETTINGS['email']['allow_invalid_certificate']) {
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
}
}
$mail->setFrom($SETTINGS["email"]["from"], 'Account Alerts');
$mail->addAddress($SETTINGS["email"]["admin_email"], "System Admin");
$mail->isHTML(false);
$mail->Subject = $Strings->get("admin alert email subject", false);
$mail->Body = $Strings->build("admin alert email message", ["username" => $this->username, "datetime" => date("Y-m-d H:i:s"), "ipaddr" => IPUtils::getClientIP(), "appname" => $appname], false);
if (!$mail->send()) {
return $mail->ErrorInfo;
}
return true;
}
}
class AccountStatus {
const NORMAL = 1;
const LOCKED_OR_DISABLED = 2;
const CHANGE_PASSWORD = 3;
const TERMINATED = 4;
const ALERT_ON_ACCESS = 5;
private $status;
public function __construct(int $status) {
$this->status = $status;
}
/**
* Get the account status/state as an integer.
* @return int
*/
public function get(): int {
return $this->status;
}
/**
* Get the account status/state as a string representation.
* @return string
*/
public function getString(): string {
switch ($this->status) {
case self::NORMAL:
return "NORMAL";
case self::LOCKED_OR_DISABLED:
return "LOCKED_OR_DISABLED";
case self::CHANGE_PASSWORD:
return "CHANGE_PASSWORD";
case self::TERMINATED:
return "TERMINATED";
case self::ALERT_ON_ACCESS:
return "ALERT_ON_ACCESS";
default:
return "OTHER_" . $this->status;
}
}
}

542
lib/login.php Normal file
View File

@ -0,0 +1,542 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* Authentication and account functions
*/
use Base32\Base32;
use OTPHP\TOTP;
use LdapTools\LdapManager;
use LdapTools\Object\LdapObjectType;
$ldap = new LdapManager($ldap_config);
////////////////////////////////////////////////////////////////////////////////
// Account handling //
////////////////////////////////////////////////////////////////////////////////
/**
* Add a user to the system. /!\ Assumes input is OK /!\
* @param string $username Username, saved in lowercase.
* @param string $password Password, will be hashed before saving.
* @param string $realname User's real legal name
* @param string $email User's email address.
* @param string $phone1 Phone number #1
* @param string $phone2 Phone number #2
* @param int $type Account type
* @return int The new user's ID number in the database.
*/
function adduser($username, $password, $realname, $email = null, $phone1 = "", $phone2 = "", $type = 1) {
global $database;
$database->insert('accounts', [
'username' => strtolower($username),
'password' => (is_null($password) ? null : encryptPassword($password)),
'realname' => $realname,
'email' => $email,
'phone1' => $phone1,
'phone2' => $phone2,
'acctstatus' => 1,
'accttype' => $type
]);
//var_dump($database->error());
return $database->id();
}
/**
* Change the password for the current user.
* @global $database $database
* @global LdapManager $ldap
* @param string $old The current password
* @param string $new The new password
* @param string $new2 New password again
* @param [string] $error If the function returns false, this will have an array
* with a message ID from `lang/messages.php` and (depending on the message) an
* extra string for that message.
* @return boolean true if the password is changed, else false
*/
function change_password($old, $new, $new2, &$error) {
global $database, $ldap;
// make sure the new password isn't the same as the current one
if ($old == $new) {
$error = ["passwords_same"];
return false;
}
// Make sure the new passwords are the same
if ($new != $new2) {
$error = ["new_password_mismatch"];
return false;
}
// check the current password
$login_ok = authenticate_user($_SESSION['username'], $old, $errmsg, $errcode);
// Allow login if the error is due to expired password
if (!$login_ok && ($errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_EXPIRED || $errcode == LdapTools\Connection\ADResponseCodes::ACCOUNT_PASSWORD_MUST_CHANGE)) {
$login_ok = true;
}
if ($login_ok) {
// Check the new password and make sure it's not stupid
require_once __DIR__ . "/worst_passwords.php";
$passrank = checkWorst500List($new);
if ($passrank !== FALSE) {
$error = ["password_500", $passrank];
return false;
}
if (strlen($new) < MIN_PASSWORD_LENGTH) {
$error = ["weak_password"];
return false;
}
// Figure out how to change the password, then do it
$acctloc = account_location($_SESSION['username']);
if ($acctloc == "LOCAL") {
$database->update('accounts', ['password' => encryptPassword($new), 'acctstatus' => 1], ['uid' => $_SESSION['uid']]);
$_SESSION['password'] = $new;
insertAuthLog(3, $_SESSION['uid']);
return true;
} else if ($acctloc == "LDAP") {
try {
$repository = $ldap->getRepository(LdapObjectType::USER);
$user = $repository->findOneByUsername($_SESSION['username']);
$user->setPassword($new);
$user->setpasswordMustChange(false);
$ldap->persist($user);
$database->update('accounts', ['acctstatus' => 1], ['uid' => $_SESSION['uid']]);
insertAuthLog(3, $_SESSION['uid']);
$_SESSION['password'] = $new;
return true;
} catch (\Exception $e) {
// Stupid password complexity BS error
if (strpos($e->getMessage(), "DSID-031A11E5") !== FALSE) {
$error = ["password_complexity"];
return false;
}
$error = ["ldap_error", $e->getMessage()];
return false;
}
}
$error = ["account_state_error"];
return false;
}
$error = ["old_password_mismatch"];
return false;
}
/**
* Get where a user's account actually is.
* @param string $username
* @return string "LDAP", "LOCAL", "LDAP_ONLY", or "NONE".
*/
function account_location($username) {
global $database;
$username = strtolower($username);
$user_exists_local = user_exists_local($username);
if (!$user_exists_local && !LDAP_ENABLED) {
return "NONE";
}
if ($user_exists_local) {
$userinfo = $database->select('accounts', ['password'], ['username' => $username])[0];
// if password empty, it's an LDAP user
if (!is_empty($userinfo['password'])) {
return "LOCAL";
} else if (is_empty($userinfo['password']) && LDAP_ENABLED) {
return "LDAP";
} else {
return "NONE";
}
} else {
if (user_exists_ldap($username)) {
return "LDAP_ONLY";
} else {
return "NONE";
}
}
}
/**
* Checks the given credentials against the database.
* @param string $username
* @param string $password
* @return boolean True if OK, else false
*/
function authenticate_user($username, $password, &$errormsg = null, &$errorcode = null) {
global $database;
global $ldap;
$username = strtolower($username);
if (is_empty($username) || is_empty($password)) {
return "NONE";
}
$loc = account_location($username, $password);
switch ($loc) {
case "LOCAL":
$hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
return (comparePassword($password, $hash));
case "LDAP":
return authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE;
case "LDAP_ONLY":
// Authenticate with LDAP and create database account
try {
if (authenticate_user_ldap($username, $password, $errormsg, $errorcode) === TRUE) {
$user = $ldap->getRepository('user')->findOneByUsername($username);
adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null), "", "", 2);
return true;
}
return false;
} catch (Exception $e) {
$errormsg = $e->getMessage();
return false;
}
default:
return false;
}
}
function user_exists($username) {
return account_location(strtolower($username)) !== "NONE";
}
/**
* Check if a username exists in the local database.
* @param String $username
*/
function user_exists_local($username) {
global $database;
$username = strtolower($username);
return $database->has('accounts', ['username' => $username]) === TRUE;
}
/**
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
* CHANGE_PASSWORD, ALERT_ON_ACCESS, or OTHER
* @global $database $database
* @param string $username
* @param string $password
* @return string
*/
function get_account_status($username, &$error = null) {
global $database;
$username = strtolower($username);
$loc = account_location($username);
if ($loc == "LOCAL") {
$statuscode = $database->select('accounts', [
'[>]acctstatus' => [
'acctstatus' => 'statusid'
]
], [
'accounts.acctstatus',
'acctstatus.statuscode'
], [
'username' => $username,
"LIMIT" => 1
]
)[0]['statuscode'];
return $statuscode;
} else if ($loc == "LDAP" || $loc == "LDAP_ONLY") {
return get_account_status_ldap($username, $error);
} else {
// account isn't setup properly
return "OTHER";
}
}
/**
* Check if the given username has the given permission (or admin access)
* @global $database $database
* @param string $username
* @param string $permcode
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
*/
function account_has_permission($username, $permcode) {
global $database;
return $database->has('assigned_permissions', [
'[>]accounts' => [
'uid' => 'uid'
],
'[>]permissions' => [
'permid' => 'permid'
]
], ['AND' => ['OR' => ['permcode #code' => $permcode, 'permcode #admin' => 'ADMIN'], 'username' => $username]]) === TRUE;
}
////////////////////////////////////////////////////////////////////////////////
// Login handling //
////////////////////////////////////////////////////////////////////////////////
/**
* Setup $_SESSION values to log in a user
* @param string $username
*/
function doLoginUser($username, $password) {
global $database;
$username = strtolower($username);
$userinfo = $database->select('accounts', ['email', 'uid', 'realname'], ['username' => $username])[0];
$_SESSION['username'] = $username;
$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['email'] = $userinfo['email'];
$_SESSION['realname'] = $userinfo['realname'];
$_SESSION['password'] = $password; // needed for accessing data in other apps
$_SESSION['loggedin'] = true;
}
/**
* Send an alert email to the system admin
*
* Used when an account with the status ALERT_ON_ACCESS logs in
* @param String $username the account username
* @return Mixed TRUE if successful, error string if not
*/
function sendLoginAlertEmail($username, $appname = SITE_TITLE) {
if (is_empty(ADMIN_EMAIL) || filter_var(ADMIN_EMAIL, FILTER_VALIDATE_EMAIL) === FALSE) {
return "invalid_to_email";
}
if (is_empty(FROM_EMAIL) || filter_var(FROM_EMAIL, FILTER_VALIDATE_EMAIL) === FALSE) {
return "invalid_from_email";
}
$username = strtolower($username);
$mail = new PHPMailer;
if (DEBUG) {
$mail->SMTPDebug = 2;
}
if (USE_SMTP) {
$mail->isSMTP();
$mail->Host = SMTP_HOST;
$mail->SMTPAuth = SMTP_AUTH;
$mail->Username = SMTP_USER;
$mail->Password = SMTP_PASS;
$mail->SMTPSecure = SMTP_SECURE;
$mail->Port = SMTP_PORT;
if (SMTP_ALLOW_INVALID_CERTIFICATE) {
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
}
}
$mail->setFrom(FROM_EMAIL, 'Account Alerts');
$mail->addAddress(ADMIN_EMAIL, "System Admin");
$mail->isHTML(false);
$mail->Subject = lang("admin alert email subject", false);
$mail->Body = lang2("admin alert email message", ["username" => $username, "datetime" => date("Y-m-d H:i:s"), "ipaddr" => getClientIP(), "appname" => $appname], false);
if (!$mail->send()) {
return $mail->ErrorInfo;
}
return TRUE;
}
function insertAuthLog($type, $uid = null, $data = "") {
global $database;
// find IP address
$ip = getClientIP();
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
}
function verifyReCaptcha($response) {
try {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', "https://www.google.com/recaptcha/api/siteverify", [
'form_params' => [
'secret' => RECAPTCHA_SECRET_KEY,
'response' => $response
]
]);
if ($response->getStatusCode() != 200) {
return false;
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['success'] === true) {
return true;
} else {
return false;
}
} catch (Exception $e) {
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
// LDAP handling //
////////////////////////////////////////////////////////////////////////////////
/**
* Checks the given credentials against the LDAP server.
* @param string $username
* @param string $password
* @return mixed True if OK, else false or the error code from the server
*/
function authenticate_user_ldap($username, $password, &$errormsg = null, &$errorcode = null) {
global $ldap;
if (is_empty($username) || is_empty($password)) {
return false;
}
$username = strtolower($username);
try {
$msg = "";
$code = 0;
if ($ldap->authenticate($username, $password, $msg, $code) === TRUE) {
$errormsg = $msg;
$errorcode = $code;
return true;
} else {
$errormsg = $msg;
$errorcode = $code;
return $msg;
}
} catch (Exception $e) {
$errormsg = $e->getMessage();
return $e->getMessage();
}
}
/**
* Check if a username exists on the LDAP server.
* @global type $ldap_config
* @param type $username
* @return boolean true if yes, else false
*/
function user_exists_ldap($username) {
global $ldap;
try {
$username = strtolower($username);
$lqb = $ldap->buildLdapQuery();
$result = $lqb->fromUsers()
->where(['username' => $username])
->getLdapQuery()
->getResult();
if (count($result) > 0) {
return true;
}
return false;
} catch (Exception $e) {
return false;
}
}
function get_account_status_ldap($username, &$error = null) {
global $ldap;
try {
$username = strtolower($username);
$normal = $ldap->buildLdapQuery()
->fromUsers()
->where(['enabled' => true, 'passwordMustChange' => false, 'locked' => false, 'disabled' => false, 'username' => $username])
->getLdapQuery()
->getResult();
if (count($normal) == 1) {
return "NORMAL";
}
$disabled = $ldap->buildLdapQuery()
->fromUsers()
->where(['disabled' => true, 'username' => $username])
->getLdapQuery()
->getResult();
$locked = $ldap->buildLdapQuery()
->fromUsers()
->where(['locked' => true, 'username' => $username])
->getLdapQuery()
->getResult();
if (count($disabled) == 1 || count($locked) == 1) {
return "LOCKED_OR_DISABLED";
}
$passwordExpired = $ldap->buildLdapQuery()
->fromUsers()
->where(['passwordMustChange' => true, 'username' => $username])
->getLdapQuery()
->getResult();
if (count($passwordExpired) == 1) {
return "CHANGE_PASSWORD";
}
$other = $ldap->buildLdapQuery()
->fromUsers()
->where(['username' => $username])
->getLdapQuery()
->getResult();
if (count($other) == 0) {
return false;
} else {
return "OTHER";
}
} catch (Exception $e) {
$error = $e->getMessage();
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
// 2-factor authentication //
////////////////////////////////////////////////////////////////////////////////
/**
* Check if a user has TOTP setup
* @global $database $database
* @param string $username
* @return boolean true if TOTP secret exists, else false
*/
function userHasTOTP($username) {
global $database;
$username = strtolower($username);
$secret = $database->select('accounts', 'authsecret', ['username' => $username])[0];
if (is_empty($secret)) {
return false;
}
return true;
}
/**
* Generate a TOTP secret for the given user.
* @param string $username
* @return string OTP provisioning URI (for generating a QR code)
*/
function newTOTP($username) {
global $database;
$username = strtolower($username);
$secret = random_bytes(20);
$encoded_secret = Base32::encode($secret);
$userdata = $database->select('accounts', ['email', 'authsecret', 'realname'], ['username' => $username])[0];
$totp = new TOTP((is_null($userdata['email']) ? $userdata['realname'] : $userdata['email']), $encoded_secret);
$totp->setIssuer(SYSTEM_NAME);
return $totp->getProvisioningUri();
}
/**
* Save a TOTP secret for the user.
* @global $database $database
* @param string $username
* @param string $secret
*/
function saveTOTP($username, $secret) {
global $database;
$username = strtolower($username);
$database->update('accounts', ['authsecret' => $secret], ['username' => $username]);
}
/**
* Verify a TOTP multiauth code
* @global $database
* @param string $username
* @param int $code
* @return boolean true if it's legit, else false
*/
function verifyTOTP($username, $code) {
global $database;
$username = strtolower($username);
$userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
if (is_empty($userdata['authsecret'])) {
return false;
}
$totp = new TOTP(null, $userdata['authsecret']);
return $totp->verify($code);
}

View File

@ -1,169 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
require_once __DIR__ . "/../required.php";
if (empty($_GET['code']) || empty($_GET['redirect'])) {
die("Bad request.");
}
// Delete old keys to keep the table small and tidy
$database->delete("userloginkeys", ["expires[<]" => date("Y-m-d H:i:s")]);
if (!$database->has("userloginkeys", ["AND" => ["key" => $_GET["code"]], "expires[>]" => date("Y-m-d H:i:s"), "uid" => null])) {
header("Location: $_GET[redirect]");
die("Invalid auth code.");
}
$APPINFO = $database->get("userloginkeys", ["appname", "appicon"], ["key" => $_GET["code"]]);
$APPNAME = $APPINFO["appname"];
$APPICON = $APPINFO["appicon"];
if (empty($_SESSION['thisstep'])) {
$_SESSION['thisstep'] = "username";
}
if (!empty($_GET['reset'])) {
$_SESSION['thisstep'] = "username";
$_SESSION['check'] = "";
header("Location: ./?code=$_GET[code]&redirect=$_GET[redirect]");
}
$error = "";
function sendUserBack($code, $url, $uid) {
global $database;
$_SESSION['check'] = null;
$_SESSION['thisstep'] = null;
$_SESSION['login_uid'] = null;
$_SESSION['login_pwd'] = null;
$database->update("userloginkeys", ["uid" => $uid], ["key" => $code]);
Log::insert(LogType::LOGIN_OK, $uid);
header("Location: $url");
die("<a href=\"" . htmlspecialchars($url) . "\">Click here</a>");
}
if (!empty($_SESSION['check'])) {
switch ($_SESSION['check']) {
case "username":
if (empty($_POST['username'])) {
$_SESSION['thisstep'] = "username";
break;
}
$user = User::byUsername($_POST['username']);
if ($user->exists()) {
$_SESSION['login_uid'] = $user->getUID();
switch ($user->getStatus()->get()) {
case AccountStatus::LOCKED_OR_DISABLED:
$error = $Strings->get("account locked", false);
break;
case AccountStatus::TERMINATED:
$error = $Strings->get("account terminated", false);
break;
case AccountStatus::ALERT_ON_ACCESS:
$mail_resp = $user->sendAlertEmail();
case AccountStatus::NORMAL:
$_SESSION['thisstep'] = "password";
break;
case AccountStatus::CHANGE_PASSWORD:
$_SESSION['thisstep'] = "change_password";
break;
}
} else {
$error = $Strings->get("Username not found.", false);
Log::insert(LogType::LOGIN_FAILED, null, "Username: " . $user->getUsername());
}
break;
case "password":
if (empty($_POST['password'])) {
$_SESSION['thisstep'] = "password";
break;
}
if (empty($_SESSION['login_uid'])) {
$_SESSION['thisstep'] = "username";
break;
}
$user = new User($_SESSION['login_uid']);
if ($user->checkPassword($_POST['password'])) {
$_SESSION['login_pwd'] = true;
if ($user->has2fa()) {
$_SESSION['thisstep'] = "totp";
} else {
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
}
} else {
$error = $Strings->get("Password incorrect.", false);
if ($user->checkAppPassword($_POST['password'])) {
$error = $Strings->get("App passwords are not allowed here.", false);
}
Log::insert(LogType::LOGIN_FAILED, $user);
}
break;
case "change_password":
if (empty($_POST['oldpassword']) || empty($_POST['newpassword']) || empty($_POST['newpassword2'])) {
$_SESSION['thisstep'] = "change_password";
$error = $Strings->get("Fill in all three boxes.", false);
break;
}
$user = new User($_SESSION['login_uid']);
try {
$result = $user->changePassword($_POST['oldpassword'], $_POST['newpassword'], $_POST['newpassword2']);
if ($result === TRUE) {
if ($user->has2fa()) {
$_SESSION['thisstep'] = "totp";
} else {
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
}
}
} catch (PasswordMatchException $e) {
$error = $Strings->get(MESSAGES["passwords_same"]["string"], false);
} catch (PasswordMismatchException $e) {
$error = $Strings->get(MESSAGES["new_password_mismatch"]["string"], false);
} catch (IncorrectPasswordException $e) {
$error = $Strings->get(MESSAGES["old_password_mismatch"]["string"], false);
} catch (WeakPasswordException $e) {
$error = $Strings->get(MESSAGES["weak_password"]["string"], false);
}
break;
case "totp":
if (empty($_POST['totp']) || empty($_SESSION['login_uid'])) {
$_SESSION['thisstep'] = "username";
break;
}
$user = new User($_SESSION['login_uid']);
if ($user->check2fa($_POST['totp'])) {
sendUserBack($_GET['code'], $_GET['redirect'], $_SESSION['login_uid']);
} else {
$error = $Strings->get("Code incorrect.", false);
Log::insert(LogType::BAD_2FA, null, "Username: " . $user->getUsername());
}
break;
}
}
include __DIR__ . "/parts/header.php";
switch ($_SESSION['thisstep']) {
case "username":
require __DIR__ . "/parts/username.php";
break;
case "password":
require __DIR__ . "/parts/password.php";
break;
case "change_password":
require __DIR__ . "/parts/change_password.php";
break;
case "totp":
require __DIR__ . "/parts/totp.php";
break;
}
include __DIR__ . "/parts/footer.php";

View File

@ -1,51 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$_SESSION['check'] = "change_password";
$username = (new User($_SESSION['login_uid']))->getUsername();
?>
<form action="" method="POST">
<div>
<?php $Strings->get("password expired"); ?>
</div>
<div class="form-group">
<label for="oldpassword"><?php $Strings->build("Current password for {user}", ["user" => htmlentities($username)]); ?></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
</div>
<input type="password" class="form-control" id="oldpassword" name="oldpassword" placeholder="" required autofocus>
</div>
</div>
<div class="form-group">
<label for="newpassword"><?php $Strings->get("New password"); ?></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
</div>
<input type="password" class="form-control" id="newpassword" name="newpassword" placeholder="" required>
</div>
</div>
<div class="form-group">
<label for="newpassword2"><?php $Strings->get("New password (again)"); ?></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
</div>
<input type="password" class="form-control" id="newpassword2" name="newpassword2" placeholder="" required>
</div>
</div>
<div class="d-flex">
<button type="submit" class="btn btn-primary ml-auto">
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
</button>
</div>
</form>

View File

@ -1,15 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
?>
</div>
</div>
</div>
</div>
</div>
<script src="../static/js/fontawesome-all.min.js"></script>

View File

@ -1,59 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
header("Link: <../static/fonts/Roboto.css>; rel=preload; as=style", false);
header("Link: <../static/css/bootstrap.min.css>; rel=preload; as=style", false);
header("Link: <../static/css/login.css>; rel=preload; as=style", false);
header("Link: <../static/css/svg-with-js.min.css>; rel=preload; as=style", false);
header("Link: <../static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
?>
<!DOCTYPE html>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo $SETTINGS['site_title']; ?></title>
<link rel="icon" href="../static/img/logo.svg">
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
<link href="../static/css/login.css" rel="stylesheet">
<link href="../static/css/svg-with-js.min.css" rel="stylesheet">
<div class="container mt-4">
<div class="row justify-content-center">
<?php
if (!empty($APPICON)) {
?>
<div class="col-12 text-center">
<img class="banner-image" src="<?php echo $APPICON; ?>" />
</div>
<?php
} else {
?>
<div class="col-12">
<div class="blank-image"></div>
</div>
<?php
}
?>
<div class="col-12 text-center">
<h1 class="display-5 mb-4"><?php $Strings->build("Login to {app}", ["app" => htmlentities($APPNAME)]); ?></h1>
</div>
<div class="col-12 col-sm-8 col-lg-6">
<div class="card mt-4">
<div class="card-body">
<?php
if (!empty($error)) {
?>
<div class="text-danger">
<?php echo $error; ?>
</div>
<?php
}
?>

View File

@ -1,32 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$_SESSION['check'] = "password";
$username = (new User($_SESSION['login_uid']))->getUsername();
?>
<form action="" method="POST">
<div class="form-group">
<label for="password"><?php $Strings->build("Password for {user}", ["user" => htmlentities($username)]); ?></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
</div>
<input type="password" class="form-control" id="password" name="password" aria-describedby="passwordHelp" placeholder="" required autofocus>
</div>
<small id="passwordHelp" class="form-text text-muted">Enter your password.</small>
</div>
<div class="d-flex">
<a href="./?code=<?php echo htmlentities($_GET['code']); ?>&amp;redirect=<?php echo htmlentities($_GET['redirect']); ?>&amp;reset=1" class="btn btn-link mr-2">
<i class="fas fa-chevron-left"></i> <?php $Strings->get("Back"); ?>
</a>
<button type="submit" class="btn btn-primary ml-auto">
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
</button>
</div>
</form>

View File

@ -1,31 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$_SESSION['check'] = "totp";
?>
<form action="" method="POST">
<div class="form-group">
<label for="totp"><?php $Strings->get("Two-factor code"); ?></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-mobile-alt"></i></span>
</div>
<input type="text" class="form-control" id="totp" name="totp" aria-describedby="totpHelp" placeholder="" required autofocus>
</div>
<small id="passwordHelp" class="form-text text-muted">Enter the two-factor code from your mobile device.</small>
</div>
<div class="d-flex">
<a href="./?code=<?php echo htmlentities($_GET['code']); ?>&amp;redirect=<?php echo htmlentities($_GET['redirect']); ?>&amp;reset=1" class="btn btn-link mr-2">
<i class="fas fa-chevron-left"></i> <?php $Strings->get("Back"); ?>
</a>
<button type="submit" class="btn btn-primary ml-auto">
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
</button>
</div>
</form>

View File

@ -1,37 +0,0 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
$_SESSION['check'] = "username";
?>
<form action="" method="POST">
<div class="form-group">
<label for="username"><?php $Strings->get("username"); ?></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user"></i></span>
</div>
<input type="text" class="form-control" id="username" name="username" aria-describedby="usernameHelp" placeholder="" required autofocus>
</div>
<small id="usernameHelp" class="form-text text-muted">Enter your username.</small>
</div>
<div class="d-flex">
<div class="ml-auto">
<?php
if ($SETTINGS['signups_enabled'] === true) {
?>
<a href="../signup/?code=<?php echo urlencode($_GET["code"]); ?>&amp;redirect=<?php echo urlencode($_GET["redirect"]); ?>" class="btn btn-link mr-2"><?php $Strings->get("Create Account"); ?></a>
<?php
}
?>
<button type="submit" class="btn btn-primary">
<i class="fas fa-chevron-right"></i> <?php $Strings->get("continue"); ?>
</button>
</div>
</div>
</form>

View File

@ -10,6 +10,8 @@
require __DIR__ . "/../required.php";
require __DIR__ . "/../lib/login.php";
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
@ -18,12 +20,12 @@ if ($VARS['action'] == "ping") {
exit(json_encode(["status" => "OK"]));
}
if ($SETTINGS['mobile_enabled'] !== TRUE) {
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
if (MOBILE_ENABLED !== TRUE) {
exit(json_encode(["status" => "ERROR", "msg" => lang("mobile login disabled", false)]));
}
// Make sure we have a username and access key
if (empty($VARS['username']) || empty($VARS['key'])) {
if (is_empty($VARS['username']) || is_empty($VARS['key'])) {
http_response_code(401);
die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
}
@ -32,19 +34,12 @@ $username = strtolower($VARS['username']);
$key = strtoupper($VARS['key']);
// Make sure the username and key are actually legit
// Don't check key if we're trying to generate one
if ($VARS['action'] == "generatesynccode") {
if (!User::byUsername($username)->exists()) {
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
}
} else {
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $key, 'accounts.username' => $username]]);
if ($user_key_valid !== TRUE) {
engageRateLimit();
Log::insert(LogType::MOBILE_BAD_KEY, null, "Username: " . $username . ", Key: " . $key);
die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
}
$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $key, 'accounts.username' => $username]]);
if ($user_key_valid !== TRUE) {
engageRateLimit();
//http_response_code(401);
insertAuthLog(21, null, "Username: " . $username . ", Key: " . $key);
die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
}
// Obscure key
@ -63,50 +58,58 @@ switch ($VARS['action']) {
case "check_password":
// Check if the user-supplied password is valid.
engageRateLimit();
$user = User::byUsername($username);
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
if (get_account_status($username) != "NORMAL") {
insertAuthLog(20, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => lang("login failed try on web", false)]));
}
if ($user->checkPassword($VARS['password'])) {
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
exit(json_encode(["status" => "OK", "uid" => $user->getUID()]));
if (authenticate_user($username, $VARS['password'], $autherror)) {
$uid = $database->get("accounts", "uid", ["username" => $username]);
insertAuthLog(19, $uid, "Key: " . $key);
exit(json_encode(["status" => "OK", "uid" => $uid]));
} else {
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
if (!is_empty($autherror)) {
insertAuthLog(20, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $autherror]));
} else {
insertAuthLog(20, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
}
}
case "user_info":
engageRateLimit();
$user = User::byUsername($username);
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
if (get_account_status($username) != "NORMAL") {
insertAuthLog(20, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => lang("login failed try on web", false)]));
}
if ($user->checkPassword($VARS['password'])) {
$userinfo = ["uid" => $user->getUID(), "username" => $user->getUsername(), "realname" => $user->getName(), "email" => $user->getEmail()];
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
if (authenticate_user($username, $VARS['password'], $autherror)) {
$userinfo = $database->get("accounts", ["uid", "username", "realname", "email"], ["username" => $username]);
insertAuthLog(19, $userinfo['uid'], "Key: " . $key);
exit(json_encode(["status" => "OK", "info" => $userinfo]));
} else {
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
if (!is_empty($autherror)) {
insertAuthLog(20, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $autherror]));
} else {
insertAuthLog(20, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
}
}
case "start_session":
// Do a web login.
engageRateLimit();
$user = User::byUsername($username);
if ($user->exists()) {
if ($user->getStatus()->get() == AccountStatus::NORMAL) {
if ($user->checkPassword($VARS['password'])) {
Session::start($user);
if (user_exists($username)) {
if (get_account_status($username) == "NORMAL") {
if (authenticate_user($username, $VARS['password'], $autherror)) {
doLoginUser($username, $VARS['password']);
$_SESSION['mobile'] = true;
exit(json_encode(["status" => "OK"]));
}
}
}
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
insertAuthLog(20, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
case "listapps":
$apps = $SETTINGS['apps'];
$apps = EXTERNAL_APPS;
// Format paths as absolute URLs
foreach ($apps as $k => $v) {
if (strpos($apps[$k]['url'], "http") === FALSE) {
@ -114,130 +117,6 @@ switch ($VARS['action']) {
}
}
exit(json_encode(["status" => "OK", "apps" => $apps]));
case "gencode":
engageRateLimit();
$user = User::byUsername($username);
$code = "";
do {
$code = random_int(100000, 999999);
} while ($database->has("onetimekeys", ["key" => $code]));
$database->insert("onetimekeys", ["key" => $code, "uid" => $user->getUID(), "expires" => date("Y-m-d H:i:s", strtotime("+1 minute"))]);
$database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
exit(json_encode(["status" => "OK", "code" => $code]));
case "checknotifications":
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
try {
$notifications = Notifications::get($user, false);
exit(json_encode(["status" => "OK", "notifications" => $notifications]));
} catch (Exception $ex) {
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
}
break;
case "readnotification":
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
if (empty($VARS['id'])) {
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
}
try {
Notifications::read($user, $VARS['id']);
exit(json_encode(["status" => "OK"]));
} catch (Exception $ex) {
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
}
break;
case "addnotification":
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
try {
$timestamp = "";
if (!empty($VARS['timestamp'])) {
$timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
}
$url = "";
if (!empty($VARS['url'])) {
$url = $VARS['url'];
}
$nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));
exit(json_encode(["status" => "OK", "id" => $nid]));
} catch (Exception $ex) {
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
}
break;
case "deletenotification":
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
if (empty($VARS['id'])) {
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
}
try {
Notifications::delete($user, $VARS['id']);
exit(json_encode(["status" => "OK"]));
} catch (Exception $ex) {
exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
}
break;
case "hasotp":
if (!empty($VARS['username'])) {
$user = User::byUsername($VARS['username']);
} else if (!empty($VARS['uid'])) {
$user = new User($VARS['uid']);
} else {
http_response_code(400);
die("\"400 Bad Request\"");
}
exit(json_encode(["status" => "OK", "otp" => $user->has2fa()]));
break;
case "generatesynccode":
$user = User::byUsername($username);
if ($user->has2fa()) {
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("2-factor is enabled, you need to use the QR code or manual setup for security reasons", false)]));
}
if ($user->getStatus()->get() != AccountStatus::NORMAL) {
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
}
if ($user->checkPassword($VARS['password'])) {
Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
$desc = htmlspecialchars($VARS['desc']);
$database->insert('mobile_codes', ['uid' => $user->getUID(), 'code' => $code, 'description' => $desc]);
exit(json_encode(["status" => "OK", "code" => $code]));
} else {
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
}
default:
http_response_code(404);
die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));

View File

@ -1,9 +0,0 @@
<#if licenseFirst??>
${licenseFirst}
</#if>
${licensePrefix}This Source Code Form is subject to the terms of the Mozilla Public
${licensePrefix}License, v. 2.0. If a copy of the MPL was not distributed with this
${licensePrefix}file, You can obtain one at http://mozilla.org/MPL/2.0/.
<#if licenseLast??>
${licenseLast}
</#if>

View File

@ -1,6 +1,5 @@
include.path=${php.global.include.path}
php.version=PHP_70
project.licensePath=./nbproject/mplheader.txt
source.encoding=UTF-8
src.dir=.
tags.asp=false

View File

@ -7,27 +7,37 @@
// List of pages and metadata
define("PAGES", [
"home" => [
"title" => "Home",
"title" => "home",
"navbar" => true,
"icon" => "fas fa-home",
"styles" => [
"static/css/dock.css"
]
"icon" => "home"
],
"security" => [
"title" => "account security",
"title" => "account options",
"navbar" => true,
"icon" => "fas fa-lock",
"styles" => [
"static/css/qrcode.css"
]
],
"sync" => [
"title" => "sync",
"navbar" => true,
"icon" => "fas fa-sync"
"icon" => "cogs"
],
"404" => [
"title" => "404 error"
"title" => "404 error",
"navbar" => false
]
]);
// Which apps to load on a given page
define("APPS", [
"home" => [
"taskfloor_tasks",
"qwikclock_inout",
"taskfloor_messages",
"inventory_link",
"account_security"
],
"security" => [
"sync_mobile",
"change_password",
"setup_2fa"
],
"404" => [
"404_error"
]
]);

View File

@ -1,10 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
?>
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
<div class="alert alert-warning"><b><?php $Strings->get("404 error");?></b><br /> <?php $Strings->get("page not found"); ?></div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More