diff --git a/.gitignore b/.gitignore
index 4f4773f..ec6a661 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
config.php
+db_file.sqlite
\ No newline at end of file
diff --git a/config.sample.php b/config.sample.php
index 419a682..69252d1 100644
--- a/config.sample.php
+++ b/config.sample.php
@@ -2,7 +2,6 @@
$homeserver = "example.com";
$access_token = "To be used for sending the registration notification";
$register_room = '$registerRoomID:example.com';
-$registration_shared_secret = "To be used for actually register the user";
$webroot="https://myregisterdomain.net/";
$howToURL = "https://my-url-for-storing-howTos.net";
diff --git a/cron.php b/cron.php
index 861137e..384ce0f 100644
--- a/cron.php
+++ b/cron.php
@@ -10,7 +10,7 @@ $sql = "SELECT id, first_name, last_name, username, email, state, note, verify_t
. " OR state = " . RegisterState::PendingSendRegistrationMail
. " OR state = " . RegisterState::RegistrationDeclined
. " OR state = " . RegisterState::AllDone . ";";
-foreach ($db->query($sql) as $row) {
+foreach ($mx_db->query($sql) as $row) {
$first_name = $row["first_name"];
$last_name = $row["last_name"];
$username = $row["username"];
@@ -28,8 +28,7 @@ foreach ($db->query($sql) as $row) {
$row["verify_url"]);
if ($success) {
- $db->exec("UPDATE registrations SET state = " . RegisterState::PendingEmailVerify
- . " WHERE id = " . $row["id"] . ";");
+ $mx_db->setRegistrationStateById(RegisterState::PendingEmailVerify, $row["id"]);
} else {
throw new Exception("Could not send mail to ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].")");
}
@@ -39,7 +38,7 @@ foreach ($db->query($sql) as $row) {
$adminUrl = $webroot . "/verify_admin.php?t=" . $row["admin_token"];
$mxConn = new MatrixConnection($homeserver, $access_token);
$mxMsg = new MatrixMessage();
- $mxMsg->set_body($first_name . ' ' . $last_name . "möchte sich registrieren und hat folgende Notiz hinterlassen:\r\n"
+ $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:
"
@@ -49,8 +48,7 @@ foreach ($db->query($sql) as $row) {
$response = $mxConn->send($register_room, $mxMsg);
if ($response) {
- $db->exec("UPDATE registrations SET state = " . RegisterState::PendingAdminVerify
- . " WHERE id = " . $row["id"] . ";");
+ $mx_db->setRegistrationStateById(RegisterState::PendingAdminVerify, $row["id"]);
send_mail_pending_approval($homeserver, $first_name . " " . $last_name, $email);
} else {
@@ -60,17 +58,15 @@ foreach ($db->query($sql) as $row) {
case RegisterState::PendingRegistration:
// Registration got accepted but registration failed
- // register user
- require_once("MatrixConnection.php");
- $mxConn = new MatrixConnection($homeserver, $access_token);
-
- // generate a password with 8 characters
- $password = bin2hex(openssl_random_pseudo_bytes(4));
-
- $res = $mxConn->register($username, $password, $shared_secret);
- if ($res) {
+ $password = addUser($row["first_name"], $row["last_name"], $row["username"], $row["email"]);
+ if ($password != NULL) {
// send registration_success
- send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $howToURL);
+ $res = send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $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($homeserver, $first_name . " " . $last_name, $email);
$mxMsg = new MatrixMessage();
diff --git a/database.php b/database.php
index abfeae3..2078aa8 100644
--- a/database.php
+++ b/database.php
@@ -1,5 +1,5 @@
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- $db->exec("CREATE TABLE registrations(
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- state INT DEFAULT 0,
- first_name TEXT,
- last_name TEXT,
- username TEXT,
- note TEXT,
- email TEXT,
- verify_token TEXT,
- admin_token TEXT,
- request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
-}
-else {
- // establish connection
- $db = new PDO('sqlite:' . $db_file);
- $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-}
+class mxDatabase
+{
+ private $db = NULL;
-// set writeable when not set already
-if (!is_writable($db_file)) {
- chmod($db_file, 0777);
+ /**
+ * Creates mxDatabase object
+ * @param db_file path to the sqlite file where the credentials should be stored
+ */
+ function __construct($db_file) {
+ // create database file when not existent yet
+ if (!file_exists($db_file)) {
+ $this->db = new PDO('sqlite:' . $db_file);
+ $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->db->exec("CREATE TABLE registrations(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ state INT DEFAULT 0,
+ first_name TEXT,
+ last_name TEXT,
+ username TEXT,
+ note TEXT,
+ email TEXT,
+ verify_token TEXT,
+ admin_token TEXT,
+ request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
+ $this->db->exec("CREATE TABLE logins (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ active INT DEFAULT 1,
+ firstname TEXT,
+ lastname TEXT,
+ localpart TEXT,
+ password_hash TEXT,
+ email TEXT,
+ create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )");
+ }
+ else {
+ // establish connection
+ $this->db = new PDO('sqlite:' . $db_file);
+ $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ }
+
+ // set writeable when not set already
+ if (!is_writable($db_file)) {
+ chmod($db_file, 0777);
+ }
+ }
+
+ /**
+ * 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 = $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()) {
+ require_once "language.php";
+ throw new Exception($language["USERNAME_PENDING_REGISTRATION"]);
+ }
+ if ($this->userRegistered($username)) {
+ require_once "language.php";
+ throw new Exception($language["USERNAME_REGISTERED"]);
+ }
+
+ $verify_token = bin2hex(random_bytes(16));
+ $admin_token = bin2hex(random_bytes(16));
+
+ $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 = $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::PendingAdminVerify . " LIMIT 1;";
+ $res = $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 = '" . $token . "' LIMIT 1;";
+ foreach ($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);
+ $first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL;
+
+ if ($res->fetchColumn() > 0) {
+ $sql = "SELECT first_name, last_name, email, password_hash FROM logins "
+ . " WHERE verify_token = '" . $token . "' LIMIT 1;";
+ foreach ($this->db->query($sql) as $row) {
+ // will only be executed once
+ 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) {
+ // generate a password with 10 characters
+ $password = bin2hex(openssl_random_pseudo_bytes(5));
+ $password_hash = password_hash($passwort, PASSWORD_BCRYPT, ["cost"=>12]);
+
+ $sql = "INSERT INTO logins (firstname, lastname, localpart, password_hash, email) VALUES "
+ . '("' . $first_name.'","' . $last_name . '","' . $username . '","'
+ . $password_hash . '","' . $email . '")';
+
+ if ($this->db->exec($sql)) {
+ return $password;
+ }
+ return NULL;
+ }
}
+
+$mx_db = new mxDatabase($db_file);
?>
diff --git a/public/login.php b/public/login.php
new file mode 100644
index 0000000..ce42668
--- /dev/null
+++ b/public/login.php
@@ -0,0 +1,105 @@
+ [
+ "success" => false,
+ ]
+];
+
+require_once("config.php");
+require_once("database.php");
+abstract class LoginRequester {
+ const UNDEFINED = 0;
+ const MXISD = 1;
+ const RestAuth = 2;
+}
+$loginRequester = LoginRequester::UNDEFINED;
+
+try {
+ $input = json_decode('
+ {
+ "user": {
+ "id": "@matrix.id.of.the.user:example.com",
+ "password": "passwordOfTheUser"
+ }
+ }
+ ',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["first_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");
+?>
\ No newline at end of file
diff --git a/public/register.php b/public/register.php
index d52a2c7..eda0435 100644
--- a/public/register.php
+++ b/public/register.php
@@ -7,7 +7,6 @@ if (!file_exists("../config.php")) {
exit();
}
require_once "../config.php";
-require_once "../mail_templates.php";
// enforce admin via https
if (!isset($_SERVER['HTTPS'])) {
@@ -17,8 +16,6 @@ if (!isset($_SERVER['HTTPS'])) {
session_start();
-require_once("../database.php");
-
if ($_SERVER["REQUEST_METHOD"] == "POST") {
try {
if (!isset($_SESSION["token"]) || !isset($_POST["token"]) || $_SESSION["token"] != $_POST["token"]) {
@@ -53,48 +50,27 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = filter_var($_POST["username"], FILTER_SANITIZE_STRING);
$note = filter_var($_POST["note"], FILTER_SANITIZE_STRING);
$email = filter_var($_POST["email"], FILTER_VALIDATE_EMAIL);
- $verify_token = bin2hex(random_bytes(16));
- $admin_token = bin2hex(random_bytes(16));
- # $first="test"; $last="test2"; $user="test3"; $note="empty"; $email="mail+test1@matthias-kesler.de";
+ require_once("../database.php");
+ $res = $mx_db->addRegistration($first_name, $last_name, $username, $note, $email);
- $sql = "SELECT COUNT(*) FROM registrations WHERE username = '" . $username . "' AND NOT state = "
- . RegisterState::RegistrationDeclined . " LIMIT 1;";
- $res = $db->query($sql);
- if ($res->fetchColumn() > 0) {
- throw new Exception($language["USERNAME_PENDING_REGISTRATION"]);
+ if (!isset($res["verify_token"])) {
+ error_log("sth. went wrong. registration did not throw but admin_token not set");
+ throw Exception ("Unknown Error");
}
- require_once("../MatrixConnection.php");
- $mxConn = new MatrixConnection($homeserver, $access_token);
- if ($mxConn->hasUser($username)) {
- throw new Exception($language["USERNAME_REGISTERED"]);
- }
-
- $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.'")');
- # $ins_stmt->bindValue(':first_name', $first);
- # $ins_stmt->bindValue(':last_lame', $last);
- # $ins_stmt->bindValue(':username', $user);
- # $ins_stmt->bindValue(':note', $note);
- # $ins_stmt->bindValue(':email', $email);
- # $ins_stmt->bindValue(':verify_token', $vToken);
- # $ins_stmt->bindValue(':admin_token', $adminToken);
- # $ins_stmt->bindValue(':now', date('Y-m-d H:i:s'));
- #
- # $ins_stmt->execute();
+ $verify_token = $res["verify_token"];
$verify_url = $webroot . "/verify.php?t=" . $verify_token;
+ require_once "../mail_templates.php";
$success = send_mail_pending_verification(
$homeserver,
$first_name . " " . $last_name,
$email,
$verify_url);
- $db->exec("UPDATE registrations SET state = " .
- ($success ? RegisterState::PendingEmailVerify : RegisterState::PendingEmailSend)
- . " WHERE verify_token = \"" . $verify_token. "\";");
+ $mx_db->setRegistrationStateVerify(
+ ($success ? RegisterState::PendingEmailVerify : RegisterState::PendingEmailSend),
+ $verify_token);
print("
" . $language["ADMIN_REGISTER_ACCEPTED_BODY"] . "
"); } elseif ($action == RegisterState::RegistrationDeclined) { - $db->exec("UPDATE registrations SET state = " . RegisterState::RegistrationDeclined - . " WHERE admin_token = '" . $token. "';"); + $mx_db->setRegistrationStateAdmin(RegisterState::RegistrationDeclined, $token); send_mail_registration_decline($homeserver, $first_name . " " . $last_name, $email, $decline_reason); print("