forked from Business/AccountHub
Compare commits
204 Commits
V2_Rewrite
...
master
Author | SHA1 | Date | |
---|---|---|---|
356044fb2d | |||
368cb6bbbf | |||
14a4fe7c46 | |||
2749dfee32 | |||
033bb50298 | |||
7e1439dd3c | |||
26b16ccbe6 | |||
3c6851e38e | |||
bc0665b022 | |||
367062b76c | |||
81ad5e653e | |||
a454dac629 | |||
589364201c | |||
b36b4080f5 | |||
ac1ad47aba | |||
bfae187a59 | |||
88cb51f1bb | |||
f27812997f | |||
42460d2165 | |||
59136bd8eb | |||
53e158b553 | |||
474047ab34 | |||
922ea55cdb | |||
c97e058786 | |||
26a662c399 | |||
289aaeaa9f | |||
df79def142 | |||
de12184bf4 | |||
61acc9710b | |||
3ca062d995 | |||
04702f6090 | |||
22fb97d0c4 | |||
99f2e07f63 | |||
29fb7feb85 | |||
7d30251cd6 | |||
7173a50c36 | |||
e66280e07a | |||
3ed75822a1 | |||
7531dc362d | |||
b250908663 | |||
5f7d45e812 | |||
0fd1aa2b54 | |||
892102528b | |||
a89c663ca9 | |||
69c634ea99 | |||
a514e66969 | |||
5b98d3e00a | |||
d853082cdb | |||
c7aad627ac | |||
6ceeeaa087 | |||
4600c87787 | |||
f1c36fdeb1 | |||
d36b340692 | |||
67388884f0 | |||
223a431e8b | |||
d7ca7125ce | |||
1729b842ba | |||
4d2b78bdba | |||
106e697fc3 | |||
e0802f582b | |||
93098309cb | |||
6d4144c78d | |||
51de8283b8 | |||
016c71d30d | |||
2698fc794e | |||
ba1369d842 | |||
2836a05f90 | |||
27502ed710 | |||
3d3e975519 | |||
a559901ac0 | |||
74971a4592 | |||
16be9438b9 | |||
bb5639c447 | |||
3f32258ba0 | |||
129efd13c7 | |||
c179ed7ebb | |||
f1a85f47fd | |||
61d660be69 | |||
20a6c6f143 | |||
5b7ab65946 | |||
fb25c4395a | |||
13b60de915 | |||
0e094809fa | |||
ca179b89ea | |||
32cd18933d | |||
4f1b81ff4b | |||
cb3c8aaf2d | |||
f1f682780c | |||
b55eaea821 | |||
1d81bfb83d | |||
ec44a6740f | |||
e714286e5a | |||
bcc41b887d | |||
eee5af3081 | |||
47539de2d7 | |||
12aea4a2e2 | |||
4c135d6e59 | |||
34f49bfd01 | |||
d4621de80f | |||
39ccaa2f2d | |||
f43f986e25 | |||
c36c365a1b | |||
8dd7ee6005 | |||
80d0a017ed | |||
a17f51b72d | |||
1271317eb9 | |||
2caec48e4c | |||
0c7b4a31f1 | |||
963fbfbf00 | |||
10575f6f59 | |||
2f9eccf931 | |||
769d24b4b7 | |||
66aa3d6fdc | |||
ee0c0f65e3 | |||
66fa86e04e | |||
dafc3b76ea | |||
8e65d4c98d | |||
9dd9f9297c | |||
41c8b6c16b | |||
be34857d71 | |||
58a991cbd0 | |||
023480bf88 | |||
4bab466169 | |||
d4070b36b9 | |||
2d98c68efd | |||
2461fec102 | |||
fa6924eb08 | |||
a0d2293a3d | |||
a9eb59c936 | |||
64c3d47c32 | |||
6a7ea5eeb7 | |||
ca6e1f2c5a | |||
038a712b88 | |||
64add57446 | |||
d10c6214a6 | |||
2a0d5bc92b | |||
d193f3df4a | |||
814c0dbc0f | |||
386615976e | |||
35e531a56b | |||
644e5c2e37 | |||
0691dd51f1 | |||
8441008219 | |||
e155ebe165 | |||
c7c7e4e4ea | |||
ee47026c0e | |||
eefa7ab00f | |||
121a49e9e0 | |||
ce8e0fb4e3 | |||
abb306a36e | |||
2e3cfb9546 | |||
d54ebed189 | |||
7c44b18854 | |||
b505a74502 | |||
37789df696 | |||
5277c5e0fb | |||
5dae7bc168 | |||
a04207da62 | |||
c0a93fb666 | |||
496b213a88 | |||
bf76d3733c | |||
8d2ac32419 | |||
d229aee8d0 | |||
5588ee494d | |||
2770e96a8a | |||
3d01bf8feb | |||
0405f695f3 | |||
4ad42bfe48 | |||
0c85643847 | |||
d918af5d65 | |||
89c6c720f1 | |||
542bb80d85 | |||
2284117cfc | |||
b9f385d6f0 | |||
3ab33d19f6 | |||
bdcb7e263d | |||
47f51420fa | |||
0b811feccb | |||
e8c9cd56e2 | |||
d6df9d582c | |||
455a199d78 | |||
ab53d719da | |||
2f31066a0c | |||
ea71b78169 | |||
427390cbc4 | |||
f14393a8a4 | |||
bae6d1ac17 | |||
de4dcc37bc | |||
eaeb8806a1 | |||
8b9407c274 | |||
501f127f04 | |||
dcd495f4e4 | |||
c6941c7bd3 | |||
17c587d3b2 | |||
ef6bddddeb | |||
591b5e6ff1 | |||
8afe41070b | |||
292ce29b31 | |||
71347c33f1 | |||
35df787547 | |||
ba5ba051e9 | |||
e28d3a93ac | |||
3110011596 | |||
16cbf2a5f1 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,4 +4,4 @@
|
||||
/nbproject/private
|
||||
*.sync-conflict*
|
||||
test*
|
||||
/conf/
|
||||
/conf/
|
||||
|
18
LICENSE.md
18
LICENSE.md
@ -1,19 +1,7 @@
|
||||
Copyright (c) 2018 Netsyms Technologies.
|
||||
Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved.
|
||||
|
||||
If you modify and redistribute this project, you must replace the branding
|
||||
assets with your own.
|
||||
|
||||
The branding assets include:
|
||||
* the application icon
|
||||
* the Netsyms N punchcard logo
|
||||
* the Netsyms for Business graph logo
|
||||
|
||||
If you are unsure if your usage is allowed, please contact us:
|
||||
https://netsyms.com/contact
|
||||
legal@netsyms.com
|
||||
|
||||
All other portions of this application,
|
||||
unless otherwise noted (in comments, headers, etc), are licensed as follows:
|
||||
Licensed under the Mozilla Public License Version 2.0. Files without MPL header
|
||||
comments, including third party code, may be under a different license.
|
||||
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
18
action.php
18
action.php
@ -23,11 +23,11 @@ dieifnotloggedin();
|
||||
|
||||
function returnToSender($msg, $arg = "") {
|
||||
global $VARS;
|
||||
if ($arg == "") {
|
||||
header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg");
|
||||
} else {
|
||||
header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg&arg=" . urlencode($arg));
|
||||
$header = "Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg";
|
||||
if ($arg != "") {
|
||||
$header .= "&arg=$arg";
|
||||
}
|
||||
header($header);
|
||||
die();
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ switch ($VARS['action']) {
|
||||
case "signout":
|
||||
Log::insert(LogType::LOGOUT, $_SESSION['uid']);
|
||||
session_destroy();
|
||||
header('Location: index.php');
|
||||
header('Location: index.php?logout=1');
|
||||
die("Logged out.");
|
||||
case "chpasswd":
|
||||
engageRateLimit();
|
||||
@ -70,7 +70,7 @@ switch ($VARS['action']) {
|
||||
returnToSender("new_pin_mismatch");
|
||||
break;
|
||||
case "add2fa":
|
||||
if (is_empty($VARS['secret'])) {
|
||||
if (empty($VARS['secret'])) {
|
||||
returnToSender("invalid_parameters");
|
||||
}
|
||||
$user = new User($_SESSION['uid']);
|
||||
@ -113,4 +113,8 @@ switch ($VARS['action']) {
|
||||
returnToSender("invalid_parameters#notifications");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "resetfeedkey":
|
||||
$database->delete('userkeys', ['AND' => ['uid' => $_SESSION['uid'], 'typeid' => 1]]);
|
||||
returnToSender("feed_key_reset");
|
||||
break;
|
||||
}
|
||||
|
437
api.php
437
api.php
@ -4,439 +4,6 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Simple JSON API to allow other apps to access 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';
|
||||
header("Content-Type: application/json");
|
||||
|
||||
|
||||
if (empty($VARS['key'])) {
|
||||
die("\"403 Unauthorized\"");
|
||||
} 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);
|
||||
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;
|
||||
}
|
||||
|
||||
if (empty($VARS['action'])) {
|
||||
http_response_code(404);
|
||||
die(json_encode("No action specified."));
|
||||
}
|
||||
|
||||
switch ($VARS['action']) {
|
||||
case "ping":
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
break;
|
||||
case "auth":
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
Log::insert(LogType::API_AUTH_OK, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK", "msg" => $Strings->get("login successful", false)]));
|
||||
} 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:
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account locked", false)]));
|
||||
case AccountStatus::TERMINATED:
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)]));
|
||||
case AccountStatus::CHANGE_PASSWORD:
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("password expired", false)]));
|
||||
case AccountStatus::NORMAL:
|
||||
break;
|
||||
default:
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account state error", false)]));
|
||||
}
|
||||
}
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
break;
|
||||
case "userinfo":
|
||||
if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else if (!empty($VARS['uid']) && is_numeric($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
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);
|
||||
exit(json_encode(["status" => "OK", "data" => $data]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
break;
|
||||
case "userexists":
|
||||
if (!empty($VARS['uid']) && is_numeric($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
|
||||
exit(json_encode(["status" => "OK", "exists" => $user->exists()]));
|
||||
break;
|
||||
case "hastotp":
|
||||
exit(json_encode(["status" => "OK", "otp" => User::byUsername($VARS['username'])->has2fa()]));
|
||||
break;
|
||||
case "verifytotp":
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->check2fa($VARS['code'])) {
|
||||
exit(json_encode(["status" => "OK", "valid" => true]));
|
||||
} else {
|
||||
Log::insert(LogType::API_BAD_2FA, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("2fa incorrect", false), "valid" => false]));
|
||||
}
|
||||
break;
|
||||
case "acctstatus":
|
||||
exit(json_encode(["status" => "OK", "account" => User::byUsername($VARS['username'])->getStatus()->getString()]));
|
||||
case "login":
|
||||
// simulate a login, checking account status and alerts
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
switch ($user->getStatus()->getString()) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account locked", false)]));
|
||||
case "TERMINATED":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account terminated", false)]));
|
||||
case "CHANGE_PASSWORD":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("password expired", false)]));
|
||||
case "NORMAL":
|
||||
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
case "ALERT_ON_ACCESS":
|
||||
$user->sendAlertEmail();
|
||||
Log::insert(LogType::API_LOGIN_OK, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "OK", "alert" => true]));
|
||||
default:
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("account state error", false)]));
|
||||
}
|
||||
} else {
|
||||
Log::insert(LogType::API_LOGIN_FAILED, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
break;
|
||||
case "ismanagerof":
|
||||
if ($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()) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['manager']]));
|
||||
}
|
||||
if (!$employee->exists()) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false), "user" => $VARS['employee']]));
|
||||
}
|
||||
|
||||
if ($database->has('managers', ['AND' => ['managerid' => $manager->getUID(), 'employeeid' => $employee->getUID()]])) {
|
||||
exit(json_encode(["status" => "OK", "managerof" => true]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "OK", "managerof" => false]));
|
||||
}
|
||||
break;
|
||||
case "getmanaged":
|
||||
if (!empty($VARS['uid'])) {
|
||||
$manager = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$manager = User::byUsername($VARS['username']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if (!$manager->exists()) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]));
|
||||
}
|
||||
if ($VARS['get'] == "username") {
|
||||
$managed = $database->select('managers', ['[>]accounts' => ['employeeid' => 'uid']], 'username', ['managerid' => $manager->getUID()]);
|
||||
} else {
|
||||
$managed = $database->select('managers', 'employeeid', ['managerid' => $manager->getUID()]);
|
||||
}
|
||||
exit(json_encode(["status" => "OK", "employees" => $managed]));
|
||||
break;
|
||||
case "getmanagers":
|
||||
if (!empty($VARS['uid'])) {
|
||||
$emp = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$emp = User::byUsername($VARS['username']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if (!$emp->exists()) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]));
|
||||
}
|
||||
$managers = $database->select('managers', 'managerid', ['employeeid' => $emp->getUID()]);
|
||||
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 (empty($VARS['code'])) {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$perm = $VARS['code'];
|
||||
if (!empty($VARS['uid'])) {
|
||||
$user = new User($VARS['uid']);
|
||||
} else if (!empty($VARS['username'])) {
|
||||
$user = User::byUsername($VARS['username']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if (!$user->exists()) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("user does not exist", false)]));
|
||||
}
|
||||
exit(json_encode(["status" => "OK", "has_permission" => $user->hasPermission($perm)]));
|
||||
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::byUsername($VARS['username'])->exists()) {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$appname = "???";
|
||||
if (!is_empty($VARS['appname'])) {
|
||||
$appname = $VARS['appname'];
|
||||
}
|
||||
$result = User::byUsername($VARS['username'])->sendAlertEmail($appname);
|
||||
if ($result === TRUE) {
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
}
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $result]));
|
||||
case "codelogin":
|
||||
$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']]);
|
||||
exit(json_encode(["status" => "OK", "user" => $user]));
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("no such code or code expired", false)]));
|
||||
}
|
||||
case "listapps":
|
||||
$apps = EXTERNAL_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'];
|
||||
}
|
||||
}
|
||||
exit(json_encode(["status" => "OK", "apps" => $apps]));
|
||||
case "getusersbygroup":
|
||||
if ($VARS['gid']) {
|
||||
if ($database->has("groups", ['groupid' => $VARS['gid']])) {
|
||||
$groupid = $VARS['gid'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("group does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
if ($VARS['get'] == "username") {
|
||||
$users = $database->select('assigned_groups', ['[>]accounts' => ['uid' => 'uid']], 'username', ['groupid' => $groupid, "ORDER" => "username"]);
|
||||
} else if ($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]);
|
||||
}
|
||||
exit(json_encode(["status" => "OK", "users" => $users]));
|
||||
break;
|
||||
case "getgroupsbyuser":
|
||||
if ($VARS['uid']) {
|
||||
if ($database->has("accounts", ['uid' => $VARS['uid']])) {
|
||||
$empid = $VARS['uid'];
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("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" => $Strings->get("user does not exist", false)]));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
$groups = $database->select('assigned_groups', ["[>]groups" => ["groupid" => "groupid"]], ['groups.groupid (id)', 'groups.groupname (name)'], ['uid' => $empid]);
|
||||
exit(json_encode(["status" => "OK", "groups" => $groups]));
|
||||
break;
|
||||
case "getgroups":
|
||||
$groups = $database->select('groups', ['groupid (id)', 'groupname (name)']);
|
||||
exit(json_encode(["status" => "OK", "groups" => $groups]));
|
||||
break;
|
||||
case "groupsearch":
|
||||
if (is_empty($VARS['search']) || strlen($VARS['search']) < 2) {
|
||||
exit(json_encode(["status" => "OK", "result" => []]));
|
||||
}
|
||||
$data = $database->select('groups', ['groupid (id)', 'groupname (name)'], ['groupname[~]' => $VARS['search'], "LIMIT" => 10]);
|
||||
exit(json_encode(["status" => "OK", "result" => $data]));
|
||||
break;
|
||||
case "checkpin":
|
||||
$pin = "";
|
||||
if (is_empty($VARS['pin'])) {
|
||||
http_response_code(400);
|
||||
die("\"400 Bad Request\"");
|
||||
}
|
||||
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 ($user->exists()) {
|
||||
$pin = $database->get("accounts", "pin", ["uid" => $user->getUID()]);
|
||||
} else {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
}
|
||||
if (is_null($pin) || $pin == "") {
|
||||
exit(json_encode(["status" => "ERROR", "pinvalid" => false, "nopinset" => true]));
|
||||
}
|
||||
exit(json_encode(["status" => "OK", "pinvalid" => ($pin == $VARS['pin'])]));
|
||||
break;
|
||||
case "getnotifications":
|
||||
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);
|
||||
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;
|
||||
default:
|
||||
http_response_code(404);
|
||||
die(json_encode("404 Not Found: the requested action is not available."));
|
||||
}
|
||||
// Load in new API from legacy location (a.k.a. here)
|
||||
require __DIR__ . "/api/index.php";
|
||||
|
5
api/.htaccess
Normal file
5
api/.htaccess
Normal file
@ -0,0 +1,5 @@
|
||||
# Rewrite for Nextcloud Notes API
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT]
|
||||
</IfModule>
|
9
api/actions/acctstatus.php
Normal file
9
api/actions/acctstatus.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?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()]);
|
14
api/actions/addapppassword.php
Normal file
14
api/actions/addapppassword.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?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]);
|
29
api/actions/addnotification.php
Normal file
29
api/actions/addnotification.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?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");
|
||||
}
|
18
api/actions/alertemail.php
Normal file
18
api/actions/alertemail.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?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");
|
39
api/actions/auth.php
Normal file
39
api/actions/auth.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?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");
|
||||
}
|
15
api/actions/checkloginkey.php
Normal file
15
api/actions/checkloginkey.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?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");
|
||||
}
|
24
api/actions/checkpin.php
Normal file
24
api/actions/checkpin.php
Normal 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/.
|
||||
*/
|
||||
|
||||
$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'])]);
|
15
api/actions/codelogin.php
Normal file
15
api/actions/codelogin.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?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");
|
||||
}
|
20
api/actions/deletenotification.php
Normal file
20
api/actions/deletenotification.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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");
|
||||
}
|
10
api/actions/getgroups.php
Normal file
10
api/actions/getgroups.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?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]);
|
23
api/actions/getgroupsbyuser.php
Normal file
23
api/actions/getgroupsbyuser.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?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]);
|
22
api/actions/getloginkey.php
Normal file
22
api/actions/getloginkey.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?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]);
|
23
api/actions/getmanaged.php
Normal file
23
api/actions/getmanaged.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?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]);
|
19
api/actions/getmanagers.php
Normal file
19
api/actions/getmanagers.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?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]);
|
20
api/actions/getnotifications.php
Normal file
20
api/actions/getnotifications.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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");
|
||||
}
|
29
api/actions/getusersbygroup.php
Normal file
29
api/actions/getusersbygroup.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?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]);
|
13
api/actions/groupsearch.php
Normal file
13
api/actions/groupsearch.php
Normal 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/.
|
||||
*/
|
||||
|
||||
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]);
|
9
api/actions/hastotp.php
Normal file
9
api/actions/hastotp.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?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()]);
|
27
api/actions/ismanagerof.php
Normal file
27
api/actions/ismanagerof.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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]);
|
||||
}
|
16
api/actions/listapps.php
Normal file
16
api/actions/listapps.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?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]);
|
46
api/actions/login.php
Normal file
46
api/actions/login.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?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)]);
|
||||
}
|
9
api/actions/mobileenabled.php
Normal file
9
api/actions/mobileenabled.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?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']]);
|
15
api/actions/mobilevalid.php
Normal file
15
api/actions/mobilevalid.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?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]);
|
19
api/actions/permission.php
Normal file
19
api/actions/permission.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?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)]);
|
9
api/actions/ping.php
Normal file
9
api/actions/ping.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?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();
|
25
api/actions/readnotification.php
Normal file
25
api/actions/readnotification.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?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");
|
||||
}
|
15
api/actions/userexists.php
Normal file
15
api/actions/userexists.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?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()]);
|
20
api/actions/userinfo.php
Normal file
20
api/actions/userinfo.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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");
|
||||
}
|
13
api/actions/usersearch.php
Normal file
13
api/actions/usersearch.php
Normal 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/.
|
||||
*/
|
||||
|
||||
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]);
|
15
api/actions/verifytotp.php
Normal file
15
api/actions/verifytotp.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?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]);
|
||||
}
|
267
api/apisettings.php
Normal file
267
api/apisettings.php
Normal file
@ -0,0 +1,267 @@
|
||||
<?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"
|
||||
],
|
||||
];
|
164
api/functions.php
Normal file
164
api/functions.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?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;
|
||||
}
|
88
api/index.php
Normal file
88
api/index.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?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"];
|
99
app.php
99
app.php
@ -1,11 +1,14 @@
|
||||
<?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.");
|
||||
@ -14,7 +17,7 @@ if ($_SESSION['loggedin'] != true) {
|
||||
require_once __DIR__ . "/pages.php";
|
||||
|
||||
$pageid = "home";
|
||||
if (isset($_GET['page']) && !is_empty($_GET['page'])) {
|
||||
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")) {
|
||||
@ -24,14 +27,19 @@ if (isset($_GET['page']) && !is_empty($_GET['page'])) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
header("Link: <static/css/fa-svg-with-js.css>; rel=preload; as=style", false);
|
||||
header("Link: <static/js/fontawesome-all.min.js>; rel=preload; as=script", 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.min.js>; rel=preload; as=script", false);
|
||||
header("Link: <static/js/bootstrap.bundle.min.js>; rel=preload; as=script", false);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@ -40,18 +48,28 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
|
||||
<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>
|
||||
<title><?php echo $SETTINGS['site_title']; ?></title>
|
||||
|
||||
<link rel="icon" href="static/img/logo.svg">
|
||||
<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">
|
||||
<link href="static/css/fa-svg-with-js.css" rel="stylesheet">
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
FontAwesomeConfig = {autoAddCss: false}
|
||||
</script>
|
||||
<script src="static/js/fontawesome-all.min.js"></script>
|
||||
<?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'])) {
|
||||
@ -66,28 +84,35 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
|
||||
|
||||
<?php
|
||||
// Alert messages
|
||||
if (isset($_GET['msg']) && !is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
|
||||
// optional string generation argument
|
||||
if (!isset($_GET['arg']) || is_empty($_GET['arg'])) {
|
||||
$alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
|
||||
if (!empty($_GET['msg'])) {
|
||||
if (array_key_exists($_GET['msg'], MESSAGES)) {
|
||||
// optional string generation argument
|
||||
if (empty($_GET['arg'])) {
|
||||
$alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
|
||||
} else {
|
||||
$alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
|
||||
}
|
||||
$alerttype = MESSAGES[$_GET['msg']]['type'];
|
||||
$alerticon = "square-o";
|
||||
switch (MESSAGES[$_GET['msg']]['type']) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
|
||||
}
|
||||
$alerttype = MESSAGES[$_GET['msg']]['type'];
|
||||
$alerticon = "square-o";
|
||||
switch (MESSAGES[$_GET['msg']]['type']) {
|
||||
case "danger":
|
||||
$alerticon = "times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "check";
|
||||
break;
|
||||
// We don't have a message for this, so just assume an error and escape stuff.
|
||||
$alertmsg = htmlentities($Strings->get($_GET['msg'], false));
|
||||
$alerticon = "times";
|
||||
$alerttype = "danger";
|
||||
}
|
||||
echo <<<END
|
||||
<div class="row justify-content-center" id="msg-alert-box">
|
||||
@ -121,7 +146,7 @@ END;
|
||||
</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 SITE_TITLE; ?>
|
||||
<?php echo $SETTINGS['site_title']; ?>
|
||||
</a>
|
||||
|
||||
<div class="collapse navbar-collapse py-0" id="navbar-collapse">
|
||||
@ -177,12 +202,12 @@ END;
|
||||
?>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo FOOTER_TEXT; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
|
||||
<?php echo $SETTINGS['footer_text']; ?><br />
|
||||
Copyright © <?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.min.js"></script>
|
||||
<script src="static/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="static/js/app.js"></script>
|
||||
<?php
|
||||
// custom page scripts
|
||||
|
@ -3,13 +3,13 @@
|
||||
"description": "Single-sign-on system and dashboard for Netsyms Business Apps",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"catfan/medoo": "^1.5",
|
||||
"catfan/medoo": "^1.7",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"spomky-labs/otphp": "^8.3",
|
||||
"endroid/qr-code": "^3.2",
|
||||
"ldaptools/ldaptools": "^0.24.0",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"phpmailer/phpmailer": "^5.2",
|
||||
"christian-riesen/base32": "^1.3"
|
||||
"christian-riesen/base32": "^1.3",
|
||||
"mibe/feedwriter": "^1.1"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"authors": [
|
||||
|
858
composer.lock
generated
858
composer.lock
generated
File diff suppressed because it is too large
Load Diff
BIN
database.mwb
BIN
database.mwb
Binary file not shown.
124
database.sql
124
database.sql
@ -1,5 +1,5 @@
|
||||
-- MySQL Script generated by MySQL Workbench
|
||||
-- Sat 28 Jul 2018 03:55:27 PM MDT
|
||||
-- Mon 11 Feb 2019 04:07:57 PM MST
|
||||
-- Model: New Model Version: 1.0
|
||||
-- MySQL Workbench Forward Engineering
|
||||
|
||||
@ -70,46 +70,13 @@ CREATE TABLE IF NOT EXISTS `accounts` (
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `apps`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `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 `available_apps`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `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 `apps` (`appid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_apps_has_accounts_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `apikeys`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `apikeys` (
|
||||
`key` VARCHAR(60) NOT NULL,
|
||||
`notes` TEXT NULL,
|
||||
`type` VARCHAR(45) NOT NULL DEFAULT 'FULL',
|
||||
PRIMARY KEY (`key`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
@ -313,6 +280,80 @@ CREATE TABLE IF NOT EXISTS `notifications` (
|
||||
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;
|
||||
@ -335,8 +376,7 @@ COMMIT;
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (1, 'LOCAL');
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (2, 'LDAP');
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (3, 'LIGHT');
|
||||
INSERT INTO `accttypes` (`typeid`, `typecode`) VALUES (2, 'EXTERNAL');
|
||||
|
||||
COMMIT;
|
||||
|
||||
@ -390,3 +430,13 @@ INSERT INTO `permissions` (`permid`, `permcode`, `perminfo`) VALUES (404, 'SITEW
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Data for table `userkeytypes`
|
||||
-- -----------------------------------------------------
|
||||
START TRANSACTION;
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (1, 'RSSAtomFeed');
|
||||
INSERT INTO `userkeytypes` (`typeid`, `typename`) VALUES (2, 'Other');
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
35
database_upgrade/2.0_2.1.sql
Normal file
35
database_upgrade/2.0_2.1.sql
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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');
|
48
database_upgrade/2.1_2.2.sql
Normal file
48
database_upgrade/2.1_2.2.sql
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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;
|
89
feed.php
Normal file
89
feed.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?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();
|
314
index.php
314
index.php
@ -1,239 +1,113 @@
|
||||
<?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) {
|
||||
header('Location: home.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');
|
||||
die();
|
||||
// This branch will likely run if the user signed in from a different app.
|
||||
}
|
||||
|
||||
/* Authenticate user */
|
||||
$username_ok = false;
|
||||
$multiauth = false;
|
||||
$change_password = false;
|
||||
if (empty($VARS['progress'])) {
|
||||
// Easy way to remove "undefined" warnings.
|
||||
} else if ($VARS['progress'] == "1") {
|
||||
engageRateLimit();
|
||||
if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) {
|
||||
$autherror = "";
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->exists()) {
|
||||
$status = $user->getStatus()->getString();
|
||||
switch ($status) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
$alert = $Strings->get("account locked", false);
|
||||
break;
|
||||
case "TERMINATED":
|
||||
$alert = $Strings->get("account terminated", false);
|
||||
break;
|
||||
case "CHANGE_PASSWORD":
|
||||
$alert = $Strings->get("password expired", false);
|
||||
$alerttype = "info";
|
||||
$_SESSION['username'] = $user->getUsername();
|
||||
$_SESSION['uid'] = $user->getUID();
|
||||
$change_password = true;
|
||||
break;
|
||||
case "NORMAL":
|
||||
$username_ok = true;
|
||||
break;
|
||||
case "ALERT_ON_ACCESS":
|
||||
$mail_resp = $user->sendAlertEmail();
|
||||
if (DEBUG) {
|
||||
var_dump($mail_resp);
|
||||
}
|
||||
$username_ok = true;
|
||||
break;
|
||||
default:
|
||||
if (!is_empty($error)) {
|
||||
$alert = $error;
|
||||
} else {
|
||||
$alert = $Strings->get("login error", false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($username_ok) {
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
$_SESSION['passok'] = true; // stop logins using only username and authcode
|
||||
if ($user->has2fa()) {
|
||||
$multiauth = true;
|
||||
} else {
|
||||
Session::start($user);
|
||||
Log::insert(LogType::LOGIN_OK, $user->getUID());
|
||||
header('Location: app.php');
|
||||
die("Logged in, go to app.php");
|
||||
}
|
||||
} else {
|
||||
$alert = $Strings->get("login incorrect", false);
|
||||
Log::insert(LogType::LOGIN_FAILED, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
}
|
||||
} else { // User does not exist anywhere
|
||||
$alert = $Strings->get("login incorrect", false);
|
||||
Log::insert(LogType::LOGIN_FAILED, null, "Username: " . $VARS['username']);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
} else {
|
||||
$alert = $Strings->get("captcha error", false);
|
||||
Log::insert(LogType::BAD_CAPTCHA, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
} else if ($VARS['progress'] == "2") {
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($_SESSION['passok'] !== true) {
|
||||
// stop logins using only username and authcode
|
||||
sendError("Password integrity check failed!");
|
||||
}
|
||||
if ($user->check2fa($VARS['authcode'])) {
|
||||
Session::start($user);
|
||||
Log::insert(LogType::LOGIN_OK, $user->getUID());
|
||||
header('Location: app.php');
|
||||
die("Logged in, go to app.php");
|
||||
} else {
|
||||
$alert = $Strings->get("2fa incorrect", false);
|
||||
Log::insert(LogType::BAD_2FA, null, "Username: " . $VARS['username']);
|
||||
}
|
||||
} else if ($VARS['progress'] == "chpasswd") {
|
||||
engageRateLimit();
|
||||
if (!is_empty($_SESSION['username'])) {
|
||||
$user = User::byUsername($_SESSION['username']);
|
||||
|
||||
try {
|
||||
$result = $user->changePassword($VARS['oldpass'], $VARS['newpass'], $VARS['conpass']);
|
||||
|
||||
if ($result === TRUE) {
|
||||
$alert = $Strings->get(MESSAGES["password_updated"]["string"], false);
|
||||
$alerttype = MESSAGES["password_updated"]["type"];
|
||||
}
|
||||
} catch (PasswordMatchException $e) {
|
||||
$alert = $Strings->get(MESSAGES["passwords_same"]["string"], false);
|
||||
$alerttype = "danger";
|
||||
} catch (PasswordMismatchException $e) {
|
||||
$alert = $Strings->get(MESSAGES["new_password_mismatch"]["string"], false);
|
||||
$alerttype = "danger";
|
||||
} catch (IncorrectPasswordException $e) {
|
||||
$alert = $Strings->get(MESSAGES["old_password_mismatch"]["string"], false);
|
||||
$alerttype = "danger";
|
||||
} catch (WeakPasswordException $e) {
|
||||
$alert = $Strings->get(MESSAGES["weak_password"]["string"], false);
|
||||
$alerttype = "danger";
|
||||
.banner-image {
|
||||
max-height: 100px;
|
||||
margin: 2em auto;
|
||||
border: 1px solid grey;
|
||||
border-radius: 15%;
|
||||
}
|
||||
} else {
|
||||
session_destroy();
|
||||
header('Location: index.php');
|
||||
die();
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
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/index.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.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 SITE_TITLE; ?></title>
|
||||
|
||||
<link rel="icon" href="static/img/logo.svg">
|
||||
|
||||
<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/index.css" rel="stylesheet">
|
||||
<?php if (CAPTCHA_ENABLED) { ?>
|
||||
<script src="<?php echo CAPTCHA_SERVER ?>/captcheck.dist.js"></script>
|
||||
<?php } ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-auto">
|
||||
<img class="banner-image" src="static/img/logo.svg" />
|
||||
<div class="col-12 text-center">
|
||||
<img class="banner-image" src="./static/img/logo.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="card col-11 col-xs-11 col-sm-8 col-md-6 col-lg-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php $Strings->get("sign in"); ?></h5>
|
||||
<form action="" method="POST">
|
||||
<?php
|
||||
if (!empty($alert)) {
|
||||
$alerttype = isset($alerttype) ? $alerttype : "danger";
|
||||
?>
|
||||
<div class="alert alert-<?php echo $alerttype ?>">
|
||||
<?php
|
||||
switch ($alerttype) {
|
||||
case "danger":
|
||||
$alerticon = "fas fa-times";
|
||||
break;
|
||||
case "warning":
|
||||
$alerticon = "fas fa-exclamation-triangle";
|
||||
break;
|
||||
case "info":
|
||||
$alerticon = "fas fa-info-circle";
|
||||
break;
|
||||
case "success":
|
||||
$alerticon = "fas fa-check";
|
||||
break;
|
||||
default:
|
||||
$alerticon = "far fa-square";
|
||||
}
|
||||
?>
|
||||
<i class="<?php echo $alerticon ?> fa-fw"></i> <?php echo $alert ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if (!$multiauth && !$change_password) {
|
||||
?>
|
||||
<input type="text" class="form-control" name="username" placeholder="<?php $Strings->get("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
|
||||
<input type="password" class="form-control" name="password" placeholder="<?php $Strings->get("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
|
||||
<?php if (CAPTCHA_ENABLED) { ?>
|
||||
<div class="captcheck_container" data-stylenonce="<?php echo $SECURE_NONCE; ?>"></div>
|
||||
<br />
|
||||
<?php } ?>
|
||||
<input type="hidden" name="progress" value="1" />
|
||||
<?php
|
||||
} else if ($multiauth) {
|
||||
?>
|
||||
<div class="alert alert-info">
|
||||
<?php $Strings->get("2fa prompt"); ?>
|
||||
</div>
|
||||
<input type="text" class="form-control" name="authcode" placeholder="<?php $Strings->get("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 $Strings->get("continue"); ?>
|
||||
</button>
|
||||
</form>
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-5 mb-4"><?php $Strings->get($title); ?></h1>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<?php echo FOOTER_TEXT; ?><br />
|
||||
Copyright © <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
|
||||
</div>
|
||||
</div>
|
||||
<script src="static/js/jquery-3.3.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"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 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.",
|
||||
@ -12,5 +12,6 @@
|
||||
"secret key": "Secret key",
|
||||
"label": "Label",
|
||||
"issuer": "Issuer",
|
||||
"no such code or code expired": "That code is incorrect or expired."
|
||||
"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."
|
||||
}
|
||||
|
11
langs/en/apppasswords.json
Normal file
11
langs/en/apppasswords.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"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."
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"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",
|
||||
@ -6,6 +7,7 @@
|
||||
"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 change Station PIN": "Change password, setup 2-factor, and change Station PIN",
|
||||
"Connect mobile devices to AccountHub": "Connect mobile devices to AccountHub"
|
||||
"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"
|
||||
}
|
||||
|
@ -1,26 +1,7 @@
|
||||
{
|
||||
"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 server unavailable": "Login server unavailable. Try again later or contact technical support.",
|
||||
"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.",
|
||||
"welcome user": "Welcome, {user}!",
|
||||
"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}",
|
||||
"login server user data error": "The login server refused to provide account information. Try again or contact technical support.",
|
||||
"captcha error": "There was a problem with the CAPTCHA (robot test). Try again.",
|
||||
"no access permission": "You do not have permission to access this system.",
|
||||
"generic op error": "An unknown error occurred. Try again later.",
|
||||
"home": "Home"
|
||||
"login server error": "The login server returned an error: {arg}"
|
||||
}
|
||||
|
7
langs/en/index.json
Normal file
7
langs/en/index.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"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"
|
||||
}
|
25
langs/en/login.json
Normal file
25
langs/en/login.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"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"
|
||||
}
|
@ -2,5 +2,8 @@
|
||||
"Notifications": "Notifications",
|
||||
"Notification deleted.": "Notification deleted.",
|
||||
"Mark as read": "Mark as read",
|
||||
"Delete": "Delete"
|
||||
"Delete": "Delete",
|
||||
"All caught up!": "All caught up!",
|
||||
"Notifications from server for user": "Notifications from {server} for {user}",
|
||||
"Sensitive content hidden": "Sensitive content hidden"
|
||||
}
|
||||
|
15
langs/en/signup.json
Normal file
15
langs/en/signup.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"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."
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"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.",
|
||||
"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.",
|
||||
@ -9,5 +9,9 @@
|
||||
"manual setup": "Manual Setup:",
|
||||
"sync key": "Sync key:",
|
||||
"url": "URL:",
|
||||
"sync code name": "Device nickname"
|
||||
"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"
|
||||
}
|
||||
|
@ -4,5 +4,6 @@
|
||||
"account options": "Account options",
|
||||
"sync": "Sync settings",
|
||||
"settings": "Settings",
|
||||
"account": "Account"
|
||||
"account": "Account",
|
||||
"Home": "Home"
|
||||
}
|
||||
|
@ -72,5 +72,9 @@ define("MESSAGES", [
|
||||
"notification_deleted" => [
|
||||
"string" => "Notification deleted.",
|
||||
"type" => "success"
|
||||
],
|
||||
"feed_key_reset" => [
|
||||
"string" => "Feed key reset.",
|
||||
"type" => "success"
|
||||
]
|
||||
]);
|
||||
|
@ -32,4 +32,4 @@ class PasswordMismatchException extends Exception {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
326
lib/FormBuilder.lib.php
Normal file
326
lib/FormBuilder.lib.php
Normal file
@ -0,0 +1,326 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
135
lib/IPUtils.lib.php
Normal file
135
lib/IPUtils.lib.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?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
|
||||
}
|
||||
|
||||
}
|
@ -18,7 +18,7 @@ class Log {
|
||||
public static function insert($type, $user, string $data = "") {
|
||||
global $database;
|
||||
// find IP address
|
||||
$ip = getClientIP();
|
||||
$ip = IPUtils::getClientIP();
|
||||
if (gettype($type) == "object" && is_a($type, "LogType")) {
|
||||
$type = $type->getType();
|
||||
}
|
||||
|
33
lib/LoginKey.lib.php
Normal file
33
lib/LoginKey.lib.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
@ -42,13 +42,18 @@ class Notifications {
|
||||
* 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) {
|
||||
public static function get(User $user, bool $all = true) {
|
||||
global $database, $Strings;
|
||||
if ($user->exists()) {
|
||||
$notifications = $database->select('notifications', ['notificationid (id)', 'timestamp', 'title', 'content', 'url', 'seen', 'sensitive'], ['uid' => $user->getUID(), 'ORDER' => ['seen', 'timestamp' => 'DESC']]);
|
||||
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);
|
||||
@ -90,4 +95,5 @@ class Notifications {
|
||||
}
|
||||
throw new Exception($Strings->get("user does not exist", false));
|
||||
}
|
||||
|
||||
}
|
||||
|
31
lib/RandomString.lib.php
Normal file
31
lib/RandomString.lib.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
}
|
@ -21,6 +21,10 @@ class Strings {
|
||||
|
||||
$this->load("en");
|
||||
|
||||
if ($language == "en") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists(__DIR__ . "/../langs/$language/")) {
|
||||
$this->language = $language;
|
||||
$this->load($language);
|
||||
|
@ -19,6 +19,7 @@ class User {
|
||||
private $authsecret;
|
||||
private $has2fa = false;
|
||||
private $exists = false;
|
||||
private $apppasswords = [];
|
||||
|
||||
public function __construct(int $uid, string $username = "") {
|
||||
global $database;
|
||||
@ -32,6 +33,7 @@ class User {
|
||||
$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;
|
||||
@ -63,7 +65,7 @@ class User {
|
||||
global $database;
|
||||
$database->insert('accounts', [
|
||||
'username' => strtolower($username),
|
||||
'password' => (is_null($password) ? null : encryptPassword($password)),
|
||||
'password' => (is_null($password) ? null : password_hash($password, PASSWORD_BCRYPT)),
|
||||
'realname' => $realname,
|
||||
'email' => $email,
|
||||
'phone1' => $phone1,
|
||||
@ -107,6 +109,20 @@ class User {
|
||||
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
|
||||
@ -119,7 +135,7 @@ class User {
|
||||
* @throws WeakPasswordException
|
||||
*/
|
||||
function changePassword(string $old, string $new, string $new2) {
|
||||
global $database;
|
||||
global $database, $SETTINGS;
|
||||
if ($old == $new) {
|
||||
throw new PasswordMatchException();
|
||||
}
|
||||
@ -137,7 +153,7 @@ class User {
|
||||
if ($passrank !== FALSE) {
|
||||
throw new WeakPasswordException();
|
||||
}
|
||||
if (strlen($new) < MIN_PASSWORD_LENGTH) {
|
||||
if (strlen($new) < $SETTINGS['min_password_length']) {
|
||||
throw new WeakPasswordException();
|
||||
}
|
||||
|
||||
@ -146,6 +162,7 @@ class User {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function check2fa(string $code): bool {
|
||||
if (!$this->has2fa) {
|
||||
return true;
|
||||
@ -171,10 +188,11 @@ class 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(SYSTEM_NAME);
|
||||
$totp->setIssuer($SETTINGS['system_name']);
|
||||
return $totp->getProvisioningUri();
|
||||
}
|
||||
|
||||
@ -214,29 +232,33 @@ class User {
|
||||
return new AccountStatus($statuscode);
|
||||
}
|
||||
|
||||
function sendAlertEmail(string $appname = SITE_TITLE) {
|
||||
if (is_empty(ADMIN_EMAIL) || filter_var(ADMIN_EMAIL, FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||
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 (is_empty(FROM_EMAIL) || filter_var(FROM_EMAIL, FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||
if (empty($SETTINGS["email"]["from"]) || filter_var($SETTINGS["email"]["from"], FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||
return "invalid_from_email";
|
||||
}
|
||||
|
||||
$mail = new PHPMailer;
|
||||
|
||||
if (DEBUG) {
|
||||
if ($SETTINGS['debug']) {
|
||||
$mail->SMTPDebug = 2;
|
||||
}
|
||||
|
||||
if (USE_SMTP) {
|
||||
if ($SETTINGS['email']['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->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,
|
||||
@ -247,11 +269,11 @@ class User {
|
||||
}
|
||||
}
|
||||
|
||||
$mail->setFrom(FROM_EMAIL, 'Account Alerts');
|
||||
$mail->addAddress(ADMIN_EMAIL, "System Admin");
|
||||
$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" => getClientIP(), "appname" => $appname], 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;
|
||||
|
169
login/index.php
Normal file
169
login/index.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?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";
|
51
login/parts/change_password.php
Normal file
51
login/parts/change_password.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?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>
|
15
login/parts/footer.php
Normal file
15
login/parts/footer.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?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>
|
59
login/parts/header.php
Normal file
59
login/parts/header.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?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
|
||||
}
|
||||
?>
|
32
login/parts/password.php
Normal file
32
login/parts/password.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?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']); ?>&redirect=<?php echo htmlentities($_GET['redirect']); ?>&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>
|
31
login/parts/totp.php
Normal file
31
login/parts/totp.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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']); ?>&redirect=<?php echo htmlentities($_GET['redirect']); ?>&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>
|
37
login/parts/username.php
Normal file
37
login/parts/username.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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"]); ?>&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>
|
139
mobile/index.php
139
mobile/index.php
@ -18,12 +18,12 @@ if ($VARS['action'] == "ping") {
|
||||
exit(json_encode(["status" => "OK"]));
|
||||
}
|
||||
|
||||
if (MOBILE_ENABLED !== TRUE) {
|
||||
if ($SETTINGS['mobile_enabled'] !== TRUE) {
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
|
||||
}
|
||||
|
||||
// Make sure we have a username and access key
|
||||
if (is_empty($VARS['username']) || is_empty($VARS['key'])) {
|
||||
if (empty($VARS['username']) || empty($VARS['key'])) {
|
||||
http_response_code(401);
|
||||
die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
|
||||
}
|
||||
@ -32,12 +32,19 @@ $username = strtolower($VARS['username']);
|
||||
$key = strtoupper($VARS['key']);
|
||||
|
||||
// Make sure the username and key are actually legit
|
||||
$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."]));
|
||||
// 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."]));
|
||||
}
|
||||
}
|
||||
|
||||
// Obscure key
|
||||
@ -99,7 +106,7 @@ switch ($VARS['action']) {
|
||||
Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
|
||||
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
|
||||
case "listapps":
|
||||
$apps = EXTERNAL_APPS;
|
||||
$apps = $SETTINGS['apps'];
|
||||
// Format paths as absolute URLs
|
||||
foreach ($apps as $k => $v) {
|
||||
if (strpos($apps[$k]['url'], "http") === FALSE) {
|
||||
@ -119,7 +126,119 @@ switch ($VARS['action']) {
|
||||
|
||||
$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."]));
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
// List of pages and metadata
|
||||
define("PAGES", [
|
||||
"home" => [
|
||||
"title" => "home",
|
||||
"title" => "Home",
|
||||
"navbar" => true,
|
||||
"icon" => "fas fa-home",
|
||||
"styles" => [
|
||||
|
@ -6,32 +6,9 @@
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-center flex-wrap">
|
||||
<?php
|
||||
foreach (EXTERNAL_APPS as $a) {
|
||||
?>
|
||||
<div class="app-dock-item m-2 mobile-app-hide">
|
||||
<p class="mb-0">
|
||||
<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 class="d-block text-center"><?php echo $a['title']; ?></span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<?php
|
||||
foreach (EXTERNAL_APPS as $a) {
|
||||
foreach ($SETTINGS['apps'] as $a) {
|
||||
if (!isset($a['card'])) {
|
||||
continue;
|
||||
}
|
||||
@ -69,7 +46,13 @@
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<span class="h5 font-weight-normal"><?php $Strings->get("account security"); ?></span><br />
|
||||
<?php $Strings->get("Change password, setup 2-factor, and change Station PIN"); ?>
|
||||
<?php
|
||||
if ($SETTINGS['station_kiosk']) {
|
||||
$Strings->get("Change password, setup 2-factor, add app passwords, and change PIN");
|
||||
} else {
|
||||
$Strings->get("Change password, setup 2-factor, and add app passwords");
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@ -84,7 +67,7 @@
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<span class="h5 font-weight-normal"><?php $Strings->get("sync"); ?></span><br />
|
||||
<?php $Strings->get("Connect mobile devices to AccountHub"); ?>
|
||||
<?php $Strings->build("Connect mobile devices to {name} and get notifications", ["name" => $SETTINGS['site_title']]); ?>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@ -96,6 +79,15 @@
|
||||
<div class="row">
|
||||
<?php
|
||||
$notifications = Notifications::get(User::byUsername($_SESSION['username']));
|
||||
if (count($notifications) == 0) {
|
||||
?>
|
||||
<div class="col-12 col-sm-6 col-md-4 col-xl-3">
|
||||
<div class="alert alert-light">
|
||||
<i class="fas fa-check"></i> <?php $Strings->get("All caught up!"); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
foreach ($notifications as $n) {
|
||||
?>
|
||||
<div class="col-12 col-sm-6 col-md-4 col-xl-3">
|
||||
@ -128,9 +120,9 @@
|
||||
<?php
|
||||
$ts = strtotime($n['timestamp']);
|
||||
if (time() - $ts < 60 * 60 * 12) {
|
||||
echo date(TIME_FORMAT, $ts);
|
||||
echo date($SETTINGS['time_format'], $ts);
|
||||
} else {
|
||||
echo date(DATETIME_FORMAT, $ts);
|
||||
echo date($SETTINGS['datetime_format'], $ts);
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
@ -140,4 +132,4 @@
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,12 +10,20 @@ use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
use Endroid\QrCode\QrCode;
|
||||
|
||||
$user = new User($_SESSION['uid']);
|
||||
|
||||
if (!empty($_GET['delpass'])) {
|
||||
if ($database->has("apppasswords", ["AND" => ["uid" => $_SESSION['uid'], "passid" => $_GET['delpass']]])) {
|
||||
$database->delete("apppasswords", ["AND" => ["uid" => $_SESSION['uid'], "passid" => $_GET['delpass']]]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-sm-6 col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-key"></i> <?php $Strings->get("change password"); ?></h5>
|
||||
<hr />
|
||||
<form action="action.php" method="POST">
|
||||
<input type="password" class="form-control" name="oldpass" placeholder="<?php $Strings->get("current password"); ?>" />
|
||||
<input type="password" class="form-control" name="newpass" placeholder="<?php $Strings->get("new password"); ?>" />
|
||||
@ -28,32 +36,42 @@ $user = new User($_SESSION['uid']);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-th"></i> <?php $Strings->get("change pin"); ?></h5>
|
||||
<hr />
|
||||
<?php $Strings->get("pin explanation"); ?>
|
||||
<hr />
|
||||
<form action="action.php" method="POST">
|
||||
<input type="password" class="form-control" name="newpin" placeholder="<?php $Strings->get("new pin"); ?>" maxlength="8" pattern="[0-9]*" inputmode="numeric" />
|
||||
<input type="password" class="form-control" name="conpin" placeholder="<?php $Strings->get("confirm pin"); ?>" maxlength="8" pattern="[0-9]*" inputmode="numeric" />
|
||||
<input type="hidden" name="action" value="chpin" />
|
||||
<input type="hidden" name="source" value="security" />
|
||||
<br />
|
||||
<button type="submit" class="btn btn-success btn-block"><?php $Strings->get("change pin"); ?></button>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
if ($SETTINGS['station_kiosk']) {
|
||||
?>
|
||||
<div class="col-sm-6 col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-th"></i> <?php $Strings->get("change pin"); ?></h5>
|
||||
<hr />
|
||||
<?php $Strings->get("pin explanation"); ?>
|
||||
<hr />
|
||||
<form action="action.php" method="POST">
|
||||
<input type="password" class="form-control" name="newpin" placeholder="<?php $Strings->get("new pin"); ?>" maxlength="8" pattern="[0-9]*" inputmode="numeric" />
|
||||
<input type="password" class="form-control" name="conpin" placeholder="<?php $Strings->get("confirm pin"); ?>" maxlength="8" pattern="[0-9]*" inputmode="numeric" />
|
||||
<input type="hidden" name="action" value="chpin" />
|
||||
<input type="hidden" name="source" value="security" />
|
||||
<br />
|
||||
<button type="submit" class="btn btn-success btn-block"><?php $Strings->get("change pin"); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="col-sm-6 col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="card-body pb-0">
|
||||
<h5 class="card-title"><i class="fas fa-mobile-alt"></i> <?php $Strings->get("setup 2fa"); ?></h5>
|
||||
<?php
|
||||
if ($user->has2fa()) {
|
||||
?>
|
||||
<hr />
|
||||
<hr />
|
||||
</div>
|
||||
<?php
|
||||
if ($user->has2fa()) {
|
||||
?>
|
||||
<div class="card-body pt-0">
|
||||
<?php $Strings->get("2fa active") ?>
|
||||
<hr />
|
||||
<form action="action.php" method="POST">
|
||||
@ -61,21 +79,22 @@ $user = new User($_SESSION['uid']);
|
||||
<input type="hidden" name="source" value="security" />
|
||||
<button type="submit" class="btn btn-info btn-block"><?php $Strings->get("remove 2fa") ?></button>
|
||||
</form>
|
||||
<?php
|
||||
} else if (!empty($_GET['2fa']) && $_GET['2fa'] == "generate") {
|
||||
$codeuri = $user->generate2fa();
|
||||
$label = SYSTEM_NAME . ":" . is_null($user->getEmail()) ? $user->getName() : $user->getEmail();
|
||||
$issuer = SYSTEM_NAME;
|
||||
$qrCode = new QrCode($codeuri);
|
||||
$qrCode->setWriterByName('svg');
|
||||
$qrCode->setSize(550);
|
||||
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH);
|
||||
$qrcode = $qrCode->writeDataUri();
|
||||
$totp = Factory::loadFromProvisioningUri($codeuri);
|
||||
$codesecret = $totp->getSecret();
|
||||
$chunk_secret = trim(chunk_split($codesecret, 4, ' '));
|
||||
?>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
} else if (!empty($_GET['2fa']) && $_GET['2fa'] == "generate") {
|
||||
$codeuri = $user->generate2fa();
|
||||
$label = $SETTINGS['system_name'] . ":" . is_null($user->getEmail()) ? $user->getName() : $user->getEmail();
|
||||
$issuer = $SETTINGS['system_name'];
|
||||
$qrCode = new QrCode($codeuri);
|
||||
$qrCode->setWriterByName('svg');
|
||||
$qrCode->setSize(550);
|
||||
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH());
|
||||
$qrcode = $qrCode->writeDataUri();
|
||||
$totp = Factory::loadFromProvisioningUri($codeuri);
|
||||
$codesecret = $totp->getSecret();
|
||||
$chunk_secret = trim(chunk_split($codesecret, 4, ' '));
|
||||
?>
|
||||
<div class="card-body pt-0">
|
||||
<div class="card-text">
|
||||
<?php $Strings->get("scan 2fa qrcode") ?>
|
||||
</div>
|
||||
@ -113,15 +132,99 @@ $user = new User($_SESSION['uid']);
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<hr />
|
||||
<?php $Strings->get("2fa explained"); ?>
|
||||
<hr />
|
||||
<a class="btn btn-success btn-block" href="app.php?page=security&2fa=generate">
|
||||
<?php $Strings->get("enable 2fa"); ?>
|
||||
</a>
|
||||
<div class="card-body pt-0">
|
||||
<?php $Strings->get("2fa explained"); ?>
|
||||
<hr />
|
||||
<a class="btn btn-success btn-block" href="app.php?page=security&2fa=generate">
|
||||
<?php $Strings->get("enable 2fa"); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-6 col-lg-4 col-xl-4">
|
||||
<div class="card mb-4">
|
||||
<?php
|
||||
if (!empty($_GET['apppassword']) && $_GET['apppassword'] == "generate" && !empty($_POST['desc'])) {
|
||||
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
|
||||
$desc = htmlspecialchars($_POST['desc']);
|
||||
$chunk_code = str_replace(" ", "-", trim(chunk_split($code, 5, ' ')));
|
||||
$database->insert('apppasswords', ['uid' => $_SESSION['uid'], 'hash' => password_hash($chunk_code, PASSWORD_DEFAULT), 'description' => $desc]);
|
||||
?>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-shield-alt"></i> <?php $Strings->get("App Passwords"); ?></h5>
|
||||
<hr />
|
||||
|
||||
<?php $Strings->build("app password setup instructions", ["app_name" => $desc]); ?>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item d-flex justify-content-between align-items-baseline">
|
||||
<div><?php $Strings->get("username"); ?>:</div>
|
||||
<div class="text-monospace text-right"><?php echo $_SESSION['username']; ?></div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-baseline">
|
||||
<div><?php $Strings->get("password"); ?></div>
|
||||
<div class="text-monospace text-right"><?php echo $chunk_code; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a class="btn btn-success btn-block" href="app.php?page=security"><?php $Strings->get("Done"); ?></a>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
$activecodes = $database->select("apppasswords", ["passid", "description"], ["uid" => $_SESSION['uid']]);
|
||||
?>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-shield-alt"></i> <?php $Strings->get("App Passwords"); ?></h5>
|
||||
<hr />
|
||||
<p class="card-text">
|
||||
<?php $Strings->build("app passwords explained", ["site_name" => $SETTINGS['site_title']]); ?>
|
||||
</p>
|
||||
<form action="app.php?page=security&apppassword=generate" method="POST">
|
||||
<input type="text" name="desc" class="form-control" placeholder="<?php $Strings->get("App name"); ?>" required />
|
||||
<button class="btn btn-success btn-block mt-2" type="submit">
|
||||
<?php $Strings->get("Generate password"); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item">
|
||||
<b><?php $Strings->get("App Passwords"); ?></b>
|
||||
</div>
|
||||
<?php
|
||||
if (count($activecodes) > 0) {
|
||||
foreach ($activecodes as $c) {
|
||||
?>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="">
|
||||
<?php echo $c['description']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn btn-danger btn-sm m-1" href="app.php?page=security&delpass=<?php echo $c['passid']; ?>" data-toggle="tooltip" data-placement="bottom" title="<?php $Strings->get("Revoke password"); ?>">
|
||||
<i class='fas fa-trash'></i><noscript> <?php $Strings->get("Revoke password"); ?></noscript>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
<div class="list-group-item">
|
||||
<?php $Strings->get("You don't have any app passwords."); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
173
pages/sync.php
173
pages/sync.php
@ -15,36 +15,31 @@ if (!empty($_GET['delsynccode'])) {
|
||||
}
|
||||
?>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5 col-xl-4">
|
||||
<div class="card">
|
||||
<div class="col-sm-10 col-md-6 col-lg-4 col-xl-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-mobile-alt"></i> <?php $Strings->get("sync mobile"); ?></h5>
|
||||
</div>
|
||||
<?php
|
||||
if (!empty($_GET['mobilecode']) && $_GET['mobilecode'] == "generate") {
|
||||
if (!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 {
|
||||
<hr />
|
||||
<?php
|
||||
if (!empty($_GET['mobilecode']) && $_GET['mobilecode'] == "generate") {
|
||||
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
|
||||
$desc = htmlspecialchars($_POST['desc']);
|
||||
$database->insert('mobile_codes', ['uid' => $_SESSION['uid'], 'code' => $code, 'description' => $desc]);
|
||||
}
|
||||
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->setWriterByName('svg');
|
||||
$qrCode->setSize(550);
|
||||
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH);
|
||||
$qrcode = $qrCode->writeDataUri();
|
||||
$chunk_code = trim(chunk_split($code, 5, ' '));
|
||||
$lang_done = $Strings->get("done adding sync code", false);
|
||||
?>
|
||||
<div class="card-body">
|
||||
if (strpos($SETTINGS['url'], "http") === 0) {
|
||||
$url = $SETTINGS['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'] : "") . $SETTINGS['url'] . "mobile/index.php";
|
||||
}
|
||||
$encodedurl = str_replace("/", "\\", $url);
|
||||
$codeuri = "bizsync://" . $encodedurl . "/" . $_SESSION['username'] . "/" . $code;
|
||||
$qrCode = new QrCode($codeuri);
|
||||
$qrCode->setWriterByName('svg');
|
||||
$qrCode->setSize(550);
|
||||
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH);
|
||||
$qrcode = $qrCode->writeDataUri();
|
||||
$chunk_code = trim(chunk_split($code, 5, ' '));
|
||||
$lang_done = $Strings->get("done adding sync code", false);
|
||||
?>
|
||||
<p class="card-text"><?php $Strings->get("scan sync qrcode"); ?></p>
|
||||
</div>
|
||||
<img src="<?php echo $qrcode; ?>" class="card-img px-4" />
|
||||
@ -72,57 +67,105 @@ if (!empty($_GET['delsynccode'])) {
|
||||
} else {
|
||||
$activecodes = $database->select("mobile_codes", ["codeid", "code", "description"], ["uid" => $_SESSION['uid']]);
|
||||
?>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<?php $Strings->get("sync explained"); ?>
|
||||
</p>
|
||||
<form action="app.php?page=sync&mobilecode=generate" method="POST">
|
||||
<input type="text" name="desc" class="form-control" placeholder="<?php $Strings->get("sync code name"); ?>" required />
|
||||
<button class="btn btn-success btn-block mt-2" type="submit">
|
||||
<?php $Strings->get("generate sync"); ?>
|
||||
</button>
|
||||
</form>
|
||||
<p class="card-text">
|
||||
<?php $Strings->build("sync explained", ["site_name" => $SETTINGS['site_title']]); ?>
|
||||
</p>
|
||||
<form action="app.php?page=sync&mobilecode=generate" method="POST">
|
||||
<input type="text" name="desc" class="form-control" placeholder="<?php $Strings->get("sync code name"); ?>" required />
|
||||
<button class="btn btn-success btn-block mt-2" type="submit">
|
||||
<?php $Strings->get("generate sync"); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item">
|
||||
<b><?php $Strings->get("active sync codes"); ?></b>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item">
|
||||
<b><?php $Strings->get("active sync codes"); ?></b>
|
||||
</div>
|
||||
<?php
|
||||
if (count($activecodes) > 0) {
|
||||
foreach ($activecodes as $c) {
|
||||
?>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="text-monospace">
|
||||
<?php echo trim(chunk_split($c['code'], 5, ' ')); ?>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<i class="fas fa-mobile-alt"></i> <?php echo $c['description']; ?>
|
||||
</div>
|
||||
<?php
|
||||
if (count($activecodes) > 0) {
|
||||
foreach ($activecodes as $c) {
|
||||
// Obscure characters
|
||||
if (strlen($c['code']) > 7) {
|
||||
for ($i = 3; $i < strlen($c['code']) - 3; $i++) {
|
||||
$c['code'][$i] = "*";
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="">
|
||||
<?php echo $c['description']; ?>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn btn-primary btn-sm m-1" href="app.php?page=sync&mobilecode=generate&showsynccode=<?php echo $c['codeid']; ?>">
|
||||
<i class="fas fa-qrcode"></i>
|
||||
</a>
|
||||
<a class="btn btn-danger btn-sm m-1" href="app.php?page=sync&delsynccode=<?php echo $c['codeid']; ?>">
|
||||
<i class='fas fa-trash'></i>
|
||||
</a>
|
||||
<div class="text-muted text-monospace">
|
||||
<?php echo $c['code']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
<div class="list-group-item">
|
||||
<?php $Strings->get("no active codes"); ?>
|
||||
<div>
|
||||
<a class="btn btn-danger btn-sm m-1" href="app.php?page=sync&delsynccode=<?php echo $c['codeid']; ?>" data-toggle="tooltip" data-placement="bottom" title="<?php $Strings->get("Revoke key"); ?>">
|
||||
<i class='fas fa-trash'></i><noscript> <?php $Strings->get("Revoke key"); ?></noscript>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<?php $Strings->get("no active codes"); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-6 col-lg-4 col-xl-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-rss-square"></i> <?php $Strings->get("Notifications"); ?></h5>
|
||||
<hr />
|
||||
<p class="card-text">
|
||||
<?php $Strings->get("notification feed explained"); ?>
|
||||
</p>
|
||||
<?php
|
||||
if ($database->has('userkeys', ['AND' => ['uid' => $_SESSION['uid'], 'typeid' => 1]])) {
|
||||
$key = $database->get('userkeys', 'key', ['AND' => ['uid' => $_SESSION['uid'], 'typeid' => 1]]);
|
||||
} else {
|
||||
$key = RandomString::generate(50);
|
||||
while ($database->has('userkeys', ['key' => $key])) {
|
||||
$key = RandomString::generate(50);
|
||||
}
|
||||
$database->insert('userkeys', ['uid' => $_SESSION['uid'], 'typeid' => 1, 'created' => date('Y-m-d H:i:s'), 'key' => $key]);
|
||||
}
|
||||
|
||||
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'];
|
||||
}
|
||||
$url = $url . "feed.php?key=$key";
|
||||
?>
|
||||
<a href="<?php echo $url; ?>&type=rss2" target="_BLANK" class="btn btn-orange mr-2"><i class="fas fa-rss"></i> RSS 2.0</a>
|
||||
<a href="<?php echo $url; ?>&type=rss1" target="_BLANK" class="btn btn-orange mr-2"><i class="fas fa-rss"></i> RSS 1.0</a>
|
||||
<a href="<?php echo $url; ?>&type=atom" target="_BLANK" class="btn btn-blue"><i class="fas fa-atom"></i> ATOM</a>
|
||||
<hr />
|
||||
RSS 2.0: <input type="text" readonly class="form-control" value="<?php echo $url; ?>&type=rss2" />
|
||||
<br />
|
||||
RSS 1.0: <input type="text" readonly class="form-control" value="<?php echo $url; ?>&type=rss1" />
|
||||
<br />
|
||||
ATOM: <input type="text" readonly class="form-control" value="<?php echo $url; ?>&type=atom" />
|
||||
<hr />
|
||||
<form action="action.php" method="POST">
|
||||
<input type="hidden" name="source" value="sync" />
|
||||
<input type="hidden" name="action" value="resetfeedkey" />
|
||||
<button type="submit" class="btn btn-danger"><i class="fas fa-sync"></i> <?php $Strings->get('Reset'); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
259
required.php
259
required.php
@ -8,34 +8,32 @@
|
||||
* This file contains global settings and utility functions.
|
||||
*/
|
||||
ob_start(); // allow sending headers after content
|
||||
//
|
||||
// Composer
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Settings file
|
||||
require __DIR__ . '/settings.php';
|
||||
|
||||
// Unicode, solves almost all stupid encoding problems
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
// l33t $ecurity h4x
|
||||
// Strip PHP version
|
||||
header('X-Powered-By: PHP');
|
||||
|
||||
// Security
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
header('X-Powered-By: PHP'); // no versions makes it harder to find vulns
|
||||
header('X-Frame-Options: "DENY"');
|
||||
header('Referrer-Policy: "no-referrer, strict-origin-when-cross-origin"');
|
||||
$SECURE_NONCE = base64_encode(random_bytes(8));
|
||||
|
||||
|
||||
$session_length = 60 * 60; // 1 hour
|
||||
$session_length = 60 * 60 * 1; // 1 hour
|
||||
ini_set('session.gc_maxlifetime', $session_length);
|
||||
session_set_cookie_params($session_length, "/", null, false, false);
|
||||
|
||||
session_start(); // stick some cookies in it
|
||||
//// renew session cookie
|
||||
setcookie(session_name(), session_id(), time() + $session_length);
|
||||
// renew session cookie
|
||||
setcookie(session_name(), session_id(), time() + $session_length, "/", false, false);
|
||||
|
||||
$captcha_server = (CAPTCHA_ENABLED === true ? preg_replace("/http(s)?:\/\//", "", CAPTCHA_SERVER) : "");
|
||||
if ($_SESSION['mobile'] === TRUE) {
|
||||
if (isset($_SESSION['mobile']) && $_SESSION['mobile'] === TRUE) {
|
||||
header("Content-Security-Policy: "
|
||||
. "default-src 'self';"
|
||||
. "object-src 'none'; "
|
||||
@ -45,7 +43,7 @@ if ($_SESSION['mobile'] === TRUE) {
|
||||
. "font-src 'self'; "
|
||||
. "connect-src *; "
|
||||
. "style-src 'self' 'unsafe-inline'; "
|
||||
. "script-src 'self' 'unsafe-inline' $captcha_server");
|
||||
. "script-src 'self' 'unsafe-inline'");
|
||||
} else {
|
||||
header("Content-Security-Policy: "
|
||||
. "default-src 'self';"
|
||||
@ -56,9 +54,13 @@ if ($_SESSION['mobile'] === TRUE) {
|
||||
. "font-src 'self'; "
|
||||
. "connect-src *; "
|
||||
. "style-src 'self' 'nonce-$SECURE_NONCE'; "
|
||||
. "script-src 'self' 'nonce-$SECURE_NONCE' $captcha_server");
|
||||
. "script-src 'self' 'nonce-$SECURE_NONCE'");
|
||||
}
|
||||
|
||||
//
|
||||
// Composer
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// List of alert messages
|
||||
require __DIR__ . '/langs/messages.php';
|
||||
|
||||
@ -67,8 +69,12 @@ foreach ($libs as $lib) {
|
||||
require_once $lib;
|
||||
}
|
||||
|
||||
$Strings = new Strings(LANGUAGE);
|
||||
$Strings = new Strings($SETTINGS['language']);
|
||||
|
||||
/**
|
||||
* Kill off the running process and spit out an error message
|
||||
* @param string $error error message
|
||||
*/
|
||||
function sendError($error) {
|
||||
global $SECURE_NONCE;
|
||||
die("<!DOCTYPE html>"
|
||||
@ -87,7 +93,7 @@ function sendError($error) {
|
||||
. "<p>" . htmlspecialchars($error) . "</p>");
|
||||
}
|
||||
|
||||
date_default_timezone_set(TIMEZONE);
|
||||
date_default_timezone_set($SETTINGS['timezone']);
|
||||
|
||||
// Database settings
|
||||
// Also inits database and stuff
|
||||
@ -96,12 +102,12 @@ use Medoo\Medoo;
|
||||
$database;
|
||||
try {
|
||||
$database = new Medoo([
|
||||
'database_type' => DB_TYPE,
|
||||
'database_name' => DB_NAME,
|
||||
'server' => DB_SERVER,
|
||||
'username' => DB_USER,
|
||||
'password' => DB_PASS,
|
||||
'charset' => DB_CHARSET
|
||||
'database_type' => $SETTINGS['database']['type'],
|
||||
'database_name' => $SETTINGS['database']['name'],
|
||||
'server' => $SETTINGS['database']['server'],
|
||||
'username' => $SETTINGS['database']['user'],
|
||||
'password' => $SETTINGS['database']['password'],
|
||||
'charset' => $SETTINGS['database']['charset']
|
||||
]);
|
||||
} catch (Exception $ex) {
|
||||
//header('HTTP/1.1 500 Internal Server Error');
|
||||
@ -109,7 +115,7 @@ try {
|
||||
}
|
||||
|
||||
|
||||
if (!DEBUG) {
|
||||
if (!$SETTINGS['debug']) {
|
||||
error_reporting(0);
|
||||
} else {
|
||||
error_reporting(E_ALL);
|
||||
@ -126,43 +132,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
define("GET", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string or whatever is empty.
|
||||
* @param $str The thingy to check
|
||||
* @return boolean True if it's empty or whatever.
|
||||
*/
|
||||
function is_empty($str) {
|
||||
return (is_null($str) || !isset($str) || $str == '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an email address is valid.
|
||||
* @param string $email Email to check
|
||||
* @return boolean True if email passes validation, else false.
|
||||
*/
|
||||
function isValidEmail($email) {
|
||||
return filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes the given plaintext password
|
||||
* @param String $password
|
||||
* @return String the hash, using bcrypt
|
||||
*/
|
||||
function encryptPassword($password) {
|
||||
return password_hash($password, PASSWORD_BCRYPT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Securely verify a password and its hash
|
||||
* @param String $password
|
||||
* @param String $hash the hash to compare to
|
||||
* @return boolean True if password OK, else false
|
||||
*/
|
||||
function comparePassword($password, $hash) {
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
function dieifnotloggedin() {
|
||||
if ($_SESSION['loggedin'] != true) {
|
||||
sendError("Session expired. Please log out and log in again.");
|
||||
@ -186,178 +155,14 @@ function checkDBError($specials = []) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* http://stackoverflow.com/a/20075147
|
||||
*/
|
||||
if (!function_exists('base_url')) {
|
||||
|
||||
function base_url($atRoot = FALSE, $atCore = FALSE, $parse = FALSE) {
|
||||
if (isset($_SERVER['HTTP_HOST'])) {
|
||||
$http = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http';
|
||||
$hostname = $_SERVER['HTTP_HOST'];
|
||||
$dir = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
|
||||
|
||||
$core = preg_split('@/@', str_replace($_SERVER['DOCUMENT_ROOT'], '', realpath(dirname(__FILE__))), NULL, PREG_SPLIT_NO_EMPTY);
|
||||
$core = $core[0];
|
||||
|
||||
$tmplt = $atRoot ? ($atCore ? "%s://%s/%s/" : "%s://%s/") : ($atCore ? "%s://%s/%s/" : "%s://%s%s");
|
||||
$end = $atRoot ? ($atCore ? $core : $hostname) : ($atCore ? $core : $dir);
|
||||
$base_url = sprintf($tmplt, $http, $hostname, $end);
|
||||
} else
|
||||
$base_url = 'http://localhost/';
|
||||
|
||||
if ($parse) {
|
||||
$base_url = parse_url($base_url);
|
||||
if (isset($base_url['path']))
|
||||
if ($base_url['path'] == '/')
|
||||
$base_url['path'] = '';
|
||||
}
|
||||
|
||||
return $base_url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function redirectToPageId($id, $args, $dontdie) {
|
||||
header('Location: ' . URL . '?id=' . $id . $args);
|
||||
if (is_null($dontdie)) {
|
||||
die("Please go to " . URL . '?id=' . $id . $args);
|
||||
}
|
||||
}
|
||||
|
||||
function redirectIfNotLoggedIn() {
|
||||
global $SETTINGS;
|
||||
if ($_SESSION['loggedin'] !== TRUE) {
|
||||
header('Location: ' . URL . '/login.php');
|
||||
header('Location: ' . $SETTINGS['url'] . '/index.php');
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
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>
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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 (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 (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
|
||||
*/
|
||||
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 (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
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the client's IP has been doing too many brute-force-friendly
|
||||
* requests lately.
|
||||
@ -371,12 +176,12 @@ function engageRateLimit() {
|
||||
global $database;
|
||||
$delay = date("Y-m-d H:i:s", strtotime("-2 seconds"));
|
||||
$database->delete('rate_limit', ["lastaction[<]" => $delay]);
|
||||
if ($database->has('rate_limit', ["AND" => ["ipaddr" => getClientIP()]])) {
|
||||
if ($database->has('rate_limit', ["AND" => ["ipaddr" => IPUtils::getClientIP()]])) {
|
||||
http_response_code(429);
|
||||
// JSONify it so API clients don't scream too loud
|
||||
die(json_encode(["status" => "ERROR", "msg" => "You're going too fast. Slow down, mkay?"]));
|
||||
} else {
|
||||
// Add a record for the IP address
|
||||
$database->insert('rate_limit', ["ipaddr" => getClientIP(), "lastaction" => date("Y-m-d H:i:s")]);
|
||||
$database->insert('rate_limit', ["ipaddr" => IPUtils::getClientIP(), "lastaction" => date("Y-m-d H:i:s")]);
|
||||
}
|
||||
}
|
||||
|
@ -1,169 +1,166 @@
|
||||
<?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/.
|
||||
*/
|
||||
|
||||
// Settings for the app.
|
||||
// Copy to settings.php and customize.
|
||||
|
||||
// Whether to show debugging data in output.
|
||||
// DO NOT SET TO TRUE IN PRODUCTION!!!
|
||||
define("DEBUG", false);
|
||||
|
||||
// Database connection settings
|
||||
// See http://medoo.in/api/new for info
|
||||
define("DB_TYPE", "mysql");
|
||||
define("DB_NAME", "accounthub");
|
||||
define("DB_SERVER", "localhost");
|
||||
define("DB_USER", "accounthub");
|
||||
define("DB_PASS", "");
|
||||
define("DB_CHARSET", "utf8");
|
||||
|
||||
define("SITE_TITLE", "AccountHub");
|
||||
|
||||
// Used to identify the system in OTP and other places
|
||||
define("SYSTEM_NAME", "Netsyms SSO Demo");
|
||||
|
||||
// For supported values, see http://php.net/manual/en/timezones.php
|
||||
define("TIMEZONE", "America/Denver");
|
||||
|
||||
// Allow or prevent users from logging in via the mobile app.
|
||||
define("MOBILE_ENABLED", TRUE);
|
||||
|
||||
// Base URL for site links.
|
||||
define('URL', 'http://localhost/accounthub');
|
||||
|
||||
// Use Captcheck on login screen
|
||||
// https://captcheck.netsyms.com
|
||||
define("CAPTCHA_ENABLED", FALSE);
|
||||
define('CAPTCHA_SERVER', 'https://captcheck.netsyms.com');
|
||||
|
||||
// See lang folder for language options
|
||||
define('LANGUAGE', "en");
|
||||
|
||||
// List of available applications, icons, and other info.
|
||||
// Used in the mobile app and in the "dock" in AccountHub.
|
||||
define('EXTERNAL_APPS', [
|
||||
"accounthub" => [
|
||||
"url" => "/accounthub",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => SITE_TITLE
|
||||
$SETTINGS = [
|
||||
// Whether to output debugging info like PHP notices, warnings,
|
||||
// and stacktraces.
|
||||
// Turning this on in production is a security risk and can sometimes break
|
||||
// things, such as JSON output where extra content is not expected.
|
||||
"debug" => false,
|
||||
// Database connection settings
|
||||
// See http://medoo.in/api/new for info
|
||||
"database" => [
|
||||
"type" => "mysql",
|
||||
"name" => "accounthub",
|
||||
"server" => "localhost",
|
||||
"user" => "accounthub",
|
||||
"password" => "",
|
||||
"charset" => "utf8"
|
||||
],
|
||||
"qwikclock" => [
|
||||
"url" => "/qwikclock",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "QwikClock",
|
||||
"station_features" => [
|
||||
"qwikclock_punchinout",
|
||||
"qwikclock_myshifts",
|
||||
"qwikclock_jobs"
|
||||
// Name of the app.
|
||||
"site_title" => "AccountHub",
|
||||
// Used to identify the system in OTP and other places
|
||||
"system_name" => "Netsyms AccountHub",
|
||||
// Allow login from the Netsyms mobile app
|
||||
"mobile_enabled" => true,
|
||||
// Allow users to signup for new accounts
|
||||
"signups_enabled" => false,
|
||||
// Terms of Service URL for user signup
|
||||
"tos_url" => "",
|
||||
// For supported values, see http://php.net/manual/en/timezones.php
|
||||
"timezone" => "America/Denver",
|
||||
// List of external apps connected to this system.
|
||||
// This list is used for generating the dashboard cards and in the
|
||||
// mobile app.
|
||||
"apps" => [
|
||||
"accounthub" => [
|
||||
"url" => "/accounthub",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "AccountHub"
|
||||
],
|
||||
"card" => [
|
||||
"color" => "blue",
|
||||
"string" => "Punch in and check work schedule"
|
||||
]
|
||||
],
|
||||
"binstack" => [
|
||||
"url" => "/binstack",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "BinStack",
|
||||
"card" => [
|
||||
"color" => "green",
|
||||
"string" => "Manage physical items"
|
||||
]
|
||||
],
|
||||
"newspen" => [
|
||||
"url" => "/newspen",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "NewsPen",
|
||||
"card" => [
|
||||
"color" => "purple",
|
||||
"string" => "Create and publish e-newsletters"
|
||||
]
|
||||
],
|
||||
"managepanel" => [
|
||||
"url" => "/managepanel",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "ManagePanel",
|
||||
"card" => [
|
||||
"color" => "brown",
|
||||
"string" => "Manage users, permissions, and security"
|
||||
]
|
||||
],
|
||||
"nickelbox" => [
|
||||
"url" => "/nickelbox",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "NickelBox",
|
||||
"card" => [
|
||||
"color" => "light-green",
|
||||
"text" => "dark",
|
||||
"string" => "Checkout customers and manage online orders"
|
||||
]
|
||||
],
|
||||
"sitewriter" => [
|
||||
"url" => "/sitewriter",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "SiteWriter",
|
||||
"card" => [
|
||||
"color" => "light-blue",
|
||||
"string" => "Build websites and manage contact form messages"
|
||||
]
|
||||
],
|
||||
"taskfloor" => [
|
||||
"url" => "/taskfloor",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "TaskFloor",
|
||||
"station_features" => [
|
||||
"taskfloor_viewtasks",
|
||||
"taskfloor_viewmessages"
|
||||
"qwikclock" => [
|
||||
"url" => "/qwikclock",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "QwikClock",
|
||||
"station_features" => [
|
||||
"qwikclock_punchinout",
|
||||
"qwikclock_myshifts",
|
||||
"qwikclock_jobs"
|
||||
],
|
||||
"card" => [
|
||||
"color" => "blue",
|
||||
"string" => "Punch in and check work schedule"
|
||||
]
|
||||
],
|
||||
"card" => [
|
||||
"color" => "blue-grey",
|
||||
"string" => "Track jobs and assigned tasks"
|
||||
"binstack" => [
|
||||
"url" => "/binstack",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "BinStack",
|
||||
"card" => [
|
||||
"color" => "green",
|
||||
"string" => "Manage physical items"
|
||||
]
|
||||
],
|
||||
"newspen" => [
|
||||
"url" => "/newspen",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "NewsPen",
|
||||
"card" => [
|
||||
"color" => "purple",
|
||||
"string" => "Create and publish e-newsletters"
|
||||
]
|
||||
],
|
||||
"managepanel" => [
|
||||
"url" => "/managepanel",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "ManagePanel",
|
||||
"card" => [
|
||||
"color" => "brown",
|
||||
"string" => "Manage users, permissions, and security"
|
||||
]
|
||||
],
|
||||
"nickelbox" => [
|
||||
"url" => "/nickelbox",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "NickelBox",
|
||||
"card" => [
|
||||
"color" => "light-green",
|
||||
"text" => "dark",
|
||||
"string" => "Checkout customers and manage online orders"
|
||||
]
|
||||
],
|
||||
"sitewriter" => [
|
||||
"url" => "/sitewriter",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "SiteWriter",
|
||||
"card" => [
|
||||
"color" => "light-blue",
|
||||
"string" => "Build websites and manage contact form messages"
|
||||
]
|
||||
],
|
||||
"taskfloor" => [
|
||||
"url" => "/taskfloor",
|
||||
"mobileapi" => "/mobile/index.php",
|
||||
"icon" => "/static/img/logo.svg",
|
||||
"title" => "TaskFloor",
|
||||
"station_features" => [
|
||||
"taskfloor_viewtasks",
|
||||
"taskfloor_viewmessages"
|
||||
],
|
||||
"card" => [
|
||||
"color" => "blue-grey",
|
||||
"string" => "Track jobs and assigned tasks"
|
||||
]
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
// Used for notification timestamp display.
|
||||
define("DATETIME_FORMAT", "M j, g:i a");
|
||||
define("TIME_FORMAT", "g:i");
|
||||
|
||||
|
||||
// Email settings for receiving admin alerts.
|
||||
define("USE_SMTP", TRUE); // if FALSE, will use PHP's mail() instead
|
||||
define("ADMIN_EMAIL", "");
|
||||
define("FROM_EMAIL", "alert-noreply@apps.biz.netsyms.com");
|
||||
define("SMTP_HOST", "");
|
||||
define("SMTP_AUTH", true);
|
||||
define("SMTP_PORT", 587);
|
||||
define("SMTP_SECURE", 'tls');
|
||||
define("SMTP_USER", "");
|
||||
define("SMTP_PASS", "");
|
||||
define("SMTP_ALLOW_INVALID_CERTIFICATE", TRUE);
|
||||
|
||||
// Minimum length for new passwords
|
||||
// The system checks new passwords against the 500 worst passwords and rejects
|
||||
// any matches.
|
||||
// If you want to have additional password requirements, go edit action.php.
|
||||
// However, all that does is encourage people to use the infamous
|
||||
// "post-it password manager". See also https://xkcd.com/936/ and
|
||||
// http://stackoverflow.com/a/34166252 for reasons why forcing passwords
|
||||
// like CaPs45$% is not actually a great idea.
|
||||
// Encourage users to use 2-factor auth whenever possible.
|
||||
define("MIN_PASSWORD_LENGTH", 8);
|
||||
|
||||
// Maximum number of rows to get in a query.
|
||||
define("QUERY_LIMIT", 1000);
|
||||
|
||||
|
||||
|
||||
define("FOOTER_TEXT", "");
|
||||
define("COPYRIGHT_NAME", "Netsyms Technologies");
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Settings for sending emails.
|
||||
"email" => [
|
||||
// If false, will use PHP mail() instead of a server
|
||||
"use_smtp" => true,
|
||||
// Admin email for alerts
|
||||
"admin_email" => "",
|
||||
"from" => "alert-noreply@example.com",
|
||||
"host" => "",
|
||||
"auth" => true,
|
||||
"port" => 587,
|
||||
"secure" => "tls",
|
||||
"user" => "",
|
||||
"password" => "",
|
||||
"allow_invalid_certificate" => true
|
||||
],
|
||||
"min_password_length" => 8,
|
||||
// Show or hide the Station PIN setup option.
|
||||
"station_kiosk" => true,
|
||||
// Used for notification timestamp display.
|
||||
"datetime_format" => "M j, g:i a",
|
||||
"time_format" => "g:i",
|
||||
// Use Captcheck on login screen to slow down bots
|
||||
// https://captcheck.netsyms.com
|
||||
"captcha" => [
|
||||
"enabled" => false,
|
||||
"server" => "https://captcheck.netsyms.com"
|
||||
],
|
||||
// Language to use for localization. See langs folder to add a language.
|
||||
"language" => "en",
|
||||
// Shown in the footer of all the pages.
|
||||
"footer_text" => "",
|
||||
// Also shown in the footer, but with "Copyright <current_year>" in front.
|
||||
"copyright" => "Netsyms Technologies",
|
||||
// Base URL for building links relative to the location of the app.
|
||||
"url" => "/accounthub/"
|
||||
];
|
||||
|
@ -14,7 +14,7 @@ if ($database->has('accounts', ["[>]assigned_permissions" => ["uid" => "uid"]],
|
||||
die("An admin account already exists, exiting.");
|
||||
}
|
||||
|
||||
if (is_empty($_POST['username']) || is_empty($_POST['password']) || is_empty($_POST['realname'])) {
|
||||
if (empty($_POST['username']) || empty($_POST['password']) || empty($_POST['realname'])) {
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<title>Admin Account Creation</title>
|
||||
|
207
signup/index.php
Normal file
207
signup/index.php
Normal file
@ -0,0 +1,207 @@
|
||||
<?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';
|
||||
|
||||
if ($SETTINGS['signups_enabled'] !== true) {
|
||||
http_response_code(403);
|
||||
die("Account creation not allowed. Contact the site administrator for an account.");
|
||||
}
|
||||
|
||||
function showHTML($errormsg = null, $genform = true, $noformcontent = "", $title = null) {
|
||||
global $SETTINGS, $SECURE_NONCE, $Strings;
|
||||
|
||||
try {
|
||||
$textcaptcha = json_decode(file_get_contents("https://api.textcaptcha.com/netsyms.com.json"));
|
||||
$captchaquestion = $textcaptcha->q;
|
||||
$_SESSION["textcaptchaanswers"] = $textcaptcha->a;
|
||||
} catch (Exception $ex) {
|
||||
$captchaquestion = "";
|
||||
}
|
||||
|
||||
$form = new FormBuilder("", "", "", "POST");
|
||||
|
||||
$form->setID("signupform");
|
||||
|
||||
$form->addInput("username", "", "text", true, null, null, "Username", "fas fa-id-card", 6, 4, 100, "[a-zA-Z0-9]+", $Strings->get("Please enter your username (4-100 characters, alphanumeric).", false));
|
||||
$form->addInput("password", "", "password", true, null, null, "Password", "fas fa-lock", 6, $SETTINGS['min_password_length'], 255, "", $Strings->build("Your password must be at least {n} characters long.", ["n" => $SETTINGS['min_password_length']], false));
|
||||
$form->addInput("email", "", "email", false, null, null, "Email", "fas fa-envelope", 6, 5, 255, "", $Strings->get("That email address doesn't look right.", false));
|
||||
$form->addInput("name", "", "text", true, null, null, "Name", "fas fa-user", 6, 2, 200, "", $Strings->get("Enter your name.", false));
|
||||
if (!empty($captchaquestion)) {
|
||||
$form->addInput("textcaptcha", "", "text", true, null, null, "$captchaquestion", "fas fa-robot", 12, 1, 200, "", "");
|
||||
} else {
|
||||
$form->addHiddenInput("textcaptcha", "DISABLE" . hash("sha1", hash("md5", date("Ymd"))));
|
||||
}
|
||||
$form->addHiddenInput("code", empty($_GET["code"]) ? "" : $_GET["code"]);
|
||||
$form->addHiddenInput("redirect", empty($_GET["redirect"]) ? "" : $_GET["code"]);
|
||||
|
||||
if (!empty($SETTINGS['tos_url'])) {
|
||||
$form->addInput("agree_tos", "1", "checkbox", true, null, null, "I agree to the <a href=\"$SETTINGS[tos_url]\" target=\"_BLANK\">terms of service</a>");
|
||||
}
|
||||
|
||||
$form->addHiddenInput("submit", "1");
|
||||
|
||||
$form->addButton($Strings->get("Create Account", false), "fas fa-user-plus", null, "submit", "savebtn");
|
||||
?>
|
||||
<!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/svg-with-js.min.css" rel="stylesheet">
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
FontAwesomeConfig = {autoAddCss: false}
|
||||
</script>
|
||||
<style nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
.display-5 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
max-height: 100px;
|
||||
margin: 2em auto;
|
||||
border: 1px solid grey;
|
||||
border-radius: 15%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-5 mb-4"><?php
|
||||
if (is_null($title)) {
|
||||
$Strings->get("Create Account");
|
||||
} else {
|
||||
echo $title;
|
||||
}
|
||||
?></h1>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-8">
|
||||
<div class="mt-4">
|
||||
<?php
|
||||
if (!is_null($errormsg)) {
|
||||
?>
|
||||
<div class="alert alert-danger">
|
||||
<?php echo $errormsg; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ($genform) {
|
||||
$form->generate();
|
||||
} else {
|
||||
echo $noformcontent;
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../static/js/fontawesome-all.min.js"></script>
|
||||
<script src="../static/js/jquery-3.3.1.min.js"></script>
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
$("#savebtn").click(function (event) {
|
||||
var form = $("#signupform");
|
||||
|
||||
if (form[0].checkValidity() === false) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
form.addClass('was-validated');
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
die();
|
||||
}
|
||||
|
||||
// If we didn't submit the form yet
|
||||
if (empty($_POST['submit'])) {
|
||||
showHTML();
|
||||
}
|
||||
|
||||
|
||||
// Validation
|
||||
if (empty($_POST['username'])) {
|
||||
showHTML($Strings->get("Choose a username.", false));
|
||||
}
|
||||
$_POST['username'] = strtolower($_POST['username']);
|
||||
if (!preg_match("/^[a-z0-9]+$/", $_POST['username'])) {
|
||||
showHTML($Strings->get("Please enter your username (4-100 characters, alphanumeric).", false));
|
||||
}
|
||||
if (User::byUsername($_POST['username'])->exists()) {
|
||||
showHTML($Strings->get("Username already taken, pick another.", false));
|
||||
}
|
||||
if (empty($_POST['password'])) {
|
||||
showHTML($Strings->get("Choose a password.", false));
|
||||
}
|
||||
if (strlen($_POST['password']) < $SETTINGS['min_password_length']) {
|
||||
showHTML($Strings->build("Your password must be at least {n} characters long.", ["n" => $SETTINGS[min_password_length]], false));
|
||||
}
|
||||
require_once __DIR__ . "/../lib/worst_passwords.php";
|
||||
$passrank = checkWorst500List($_POST['password']);
|
||||
if ($passrank !== FALSE) {
|
||||
showHTML($Strings->get("That password is one of the most popular and insecure ever, make a better one.", false));
|
||||
}
|
||||
if (!empty($_POST['email']) && !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
showHTML($Strings->get("That email address doesn't look right.", false));
|
||||
}
|
||||
if (!empty($_POST['email']) && $database->has("accounts", ["email" => strtolower($_POST['email'])])) {
|
||||
showHTML($Strings->get("That email address is already in use.", false));
|
||||
}
|
||||
if (empty($_POST['name'])) {
|
||||
showHTML($Strings->get("Enter your name.", false));
|
||||
}
|
||||
if ($_POST["textcaptcha"] != "DISABLE" . hash("sha1", hash("md5", date("Ymd")))) {
|
||||
$answer = hash("md5", strtolower($_POST["textcaptcha"]));
|
||||
$ok = false;
|
||||
foreach ($_SESSION["textcaptchaanswers"] as $ans) {
|
||||
if ($ans == $answer) {
|
||||
$ok = true;
|
||||
}
|
||||
}
|
||||
if (!$ok) {
|
||||
showHTML($Strings->get("CAPTCHA answer incorrect.", false));
|
||||
}
|
||||
}
|
||||
|
||||
// Create account
|
||||
|
||||
$userid = User::add($_POST['username'], $_POST['password'], $_POST['name'], (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) ? strtolower($_POST['email']) : null));
|
||||
$signinstr = $Strings->get("sign in", false);
|
||||
$redirect = urlencode($_POST["redirect"]);
|
||||
$code = urlencode($_POST["code"]);
|
||||
if (!empty($code)) {
|
||||
showHTML(null, false, <<<END
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<a href="../login/?code=$code&redirect=$redirect" class="btn btn-primary btn-block">$signinstr</a>
|
||||
</div>
|
||||
</div>
|
||||
END
|
||||
, $Strings->get("Account Created", false));
|
||||
} else {
|
||||
showHTML(null, false, <<<END
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<a href="../" class="btn btn-primary btn-block">$signinstr</a>
|
||||
</div>
|
||||
</div>
|
||||
END
|
||||
, $Strings->get("Account Created", false));
|
||||
}
|
12
static/css/bootstrap.min.css
vendored
12
static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +0,0 @@
|
||||
/*!
|
||||
* Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1em}.svg-inline--fa.fa-stack-2x{height:2em;width:2em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}
|
5
static/css/fontawesome-all.min.css
vendored
Normal file
5
static/css/fontawesome-all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,15 +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/. */
|
||||
|
||||
.banner-image {
|
||||
max-height: 100px;
|
||||
margin: 2em auto;
|
||||
border: 1px solid grey;
|
||||
border-radius: 15%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 10em;
|
||||
text-align: center;
|
||||
}
|
23
static/css/login.css
Normal file
23
static/css/login.css
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
.display-5 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
max-height: 100px;
|
||||
margin: 2em auto;
|
||||
border: 1px solid grey;
|
||||
border-radius: 15%;
|
||||
}
|
||||
|
||||
.blank-image {
|
||||
height: 100px;
|
||||
margin: 2em auto;
|
||||
}
|
5
static/css/svg-with-js.min.css
vendored
Normal file
5
static/css/svg-with-js.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
static/img/logo.png
Normal file
BIN
static/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -13,7 +13,7 @@ $(document).ready(function () {
|
||||
var gone = 20;
|
||||
|
||||
var msgticker = setInterval(function () {
|
||||
if ($('#msg-alert-box .alert:hover').length) {
|
||||
if ($("#msg-alert-box .alert:hover").length) {
|
||||
msginteractiontick = 0;
|
||||
} else {
|
||||
msginteractiontick++;
|
||||
@ -55,13 +55,14 @@ $(document).ready(function () {
|
||||
$("#msg-alert-box").on("mouseenter", function () {
|
||||
$("#msg-alert-box").css("opacity", "1");
|
||||
msginteractiontick = 0;
|
||||
console.log("👈😎👈 zoop");
|
||||
});
|
||||
$("#msg-alert-box").on("click", ".close", function (e) {
|
||||
$("#msg-alert-box").fadeOut("slow");
|
||||
window.clearInterval(msgticker);
|
||||
});
|
||||
}
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
||||
|
||||
@ -78,4 +79,4 @@ try {
|
||||
window.history.replaceState("", "", getniceurl());
|
||||
} catch (ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
static/js/bootstrap.min.js
vendored
7
static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
6
static/js/fontawesome-all.min.js
vendored
6
static/js/fontawesome-all.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
static/webfonts/fa-brands-400.eot
Normal file
BIN
static/webfonts/fa-brands-400.eot
Normal file
Binary file not shown.
3570
static/webfonts/fa-brands-400.svg
Normal file
3570
static/webfonts/fa-brands-400.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 699 KiB |
BIN
static/webfonts/fa-brands-400.ttf
Normal file
BIN
static/webfonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
static/webfonts/fa-brands-400.woff
Normal file
BIN
static/webfonts/fa-brands-400.woff
Normal file
Binary file not shown.
BIN
static/webfonts/fa-brands-400.woff2
Normal file
BIN
static/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
static/webfonts/fa-regular-400.eot
Normal file
BIN
static/webfonts/fa-regular-400.eot
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user