Single-sign-on and self-serve account management. https://netsyms.biz/apps/accounthub
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.php 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. /*
  6. * Mobile app API
  7. */
  8. require __DIR__ . "/../required.php";
  9. header('Content-Type: application/json');
  10. header('Access-Control-Allow-Origin: *');
  11. // Allow ping check without authentication
  12. if ($VARS['action'] == "ping") {
  13. exit(json_encode(["status" => "OK"]));
  14. }
  15. if (MOBILE_ENABLED !== TRUE) {
  16. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
  17. }
  18. // Make sure we have a username and access key
  19. if (empty($VARS['username']) || empty($VARS['key'])) {
  20. http_response_code(401);
  21. die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
  22. }
  23. $username = strtolower($VARS['username']);
  24. $key = strtoupper($VARS['key']);
  25. // Make sure the username and key are actually legit
  26. // Don't check key if we're trying to generate one
  27. if ($VARS['action'] == "generatesynccode") {
  28. if (!User::byUsername($username)->exists()) {
  29. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  30. die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
  31. }
  32. } else {
  33. $user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $key, 'accounts.username' => $username]]);
  34. if ($user_key_valid !== TRUE) {
  35. engageRateLimit();
  36. Log::insert(LogType::MOBILE_BAD_KEY, null, "Username: " . $username . ", Key: " . $key);
  37. die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
  38. }
  39. }
  40. // Obscure key
  41. if (strlen($key) > 7) {
  42. for ($i = 3; $i < strlen($key) - 3; $i++) {
  43. $key[$i] = "*";
  44. }
  45. }
  46. // Process the action
  47. switch ($VARS['action']) {
  48. case "check_key":
  49. // Check if the username/key combo is valid.
  50. // If we get this far, it is, so return success.
  51. exit(json_encode(["status" => "OK"]));
  52. case "check_password":
  53. // Check if the user-supplied password is valid.
  54. engageRateLimit();
  55. $user = User::byUsername($username);
  56. if ($user->getStatus()->get() != AccountStatus::NORMAL) {
  57. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  58. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
  59. }
  60. if ($user->checkPassword($VARS['password'])) {
  61. Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
  62. exit(json_encode(["status" => "OK", "uid" => $user->getUID()]));
  63. } else {
  64. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  65. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
  66. }
  67. case "user_info":
  68. engageRateLimit();
  69. $user = User::byUsername($username);
  70. if ($user->getStatus()->get() != AccountStatus::NORMAL) {
  71. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  72. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
  73. }
  74. if ($user->checkPassword($VARS['password'])) {
  75. $userinfo = ["uid" => $user->getUID(), "username" => $user->getUsername(), "realname" => $user->getName(), "email" => $user->getEmail()];
  76. Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
  77. exit(json_encode(["status" => "OK", "info" => $userinfo]));
  78. } else {
  79. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  80. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
  81. }
  82. case "start_session":
  83. // Do a web login.
  84. engageRateLimit();
  85. $user = User::byUsername($username);
  86. if ($user->exists()) {
  87. if ($user->getStatus()->get() == AccountStatus::NORMAL) {
  88. if ($user->checkPassword($VARS['password'])) {
  89. Session::start($user);
  90. $_SESSION['mobile'] = true;
  91. exit(json_encode(["status" => "OK"]));
  92. }
  93. }
  94. }
  95. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  96. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
  97. case "listapps":
  98. $apps = EXTERNAL_APPS;
  99. // Format paths as absolute URLs
  100. foreach ($apps as $k => $v) {
  101. if (strpos($apps[$k]['url'], "http") === FALSE) {
  102. $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'];
  103. }
  104. }
  105. exit(json_encode(["status" => "OK", "apps" => $apps]));
  106. case "gencode":
  107. engageRateLimit();
  108. $user = User::byUsername($username);
  109. $code = "";
  110. do {
  111. $code = random_int(100000, 999999);
  112. } while ($database->has("onetimekeys", ["key" => $code]));
  113. $database->insert("onetimekeys", ["key" => $code, "uid" => $user->getUID(), "expires" => date("Y-m-d H:i:s", strtotime("+1 minute"))]);
  114. $database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
  115. exit(json_encode(["status" => "OK", "code" => $code]));
  116. case "checknotifications":
  117. if (!empty($VARS['username'])) {
  118. $user = User::byUsername($VARS['username']);
  119. } else if (!empty($VARS['uid'])) {
  120. $user = new User($VARS['uid']);
  121. } else {
  122. http_response_code(400);
  123. die("\"400 Bad Request\"");
  124. }
  125. try {
  126. $notifications = Notifications::get($user, false);
  127. exit(json_encode(["status" => "OK", "notifications" => $notifications]));
  128. } catch (Exception $ex) {
  129. exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
  130. }
  131. break;
  132. case "readnotification":
  133. if (!empty($VARS['username'])) {
  134. $user = User::byUsername($VARS['username']);
  135. } else if (!empty($VARS['uid'])) {
  136. $user = new User($VARS['uid']);
  137. } else {
  138. http_response_code(400);
  139. die("\"400 Bad Request\"");
  140. }
  141. if (empty($VARS['id'])) {
  142. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
  143. }
  144. try {
  145. Notifications::read($user, $VARS['id']);
  146. exit(json_encode(["status" => "OK"]));
  147. } catch (Exception $ex) {
  148. exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
  149. }
  150. break;
  151. case "addnotification":
  152. if (!empty($VARS['username'])) {
  153. $user = User::byUsername($VARS['username']);
  154. } else if (!empty($VARS['uid'])) {
  155. $user = new User($VARS['uid']);
  156. } else {
  157. http_response_code(400);
  158. die("\"400 Bad Request\"");
  159. }
  160. try {
  161. $timestamp = "";
  162. if (!empty($VARS['timestamp'])) {
  163. $timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
  164. }
  165. $url = "";
  166. if (!empty($VARS['url'])) {
  167. $url = $VARS['url'];
  168. }
  169. $nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));
  170. exit(json_encode(["status" => "OK", "id" => $nid]));
  171. } catch (Exception $ex) {
  172. exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
  173. }
  174. break;
  175. case "deletenotification":
  176. if (!empty($VARS['username'])) {
  177. $user = User::byUsername($VARS['username']);
  178. } else if (!empty($VARS['uid'])) {
  179. $user = new User($VARS['uid']);
  180. } else {
  181. http_response_code(400);
  182. die("\"400 Bad Request\"");
  183. }
  184. if (empty($VARS['id'])) {
  185. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
  186. }
  187. try {
  188. Notifications::delete($user, $VARS['id']);
  189. exit(json_encode(["status" => "OK"]));
  190. } catch (Exception $ex) {
  191. exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
  192. }
  193. break;
  194. case "hasotp":
  195. if (!empty($VARS['username'])) {
  196. $user = User::byUsername($VARS['username']);
  197. } else if (!empty($VARS['uid'])) {
  198. $user = new User($VARS['uid']);
  199. } else {
  200. http_response_code(400);
  201. die("\"400 Bad Request\"");
  202. }
  203. exit(json_encode(["status" => "OK", "otp" => $user->has2fa()]));
  204. break;
  205. case "generatesynccode":
  206. $user = User::byUsername($username);
  207. if ($user->has2fa()) {
  208. 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)]));
  209. }
  210. if ($user->getStatus()->get() != AccountStatus::NORMAL) {
  211. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  212. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
  213. }
  214. if ($user->checkPassword($VARS['password'])) {
  215. Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
  216. $code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
  217. $desc = htmlspecialchars($VARS['desc']);
  218. $database->insert('mobile_codes', ['uid' => $user->getUID(), 'code' => $code, 'description' => $desc]);
  219. exit(json_encode(["status" => "OK", "code" => $code]));
  220. } else {
  221. Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
  222. exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
  223. }
  224. default:
  225. http_response_code(404);
  226. die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));
  227. }