diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ec6a661
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+config.php
+db_file.sqlite
\ No newline at end of file
diff --git a/MatrixConnection.php b/MatrixConnection.php
new file mode 100644
index 0000000..aa63a3c
--- /dev/null
+++ b/MatrixConnection.php
@@ -0,0 +1,155 @@
+hs = $homeserver;
+ $this->at = $access_token;
+ }
+
+ function send($room_id, $message) {
+ if (!$this->at) {
+ error_log("No access token defined");
+ return false;
+ }
+
+ $send_message = NULL;
+ if (!$message) {
+ error_log("no message to send");
+ return false;
+ } elseif(is_array($message)) {
+ $send_message = $message;
+ } elseif ($message instanceof MatrixMessage) {
+ $send_message = $message->get_object();
+ } else {
+ error_log("message is of not valid type\n");
+ return false;
+ }
+
+ $url="https://".$this->hs."/_matrix/client/r0/rooms/"
+ . urlencode($room_id) ."/send/m.room.message?access_token=".$this->at;
+ $handle = curl_init($url);
+ curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
+ curl_setopt($handle, CURLOPT_TIMEOUT, 60);
+ curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($send_message));
+ curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
+
+ $response = $this->exec_curl_request($handle);
+ return isset($response["event_id"]);
+ }
+
+ function send_msg($room_id, $message) {
+ return $this->send($room_id, array(
+ "msgtype" => "m.notice",
+ "body" => $message
+ )
+ );
+ }
+
+ function hasUser($username) {
+ if (!$username) {
+ throw new Exception ("no user given to lookup");
+ }
+
+ $url = "https://".$this->hs."/_matrix/client/r0/profile/@" . $username . ":" . $this->hs;
+ $handle = curl_init($url);
+ curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
+ curl_setopt($handle, CURLOPT_TIMEOUT, 60);
+ curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
+
+ $res = $this->exec_curl_request($handle);
+ return !(isset($res["errcode"]) && $res["errcode"] == "M_UNKNOWN");
+ }
+
+ function register($username, $password, $shared_secret) {
+ if (!$username) {
+ error_log("no username provided");
+ }
+ if (!$password) {
+ error_log("no message to send");
+ }
+
+ $mac = hash_hmac('sha1', $username, $shared_secret);
+
+ $data = array(
+ "username" => $username,
+ "password" => $password,
+ "mac" => $mac,
+ );
+ $url = "https://".$this->hs."/_matrix/client/v2_alpha/register";
+ $handle = curl_init($url);
+ curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
+ curl_setopt($handle, CURLOPT_TIMEOUT, 60);
+ curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
+ curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($data));
+
+ return $this->exec_curl_request($handle);
+ }
+
+ function exec_curl_request($handle) {
+ $response = curl_exec($handle);
+
+ if ($response === false) {
+ $errno = curl_errno($handle);
+ $error = curl_error($handle);
+ error_log("Curl returned error $errno: $error\n");
+ curl_close($handle);
+ return false;
+ }
+
+ $http_code = intval(curl_getinfo($handle, CURLINFO_HTTP_CODE));
+ curl_close($handle);
+
+ if ($http_code >= 500) {
+ // do not want to DDOS server if something goes wrong
+ sleep(10);
+ return false;
+ } else if ($http_code != 200) {
+ $response = json_decode($response, true);
+ error_log("Request has failed with error {$response['error']}\n");
+ if ($http_code == 401) {
+ throw new Exception('Invalid access token provided');
+ }
+ } else {
+ $response = json_decode($response, true);
+ }
+
+ return $response;
+ }
+}
+
+class MatrixMessage
+{
+ private $message;
+
+ function __construct() {
+ $this->message = ["msgtype" => "m.notice"];
+ }
+
+ function set_type($msgtype) {
+ $this->message["msgtype"] = $msgtype;
+ }
+
+ function set_format($format) {
+ $this->message["format"] = $format;
+ }
+
+ function set_body($body) {
+ $this->message["body"] = $body;
+ }
+
+ function set_formatted_body($fbody, $format="org.matrix.custom.html") {
+ $this->message["formatted_body"] = $fbody;
+ $this->message["format"] = $format;
+ }
+
+ function get_object() {
+ return $this->message;
+ }
+}
+?>
diff --git a/README.md b/README.md
index 3ffa19c..7af7bb2 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,27 @@ This bot provides a two-step-registration for matrix.
This is done in several steps:
- potential new user registers on a bot-provided side
-- bot sends a message to prefined room with a registration notification.
+- bot sends a message to predefined room with a registration notification.
- users in that room now can approve or decline the registration.
-- The bot then uses the registration token to register the user or just drops the registration request.
\ No newline at end of file
+- When approved
+ - the bot creates credentials
+ - sends them to the user
+ - stores them encrypted in own database
+ - provides that credentials to [matrix-synapse-rest-auth](https://github.com/kamax-io/matrix-synapse-rest-auth#integrate) which has to be configured to query login.php
+
+2nd step: Implement the other apis to integrade [mxisd](https://github.com/kamax-io/mxisd/blob/master/docs/backends/rest.md)
+
+## How to install
+
+- Copy `config.sample.php` to `config.php` and configure the bot as you can find there
+- Configure your webserver to publish the folder `public` and configure.
+ The folder `internal` contains files that can be accessed by mxisd or matrix-synapse-rest-auth
+- To integrate with matrix-synapse-rest-auth:
+ - `/_matrix-internal/identity/v1/check_credentials` should map to `internal/login.php`
+- To integrate with mxisd: Have a look at [the docs](https://github.com/kamax-io/mxisd/blob/master/docs/backends/rest.md) and apply as follows:
+| Key | file which handles that | Description |
+|--------------------------------|-------------------------------|------------------------------------------------------|
+| rest.endpoints.auth | internal/login.php | Validate credentials and get user profile |
+| rest.endpoints.directory | internal/directory_search.php | Search for users by arbitrary input |
+| rest.endpoints.identity.single | internal/identity_single.php | Endpoint to query a single 3PID |
+| rest.endpoints.identity.bulk | internal/identity_bulk.php | Endpoint to query a list of 3PID |
diff --git a/config.sample.php b/config.sample.php
new file mode 100644
index 0000000..6028e67
--- /dev/null
+++ b/config.sample.php
@@ -0,0 +1,26 @@
+ "example.com",
+ "access_token" => "To be used for sending the registration notification",
+
+ // Which e-mail-adresse shall the bot use to send e-mails?
+ "register_email" => 'register_bot@example.com',
+ // Where should the bot post registration requests to?
+ "register_room" => '$registerRoomID:example.com',
+
+ // Where is the public part of the bot located? make sure you have a / at the end
+ "webroot" => "https://myregisterdomain.net/",
+
+ // optional: Do you have a place where howTo's are located? If not leave this value out
+ "howToURL" => "https://my-url-for-storing-howTos.net",
+
+ // When you want to collect the password on registration set this to true
+ "getPasswordOnRegistration" => false,
+
+ // to define where the data should be stored:
+ "databaseURI" => "sqlite:" . dirname(__FILE__) . "/db_file.sqlite",
+ // credentials for sqlite not used
+ "databaseUser" => "dbUser123",
+ "databasePass" => "secretPassword",
+]
+?>
diff --git a/cron.php b/cron.php
new file mode 100644
index 0000000..473a3cd
--- /dev/null
+++ b/cron.php
@@ -0,0 +1,92 @@
+query($sql) as $row) {
+ $first_name = $row["first_name"];
+ $last_name = $row["last_name"];
+ $username = $row["username"];
+ $email = $row["email"];
+ $state = $row["state"];
+
+ try {
+ switch ($state) {
+ case RegisterState::PendingEmailSend:
+ $verify_url = $config["webroot"] . "/verify.php?t=" . $row["verify_token"];
+ $success = send_mail_pending_verification(
+ $config["homeserver"],
+ $row["first_name"] . " " . $row["last_name"],
+ $row["email"],
+ $verify_url);
+
+ if ($success) {
+ $mx_db->setRegistrationStateById(RegisterState::PendingEmailVerify, $row["id"]);
+ } else {
+ throw new Exception("Could not send mail to ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].")");
+ }
+ break;
+ case RegisterState::PendingAdminSend:
+ require_once("MatrixConnection.php");
+ $adminUrl = $config["webroot"] . "/verify_admin.php?t=" . $row["admin_token"];
+ $mxConn = new MatrixConnection($config["homeserver"], $config["access_token"]);
+ $mxMsg = new MatrixMessage();
+ $mxMsg->set_body($first_name . ' ' . $last_name . " möchte sich registrieren und hat folgende Notiz hinterlassen:\r\n"
+ . $row["note"] . "\r\n"
+ . "Zum Bearbeiten hier klicken:\r\n" . $adminUrl);
+ $mxMsg->set_formatted_body($first_name . ' ' . $last_name . " möchte sich registrieren und hat folgende Notiz hinterlassen:
"
+ . $row["note"] . "
"
+ . "Zum Bearbeiten hier klicken");
+ $mxMsg->set_type("m.text");
+ $response = $mxConn->send($config["register_room"], $mxMsg);
+
+ if ($response) {
+ $mx_db->setRegistrationStateById(RegisterState::PendingAdminVerify, $row["id"]);
+
+ send_mail_pending_approval($config["homeserver"], $first_name . " " . $last_name, $email);
+ } else {
+ throw new Exception("Could not send notification for ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].") to admins.");
+ }
+ break;
+ case RegisterState::PendingRegistration:
+ // Registration got accepted but registration failed
+
+ $password = $mx_db->addUser($row["first_name"], $row["last_name"], $row["username"], $row["email"]);
+ if ($password != NULL) {
+ // send registration_success
+ $res = send_mail_registration_success($config["homeserver"], $first_name . " " . $last_name, $email, $username, $password, $config["howToURL"]);
+ if ($res) {
+ $mx_db->setRegistrationStateById(RegisterState::AllDone, $row["id"]);
+ } else {
+ $mx_db->setRegistrationStateById(RegisterState::PendingSendRegistrationMail, $row["id"]);
+ }
+ } else {
+ send_mail_registration_allowed_but_failed($config["homeserver"], $first_name . " " . $last_name, $email);
+ $mxMsg = new MatrixMessage();
+ $mxMsg->set_type("m.text");
+ $mxMsg->set_body("Fehler beim Registrieren von " . $first_name . " " . $last_name . ".");
+ $mxConn->send($config["register_room"], $mxMsg);
+ throw new Exception($language["REGISTRATION_FAILED"]);
+ }
+ break;
+ case RegisterState::PendingSendRegistrationMail:
+ print ("Error: Unhandled state: PendingSendRegistrationMail for " . $first_name . " " . $last_name . " (" . $username . ")\n");
+ break;
+ case RegisterState::RegistrationDeclined:
+ case RegisterState::AllDone:
+ // do reqular cleanup
+ break;
+ }
+ } catch (Exception $e) {
+ print("Error while handling cron for " . $first_name . " " . $last_name . " (" . $username . ")\n");
+ print($e->getMessage());
+ }
+}
+?>
diff --git a/database.php b/database.php
new file mode 100644
index 0000000..d33d6cf
--- /dev/null
+++ b/database.php
@@ -0,0 +1,339 @@
+db = new PDO($db_input, $user, $password);
+ $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->db->exec("CREATE TABLE IF NOT EXISTS registrations(
+ id SERIAL PRIMARY KEY,
+ state INT DEFAULT 0,
+ first_name TEXT,
+ last_name TEXT,
+ username TEXT,
+ password_hash TEXT DEFAULT '',
+ note TEXT,
+ email TEXT,
+ verify_token TEXT,
+ admin_token TEXT,
+ request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )");
+ $this->db->exec("CREATE TABLE IF NOT EXISTS logins (
+ id SERIAL PRIMARY KEY,
+ active INT DEFAULT 1,
+ first_name TEXT,
+ last_name TEXT,
+ localpart TEXT,
+ password_hash TEXT,
+ email TEXT,
+ create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )");
+ // make sure the bot is allowed to login
+ if (!$this->userRegistered("register_bot")) {
+ $password = $this->addUser("Register", "Bot", "register_bot", $config["register_email"]);
+ $config["register_password"] = $password;
+ $myfile = fopen(dirname(__FILE__) . "/config.json", "w");
+ fwrite($myfile, json_encode($config, JSON_PRETTY_PRINT));
+ fclose($myfile);
+ }
+
+ // set writeable when not set already
+ if (strpos($db_input, "sqlite") === 0) {
+ $sqlite_file = substr($db_input, strlen("sqlite:"));
+ if (!is_writable($sqlite_file)) {
+ chmod($sqlite_file, 0660);
+ }
+ unset($sqlite_file);
+ }
+ }
+
+ /**
+ * WARNING: This allows accessing the database directly.
+ * This was only be added for convenience. You are advised to not use this function extensively
+ *
+ * @param sql String wich will be passed directly to the database
+ * @return Response of PDO::query()
+ */
+ function query($sql) {
+ return $this->db->query($sql);
+ }
+
+ function setRegistrationStateVerify($state, $token) {
+ $sql = "UPDATE registrations SET state = " . $state
+ . " WHERE verify_token = '" . $token . "';";
+
+ return $this->db->exec($sql);
+ }
+
+ function setRegistrationStateById($state, $id) {
+ $sql = "UPDATE registrations SET state = " . $state
+ . " WHERE id = '" . $id . "';";
+
+ return $this->db->exec($sql);
+ }
+
+ function setRegistrationStateAdmin($state, $token) {
+ $sql = "UPDATE registrations SET state = " . $state
+ . " WHERE admin_token = '" . $token . "';";
+
+ return $this->db->exec($sql);
+ }
+
+ function setRegistrationState($state, $token) {
+ $sql = "UPDATE registrations SET state = " . $state
+ . " WHERE verify_token = '" . $token . "' OR admin_token = '" . $token . "';";
+
+ return $this->db->exec($sql);
+ }
+
+ function userPendingRegistrations($username) {
+ $sql = "SELECT COUNT(*) FROM registrations WHERE username = '" . $username . "' AND NOT state = "
+ . RegisterState::RegistrationDeclined . " LIMIT 1;";
+ $res = $this->db->query($sql);
+ if ($res->fetchColumn() > 0) {
+ return true;
+ }
+ return false;
+ }
+ function userRegistered($username) {
+ $sql = "SELECT COUNT(*) FROM logins WHERE localpart = '" . $username . "' LIMIT 1;";
+ $res = $this->db->query($sql);
+ if ($res->fetchColumn() > 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds user to the database. Next steps should be sending a verify-mail to the user
+ * @param first_name First name of the user
+ * @param last_name Sirname of the user
+ * @param username the future localpart of that user
+ * @param note Note the user typed in to give a hint
+ * @param email E-Mail-Adress which will be stored into the database.
+ * This will be send to the server on first login
+ *
+ * @return ["verify_token"]
+ */
+ function addRegistration($first_name, $last_name, $username, $note, $email) {
+ if ($this->userPendingRegistrations($username)) {
+ require_once("language.php");
+ throw new Exception($language["USERNAME_PENDING_REGISTRATION"]." (requested)");
+ }
+ if ($this->userRegistered($username)) {
+ require_once("language.php");
+ throw new Exception($language["USERNAME_REGISTERED"] . " (registered)");
+ }
+
+ $verify_token = bin2hex(random_bytes(16));
+ $admin_token = bin2hex(random_bytes(16));
+
+ $this->db->exec("INSERT INTO registrations
+ (first_name, last_name, username, note, email, verify_token, admin_token)
+ VALUES ('" . $first_name."','" . $last_name . "','" . $username . "','" . $note . "','"
+ . $email."','" .$verify_token."','" .$admin_token."')");
+
+ return [
+ "verify_token"=> $verify_token,
+ ];
+ }
+
+ /**
+ * Gets the user for the verify_admin page.
+ *
+ * @return ArrayOfUser|NULL Array with "first_name, last_name, username, note and email"
+ * as members
+ */
+ function getUserForApproval($admin_token) {
+ $sql = "SELECT COUNT(*) FROM registrations WHERE admin_token = '" . $admin_token . "'"
+ . " AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;";
+ $res = $this->db->query($sql);
+ $first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL;
+
+ if ($res->fetchColumn() > 0) {
+ $sql = "SELECT first_name, last_name, username, note, email FROM registrations"
+ . " WHERE admin_token = '" . $admin_token . "'"
+ . " AND state = " . RegisterState::PendingAdminVerify
+ . " LIMIT 1;";
+ foreach ($this->db->query($sql) as $row) {
+ // will only be executed once
+ return $row;
+ }
+ }
+ return NULL;
+ }
+
+ /**
+ * Gets the user when it opens the page to verify its mail
+ *
+ * @return ArrayOfUser|NULL Array with "first_name, last_name, note, email and admin_token"
+ * as members
+ */
+ function getUserForVerify($verify_token) {
+ $sql = "SELECT COUNT(*) FROM registrations WHERE verify_token = '" . $verify_token . "'"
+ . " AND state = " . RegisterState::PendingEmailVerify . " LIMIT 1;";
+ $res = $this->db->query($sql);
+ $first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL;
+
+ if ($res->fetchColumn() > 0) {
+ $sql = "SELECT first_name, last_name, note, email, admin_token FROM registrations "
+ . " WHERE verify_token = '" . $verify_token . "'"
+ . " AND state = " . RegisterState::PendingEmailVerify . " LIMIT 1;";
+ foreach ($this->db->query($sql) as $row) {
+ // will only be executed once
+ return $row;
+ }
+ }
+ return NULL;
+ }
+
+ function getUserForLogin($localpart, $password) {
+ $sql = "SELECT COUNT(*) FROM logins WHERE localpart = '" . $localpart
+ . "' AND active = 1 LIMIT 1;";
+ $res = $this->db->query($sql);
+
+ if ($res->fetchColumn() > 0) {
+ $sql = "SELECT first_name, last_name, email, password_hash FROM logins "
+ . " WHERE localpart = '" . $localpart . "' AND active = 1 LIMIT 1;";
+ foreach ($this->db->query($sql) as $row) {
+ if (password_verify($password, $row["password_hash"])) {
+ return $row;
+ }
+ }
+ }
+ return NULL;
+ }
+
+ /**
+ * adds User to be able to login afterwards.
+ * @param first_name First name of the user
+ * @param last_name Sirname of the user
+ * @param username the future localpart of that user
+ * @param email E-Mail-Adress which will be stored into the database.
+ * This will be send to the server on first login
+ *
+ * @return password|NULL with member password as this method generates a
+ * password and saves that into the database
+ * NULL when failed
+ *
+ */
+ function addUser($first_name, $last_name, $username, $email) {
+ // check if user already exists and abort in that case
+ if ($this->userRegistered($username)) {
+ return NULL;
+ }
+
+ // generate a password with 10 characters
+ $password = bin2hex(openssl_random_pseudo_bytes(5));
+ $password_hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>12]);
+
+ $sql = "INSERT INTO logins (first_name, last_name, localpart, password_hash, email) VALUES "
+ . "('" . $first_name."','" . $last_name . "','" . $username . "','"
+ . $password_hash . "','" . $email . "');";
+
+ if ($this->db->exec($sql)) {
+ return $password;
+ }
+ return NULL;
+ }
+
+ function searchUserByName($search_term) {
+ $term = filter_var($search_term, FILTER_SANITIZE_STRING);
+ $result = array();
+ $sql = "SELECT COUNT(*) FROM logins WHERE"
+ . " localpart LIKE '" . $term . "%' AND active = 1;";
+ $res = $this->db->query($sql);
+
+ if ($res->fetchColumn() > 0) {
+ $sql = "SELECT first_name, last_name, localpart FROM logins WHERE"
+ . " localpart LIKE '" . $term . "%' AND active = 1;";
+ foreach ($this->db->query($sql) as $row) {
+ array_push($result, [
+ "display_name" => $row["first_name"] . " " . $row["last_name"],
+ "user_id" => $row["localpart"],
+ ]);
+ }
+ }
+ return $result;
+ }
+
+ function searchUserByEmail($search_term) {
+ $term = filter_var($search_term, FILTER_SANITIZE_STRING);
+ $result = array();
+ $sql = "SELECT COUNT(*) FROM logins WHERE"
+ . " email = '" . $term . "' AND active = 1;";
+ $res = $this->db->query($sql);
+
+ if ($res->fetchColumn() > 0) {
+ $sql = "SELECT first_name, last_name, localpart FROM logins WHERE"
+ . " email = '" . $term . "' AND active = 1;";
+ foreach ($this->db->query($sql) as $row) {
+ array_push($result, [
+ "display_name" => $row["first_name"] . " " . $row["last_name"],
+ "user_id" => $row["localpart"],
+ ]);
+ }
+ }
+ return $result;
+ }
+}
+
+if (!isset($mx_db)) {
+ $mx_db = new mxDatabase($config);
+}
+?>
diff --git a/internal/directory_search.php b/internal/directory_search.php
new file mode 100644
index 0000000..fe2b5e1
--- /dev/null
+++ b/internal/directory_search.php
@@ -0,0 +1,36 @@
+ false,
+ "result" => [],
+];
+
+try {
+ $inputJSON = file_get_contents('php://input');
+ $input = json_decode($inputJSON, TRUE);
+ if (empty($input)) {
+ throw new Exception('no valid json as input present');
+ }
+ if (!isset($input["by"])) {
+ throw new Exception('"by" is not defined');
+ }
+ if (!isset($input["search_term"])) {
+ throw new Exception('"search_term" is not defined');
+ }
+ switch ($input["by"]) {
+ case "name":
+ $response["result"] = $mx_db->searchUserByName($input["search_term"]);
+ break;
+ case "threepid":
+ $response["result"] = $mx_db->searchUserByEmail($input["search_term"]);
+ break;
+ default:
+ throw new Exception("unknown type for \"by\" param");
+ }
+
+} catch (Exception $e) {
+ error_log("failed with error: " . $e->getMessage());
+ $response["error"] = $e->getMessage();
+}
+print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
+?>
diff --git a/internal/identity_bulk.php b/internal/identity_bulk.php
new file mode 100644
index 0000000..3768a70
--- /dev/null
+++ b/internal/identity_bulk.php
@@ -0,0 +1,53 @@
+ []
+];
+try {
+ $inputJSON = file_get_contents('php://input');
+ $input = json_decode($inputJSON, TRUE);
+ if (!isset($input)) {
+ throw new Exception('request body is no valid json');
+ }
+
+ if (!isset($input["lookup"])) {
+ throw new Exception('"lookup" is not defined');
+ }
+ if (!is_array($input["lookup"])) {
+ throw new Exception('"lookup" is not an array');
+ }
+ foreach ($input["lookup"] as $lookup) {
+ if (!isset($lookup["medium"])) {
+ throw new Exception('"lookup.medium" is not defined');
+ }
+ if (!isset($lookup["address"])) {
+ throw new Exception('"lookup.address" is not defined');
+ }
+ $res2 = array();
+ switch ($lookup["medium"]) {
+ case "email":
+ $res2 = $mx_db->searchUserByEmail($lookup["address"]);
+ if (!empty($res2)) {
+ array_push($response["lookup"], [
+ "medium" => $lookup["medium"],
+ "address" => $lookup["address"],
+ "id" => [
+ "type" => "localpart",
+ "value" => $res2[0]["user_id"],
+ ]
+ ]
+ );
+ }
+ break;
+ case "msisdn":
+ break;
+ default:
+ throw new Exception("unknown type for \"by\" param");
+ }
+ }
+} catch (Exception $e) {
+ error_log("ídentity_bulk failed with error: " . $e->getMessage());
+ $response["error"] = $e->getMessage();
+}
+print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
+?>
diff --git a/internal/identity_single.php b/internal/identity_single.php
new file mode 100644
index 0000000..404f019
--- /dev/null
+++ b/internal/identity_single.php
@@ -0,0 +1,46 @@
+searchUserByEmail($input["lookup"]["address"]);
+ if (!empty($res2)) {
+ $response = [
+ "lookup" => [
+ "medium" => $input["lookup"]["medium"],
+ "address" => $input["lookup"]["address"],
+ "id" => [
+ "type" => "localpart",
+ "value" => $res2[0]["user_id"],
+ ]
+ ]
+ ];
+ }
+
+
+ break;
+ default:
+ throw new Exception("unknown type for \"by\" param");
+ }
+} catch (Exception $e) {
+ error_log("ídentity_bulk failed with error: " . $e->getMessage());
+ $response["error"] = $e->getMessage();
+}
+print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
+?>
diff --git a/internal/login.php b/internal/login.php
new file mode 100644
index 0000000..e63bd08
--- /dev/null
+++ b/internal/login.php
@@ -0,0 +1,98 @@
+ [
+ "success" => false,
+ ]
+];
+
+require_once("../database.php");
+abstract class LoginRequester {
+ const UNDEFINED = 0;
+ const MXISD = 1;
+ const RestAuth = 2;
+}
+$loginRequester = LoginRequester::UNDEFINED;
+
+try {
+ $inputJSON = file_get_contents('php://input');
+ $input = json_decode($inputJSON, TRUE);
+ $mxid = NULL;
+ $localpart = NULL;
+ if (isset($input["user"])) {
+ if (isset($input["user"]["localpart"])) {
+ $localpart = $input["user"]["localpart"];
+ $loginRequester = LoginRequester::MXISD;
+ } elseif (isset($input["user"]["id"])) {
+ // compatibility for matrix-synapse-rest-auth
+ $mxid = $input["user"]["id"];
+ $loginRequester = LoginRequester::RestAuth;
+ } elseif (isset($input["user"]["mxid"])) {
+ // compatibility for mxisd
+ $mxid = $input["user"]["mxid"];
+ $loginRequester = LoginRequester::MXISD;
+ }
+ }
+
+ // prefer the localpart attribute of mxisd. But in case of matrix-synapse-rest-auth
+ // we have to parse it on our own
+ if (empty($localpart) && !empty($mxid)) {
+ // A mxid would start with an @ so we start at the 2. position
+ $sepPos = strpos($mxid,':', 1);
+ if ($sepPos === false) {
+ // : not found. Assume mxid is localpart
+ // TODO: further checks
+ $localpart = $mxid;
+ } else {
+ $localpart = substr($mxid, 1, strpos($mxid,':') - 1 );
+ }
+ }
+
+ if (empty($localpart)) {
+ throw new Exception ("localpart cannot be identified");
+ }
+
+ $password = NULL;
+ if (isset($input["user"]) && isset($input["user"]["password"])) {
+ $password = $input["user"]["password"];
+ }
+ if (empty($password)) {
+ throw new Exception ("password is not present");
+ }
+
+ $user = $mx_db->getUserForLogin($localpart, $password);
+ if (!$user) {
+ throw new Exception("user not found or password did not match");
+ }
+ $response["auth"]["success"] = true;
+ $response["auth"]["profile"] = [
+ "display_name" => $user["first_name"] . " " . $user["last_name"],
+ "three_pids" => [
+ [
+ "medium" => "email",
+ "address" => $user["email"],
+ ],
+ ],
+ ];
+
+ switch ($loginRequester) {
+ case LoginRequester::RestAuth:
+ $response["auth"]["mxid"] = $mxid;
+ break;
+ case LoginRequester::MXISD;
+ $response["auth"]["id"] = [
+ "type" => "localpart",
+ "value" => $localpart,
+ ];
+ break;
+ default:
+ // only return that it was successful.
+ // we do not know how the data shall be transmitted so we do nothing with it
+ $response["auth"]["success"] = false;
+ break;
+ }
+} catch (Exception $e) {
+ error_log("Auth failed with error: " . $e->getMessage());
+ $response["auth"]["error"] = $e->getMessage();
+}
+print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
+?>
diff --git a/lang/lang.de-de.php b/lang/lang.de-de.php
new file mode 100644
index 0000000..84dafb0
--- /dev/null
+++ b/lang/lang.de-de.php
@@ -0,0 +1,27 @@
+ "Es konnte keine Konfiguration gefunden werden.",
+ "UNKNOWN_SESSION" => "Sitzungstoken nicht vorhanden oder ungültig.",
+ "UNKNOWN_USERNAME" => "Nutzername fehlt",
+ "UNKNOWN_TOKEN" => "Token ist unbekannt",
+ "USERNAME_LENGTH_INVALID" => "Entweder mehr als 20 oder weniger als 3 Zeichen für den Nutzernamen verwendet",
+ "USERNAME_NOT_ALNUM" => "Nutzername ist nicht alphanumerisch",
+ "USERNAME_PENDING_REGISTRATION" => "Dieser Nutzername wurde bereits zur Registrierung vorgemerkt. Versuche es später noch einmal oder wähle einen anderen Nutzernamen",
+ "USERNAME_REGISTERED" => "Dieser Nutzername wurde bereits registriert. Bitte wähle einen anderen Nutzernamen",
+ "PASSWORD_NOT_MATCH" => "Passwörter stimmen nicht überein",
+ "NOTE_LENGTH_EXEEDED" => "Notiz ist länger als die erlaubten 50 Zeichen",
+ "EMAIL_INVALID_FORMAT" => "Keine valide E-Mail-Adresse angegeben",
+ "FIRSTNAME_INVALID_FORMAT" => "Vorname hat ungültiges Format",
+ "SIRNAME_INVALID_FORMAT" => "Nachname hat ungültiges Format",
+ "SEND_MAIL_FAIL" => "Senden der E-Mail fehlgeschlagen",
+ "SEND_MATRIX_FAIL" => "Senden einer Nachricht an die Administratoren fehlgeschlagen",
+ "REGISTRATION_REQUEST_FAILED" => "Registrierungsanfrage ist fehlgeschlagen",
+ "REGISTRATION_FAILED" => "Registrierung ist fehlgeschlagen",
+ "VERIFICATION_SUCEEDED" => "Verifizierung erfolgreich",
+ "VERIFICATION_FAILED" => "Verifizierung fehlgeschlagen",
+ "VERIFICATION_SUCCESS_BODY" => "Vielen Dank. Die Administratoren wurden informiert",
+ "ADMIN_VERIFY_SITE_TITLE" => "Registrierungsanfrage bearbeiten",
+ "ADMIN_REGISTER_ACCEPTED_BODY" => "Die Registrierungsanfrage wurde akzeptiert. Der Nutzer wurde per Mail informiert.",
+ "ADMIN_REGISTER_DECLINED_BODY" => "Die Registrierungsanfrage wurde angelehnt. Der Nutzer wurde per Mail informiert.",
+);
+?>
diff --git a/language.php b/language.php
new file mode 100644
index 0000000..5a1c960
--- /dev/null
+++ b/language.php
@@ -0,0 +1,13 @@
+
diff --git a/mail_templates.php b/mail_templates.php
new file mode 100644
index 0000000..c6a3b22
--- /dev/null
+++ b/mail_templates.php
@@ -0,0 +1,108 @@
+
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..4c1f56c
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,218 @@
+
+
Bitte überprüfe deine E-Mails um deine E-Mail-Adresse zu bestätigen.
"); + print("Zur Registrierungsseite"); + } catch (Exception $e) { + print("" . $e->getMessage() . "
"); + print("Zur Registrierungsseite"); + } +} else { + $_SESSION["token"] = bin2hex(random_bytes(16)); +?> +Hinweis:
+ ist ein geschlossenes Chat-Netzwerk in dem jeder Nutzer bestätigt werden muss.
+ Du bekommst eine E-Mail wenn jemand deine Mitgliedschaft bestätigt hat. An diese wird auch dein initiales Passwort gesendet.
+ Hinterlasse also bitte einen Hinweis zu dir (der nur den entsprechenden Personen gezeigt wird).
+ Liebe Grüße vom Team von
+
" . $language["VERIFICATION_SUCCESS_BODY"] . "
"); + print("Zur Registrierungsseite"); +} catch (Exception $e) { + print("" . $e->getMessage() . "
"); + print("Zur Registrierungsseite"); +} +?> + + diff --git a/public/verify_admin.php b/public/verify_admin.php new file mode 100644 index 0000000..15aa335 --- /dev/null +++ b/public/verify_admin.php @@ -0,0 +1,168 @@ + + +getUserForApproval($token); + if ($user == NULL) { + throw new Exception($language["UNKNOWN_TOKEN"]); + } + + $first_name = $user["first_name"]; + $last_name = $user["last_name"]; + $username = $user["username"]; + $note = $user["note"]; + $email = $user["email"]; + + if ($action == RegisterState::RegistrationAccepted) { + $mx_db->setRegistrationStateAdmin(RegisterState::PendingRegistration, $token); + + // register user + require_once("../MatrixConnection.php"); + $mxConn = new MatrixConnection($config["homeserver"], $config["access_token"]); + + // generate a password with 8 characters + $password = $mx_db->addUser($first_name, $last_name, $username, $email); + if ($password != NULL) { + // send registration_success + $res = send_mail_registration_success($config["homeserver"], $first_name . " " . $last_name, $email, $username, $password, $config["howToURL"]); + if ($res) { + $mx_db->setRegistrationStateAdmin(RegisterState::AllDone, $token); + } else { + $mx_db->setRegistrationStateAdmin(RegisterState::PendingSendRegistrationMail, $token); + } + } else { + send_mail_registration_allowed_but_failed($config["homeserver"], $first_name . " " . $last_name, $email); + $mxMsg = new MatrixMessage(); + $mxMsg->set_type("m.text"); + $mxMsg->set_body("Fehler beim Registrieren von " . $first_name . " " . $last_name . "."); + $mxConn->send($config["register_room"], $mxMsg); + throw new Exception($language["REGISTRATION_FAILED"]); + } + + print("" . $language["ADMIN_REGISTER_ACCEPTED_BODY"] . "
"); + } elseif ($action == RegisterState::RegistrationDeclined) { + $mx_db->setRegistrationStateAdmin(RegisterState::RegistrationDeclined, $token); + send_mail_registration_decline($config["homeserver"], $first_name . " " . $last_name, $email, $decline_reason); + print("" . $language["ADMIN_REGISTER_DECLINED_BODY"] . "
"); + } else { + + print("