<?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/. */

/*
 * Mobile app API
 */

require __DIR__ . "/../required.php";

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

// Allow ping check without authentication
if ($VARS['action'] == "ping") {
    exit(json_encode(["status" => "OK"]));
}

if ($SETTINGS['mobile_enabled'] !== TRUE) {
    exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
}

// Make sure we have a username and access key
if (empty($VARS['username']) || empty($VARS['key'])) {
    http_response_code(401);
    die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
}

$username = strtolower($VARS['username']);
$key = strtoupper($VARS['key']);

// Make sure the username and key are actually legit
// Don't check key if we're trying to generate one
if ($VARS['action'] == "generatesynccode") {
    if (!User::byUsername($username)->exists()) {
        Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
        die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
    }
} else {
    $user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $key, 'accounts.username' => $username]]);
    if ($user_key_valid !== TRUE) {
        engageRateLimit();
        Log::insert(LogType::MOBILE_BAD_KEY, null, "Username: " . $username . ", Key: " . $key);
        die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."]));
    }
}

// Obscure key
if (strlen($key) > 7) {
    for ($i = 3; $i < strlen($key) - 3; $i++) {
        $key[$i] = "*";
    }
}

// Process the action
switch ($VARS['action']) {
    case "check_key":
        // Check if the username/key combo is valid.
        // If we get this far, it is, so return success.
        exit(json_encode(["status" => "OK"]));
    case "check_password":
        // Check if the user-supplied password is valid.
        engageRateLimit();
        $user = User::byUsername($username);
        if ($user->getStatus()->get() != AccountStatus::NORMAL) {
            Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
        }
        if ($user->checkPassword($VARS['password'])) {
            Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
            exit(json_encode(["status" => "OK", "uid" => $user->getUID()]));
        } else {
            Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
        }
    case "user_info":
        engageRateLimit();
        $user = User::byUsername($username);
        if ($user->getStatus()->get() != AccountStatus::NORMAL) {
            Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
        }
        if ($user->checkPassword($VARS['password'])) {
            $userinfo = ["uid" => $user->getUID(), "username" => $user->getUsername(), "realname" => $user->getName(), "email" => $user->getEmail()];
            Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
            exit(json_encode(["status" => "OK", "info" => $userinfo]));
        } else {
            Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
        }
    case "start_session":
        // Do a web login.
        engageRateLimit();
        $user = User::byUsername($username);
        if ($user->exists()) {
            if ($user->getStatus()->get() == AccountStatus::NORMAL) {
                if ($user->checkPassword($VARS['password'])) {
                    Session::start($user);
                    $_SESSION['mobile'] = true;
                    exit(json_encode(["status" => "OK"]));
                }
            }
        }
        Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
        exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
    case "listapps":
        $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'];
            }
        }
        exit(json_encode(["status" => "OK", "apps" => $apps]));
    case "gencode":
        engageRateLimit();
        $user = User::byUsername($username);
        $code = "";
        do {
            $code = random_int(100000, 999999);
        } while ($database->has("onetimekeys", ["key" => $code]));

        $database->insert("onetimekeys", ["key" => $code, "uid" => $user->getUID(), "expires" => date("Y-m-d H:i:s", strtotime("+1 minute"))]);

        $database->delete("onetimekeys", ["expires[<]" => date("Y-m-d H:i:s")]); // cleanup
        exit(json_encode(["status" => "OK", "code" => $code]));
    case "checknotifications":
        if (!empty($VARS['username'])) {
            $user = User::byUsername($VARS['username']);
        } else if (!empty($VARS['uid'])) {
            $user = new User($VARS['uid']);
        } else {
            http_response_code(400);
            die("\"400 Bad Request\"");
        }
        try {
            $notifications = Notifications::get($user, false);
            exit(json_encode(["status" => "OK", "notifications" => $notifications]));
        } catch (Exception $ex) {
            exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
        }
        break;
    case "readnotification":
        if (!empty($VARS['username'])) {
            $user = User::byUsername($VARS['username']);
        } else if (!empty($VARS['uid'])) {
            $user = new User($VARS['uid']);
        } else {
            http_response_code(400);
            die("\"400 Bad Request\"");
        }
        if (empty($VARS['id'])) {
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
        }
        try {
            Notifications::read($user, $VARS['id']);
            exit(json_encode(["status" => "OK"]));
        } catch (Exception $ex) {
            exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
        }
        break;
    case "addnotification":
        if (!empty($VARS['username'])) {
            $user = User::byUsername($VARS['username']);
        } else if (!empty($VARS['uid'])) {
            $user = new User($VARS['uid']);
        } else {
            http_response_code(400);
            die("\"400 Bad Request\"");
        }

        try {
            $timestamp = "";
            if (!empty($VARS['timestamp'])) {
                $timestamp = date("Y-m-d H:i:s", strtotime($VARS['timestamp']));
            }
            $url = "";
            if (!empty($VARS['url'])) {
                $url = $VARS['url'];
            }
            $nid = Notifications::add($user, $VARS['title'], $VARS['content'], $timestamp, $url, isset($VARS['sensitive']));

            exit(json_encode(["status" => "OK", "id" => $nid]));
        } catch (Exception $ex) {
            exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
        }
        break;
    case "deletenotification":
        if (!empty($VARS['username'])) {
            $user = User::byUsername($VARS['username']);
        } else if (!empty($VARS['uid'])) {
            $user = new User($VARS['uid']);
        } else {
            http_response_code(400);
            die("\"400 Bad Request\"");
        }

        if (empty($VARS['id'])) {
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("invalid parameters", false)]));
        }
        try {
            Notifications::delete($user, $VARS['id']);
            exit(json_encode(["status" => "OK"]));
        } catch (Exception $ex) {
            exit(json_encode(["status" => "ERROR", "msg" => $ex->getMessage()]));
        }
        break;
    case "hasotp":
        if (!empty($VARS['username'])) {
            $user = User::byUsername($VARS['username']);
        } else if (!empty($VARS['uid'])) {
            $user = new User($VARS['uid']);
        } else {
            http_response_code(400);
            die("\"400 Bad Request\"");
        }

        exit(json_encode(["status" => "OK", "otp" => $user->has2fa()]));
        break;
    case "generatesynccode":
        $user = User::byUsername($username);
        if ($user->has2fa()) {
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("2-factor is enabled, you need to use the QR code or manual setup for security reasons", false)]));
        }
        if ($user->getStatus()->get() != AccountStatus::NORMAL) {
            Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login failed try on web", false)]));
        }
        if ($user->checkPassword($VARS['password'])) {
            Log::insert(LogType::MOBILE_LOGIN_OK, $user->getUID(), "Key: " . $key);
            $code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
            $desc = htmlspecialchars($VARS['desc']);
            $database->insert('mobile_codes', ['uid' => $user->getUID(), 'code' => $code, 'description' => $desc]);
            exit(json_encode(["status" => "OK", "code" => $code]));
        } else {
            Log::insert(LogType::MOBILE_LOGIN_FAILED, null, "Username: " . $username . ", Key: " . $key);
            exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
        }
    default:
        http_response_code(404);
        die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));
}
