From d4508e157fc909e34422e2efe6340f1932c1a75a Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sun, 24 Sep 2017 20:47:13 -0700 Subject: [PATCH] ircd::m: Deduplicate user related in m::user. Update various resource related. --- charybdis/console.cc | 70 ++++++++++-- include/ircd/m/filter.h | 2 + include/ircd/m/user.h | 24 ++-- include/ircd/resource.h | 1 + include/ircd/stdinc.h | 3 +- ircd/db.cc | 2 - ircd/ircd.cc | 1 - ircd/matrix.cc | 217 ++++++++++++++++++++++++++++++++----- ircd/resource.cc | 18 ++- modules/client/account.cc | 87 ++++++++++----- modules/client/login.cc | 69 ++---------- modules/client/register.cc | 60 +++------- modules/client/user.cc | 17 +-- 13 files changed, 365 insertions(+), 206 deletions(-) diff --git a/charybdis/console.cc b/charybdis/console.cc index 271ef6ac8..37991bfec 100644 --- a/charybdis/console.cc +++ b/charybdis/console.cc @@ -564,7 +564,7 @@ try moi->quote(q); break; } - +*/ case hash("password"): { if(!moi) @@ -575,20 +575,66 @@ try const auto args(tokens_after(line, " ", 0)); const params token{args, " ", {"new_password"}}; - m::request::password password - {{ - { "new_password", token.at(0) }, - { "auth", - { - { "session", "abcdef" }, - { "type", "m.login.token" } - }} - }}; + static char query[512]; const auto query_len + { + fmt::snprintf(query, sizeof(query), "%s=%s", + "access_token", + moi->access_token) + }; - moi->password(password); + m::request request + { + "POST", "_matrix/client/r0/account/password", query, json::members + { + { "new_password", token.at(0) }, + { "auth", json::members + { + { "type", "m.login.password" } + }}, + } + }; + + static char buf[4096]; + ircd::parse::buffer pb{buf}; + const json::object response{(*moi)(pb, request)}; + std::cout << string_view{response} << std::endl; break; } -*/ + + case hash("deactivate"): + { + if(!moi) + { + std::cerr << "No current session" << std::endl; + break; + } + + const auto args(tokens_after(line, " ", 0)); + static char query[512]; const auto query_len + { + fmt::snprintf(query, sizeof(query), "%s=%s", + "access_token", + moi->access_token) + }; + + m::request request + { + "POST", "_matrix/client/r0/account/deactivate", query, json::members + { + { "auth", json::members + { + { "type", "m.login.password" } + }}, + } + }; + + static char buf[4096]; + ircd::parse::buffer pb{buf}; + const json::object response{(*moi)(pb, request)}; + std::cout << string_view{response} << std::endl; + break; + } + case hash("setfilter"): { if(!moi) diff --git a/include/ircd/m/filter.h b/include/ircd/m/filter.h index b80c342d6..7db514f7f 100644 --- a/include/ircd/m/filter.h +++ b/include/ircd/m/filter.h @@ -109,6 +109,8 @@ struct ircd::m::filter json::property > { + static room filters; + using super_type::tuple; using super_type::operator=; }; diff --git a/include/ircd/m/user.h b/include/ircd/m/user.h index 086ea0ed4..94880f11a 100644 --- a/include/ircd/m/user.h +++ b/include/ircd/m/user.h @@ -28,10 +28,10 @@ namespace ircd::m { struct user; -} -namespace ircd::m::users -{ + extern user me; + extern room my_room; + extern room filters; } struct ircd::m::user @@ -40,10 +40,16 @@ struct ircd::m::user id user_id; - user(const id &user_id); -}; + static room accounts; -inline -ircd::m::user::user(const id &user_id) -:user_id{user_id} -{} + bool is_active() const; + bool is_password(const string_view &password) const; + + void password(const string_view &password); + void activate(const json::members &contents = {}); + void deactivate(const json::members &contents = {}); + + user(const id &user_id) + :user_id{user_id} + {} +}; diff --git a/include/ircd/resource.h b/include/ircd/resource.h index 3c72fece9..c1082d6cd 100644 --- a/include/ircd/resource.h +++ b/include/ircd/resource.h @@ -77,6 +77,7 @@ struct ircd::resource::request const http::request::head &head; http::request::content &content; http::query::string query; + m::user::id::buf user_id; request(const http::request::head &head, http::request::content &content, http::query::string query); }; diff --git a/include/ircd/stdinc.h b/include/ircd/stdinc.h index 1d9dc3c1c..abfc0a791 100644 --- a/include/ircd/stdinc.h +++ b/include/ircd/stdinc.h @@ -227,13 +227,14 @@ namespace ircd #include "fmt.h" #include "fs.h" #include "ctx.h" -#include "resource.h" #include "logger.h" #include "db.h" #include "js.h" #include "client.h" #include "mods.h" #include "listen.h" +#include "m.h" +#include "resource.h" template std::string diff --git a/ircd/db.cc b/ircd/db.cc index 6005dcd39..a803ed521 100644 --- a/ircd/db.cc +++ b/ircd/db.cc @@ -33,8 +33,6 @@ #include #include -#include - namespace ircd::db { const auto BLOCKING = rocksdb::ReadTier::kReadAllTier; diff --git a/ircd/ircd.cc b/ircd/ircd.cc index c1336a4b9..cc05662a1 100644 --- a/ircd/ircd.cc +++ b/ircd/ircd.cc @@ -24,7 +24,6 @@ */ #include -#include namespace ircd { diff --git a/ircd/matrix.cc b/ircd/matrix.cc index 200bfc9c6..3e2195eca 100644 --- a/ircd/matrix.cc +++ b/ircd/matrix.cc @@ -19,8 +19,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include - /////////////////////////////////////////////////////////////////////////////// // // m/session.h @@ -87,6 +85,24 @@ namespace ircd::m void bootstrap(); } +ircd::m::user +ircd::m::me +{ + "@ircd:cdc.z" +}; + +ircd::m::room +ircd::m::my_room +{ + ircd::m::room::id{"!ircd:cdc.z"} +}; + +ircd::m::room +ircd::m::filter::filters +{ + ircd::m::room::id{"!filters:cdc.z"} +}; + ircd::m::init::init() try { @@ -139,12 +155,8 @@ ircd::m::join_ircd_room() try { // ircd.start event - const m::id::room::buf room_id{"ircd", "cdc.z"}; - const m::id::user::buf user_id{"ircd", "cdc.z"}; - m::room ircd_room{room_id}; - json::iov content; - ircd_room.join(user_id, content); + my_room.join(me.user_id, content); } catch(const m::ALREADY_MEMBER &e) { @@ -155,12 +167,8 @@ void ircd::m::leave_ircd_room() { // ircd.start event - const m::id::room::buf room_id{"ircd", "cdc.z"}; - const m::id::user::buf user_id{"ircd", "cdc.z"}; - m::room ircd_room{room_id}; - json::iov content; - ircd_room.leave(user_id, content); + my_room.leave(me.user_id, content); } void @@ -175,46 +183,38 @@ ircd::m::bootstrap() "database is empty. I will be bootstrapping it with initial events now..." ); - const m::id::user::buf user_id{"ircd", "cdc.z"}; - const m::id::room::buf ircd_room_id{"ircd", "cdc.z"}; - m::room ircd_room{ircd_room_id}; - ircd_room.send( + my_room.send( { { "type", "m.room.create" }, - { "sender", user_id }, + { "sender", me.user_id }, { "state_key", "" }, { "content", json::members { - { "creator", user_id } + { "creator", me.user_id } }} }); - const m::id::room::buf accounts_room_id{"accounts", "cdc.z"}; - m::room accounts_room{accounts_room_id}; - accounts_room.send( + user::accounts.send( { { "type", "m.room.create" }, - { "sender", user_id }, + { "sender", me.user_id }, { "state_key", "" }, { "content", json::members { - { "creator", user_id } + { "creator", me.user_id } }} }); json::iov content; - accounts_room.join(user_id, content); - - const m::id::room::buf filters_room_id{"filters", "cdc.z"}; - m::room filters_room{filters_room_id}; - filters_room.send( + user::accounts.join(me.user_id, content); + filter::filters.send( { { "type", "m.room.create" }, - { "sender", user_id }, + { "sender", me.user_id }, { "state_key", "" }, { "content", json::members { - { "creator", user_id } + { "creator", me.user_id } }} }); } @@ -638,6 +638,163 @@ const return false; } +/////////////////////////////////////////////////////////////////////////////// +// +// m/user.h +// + +ircd::m::room +ircd::m::user::accounts +{ + ircd::m::room::id{"!accounts:cdc.z"} +}; + +/// Register the user by joining them to the accounts room. +/// +/// The content of the join event may store keys including the registration +/// options. Once this call completes the join was successful and the user is +/// registered, otherwise throws. +void +ircd::m::user::activate(const json::members &contents) +try +{ + json::iov content; + json::iov::push members[contents.size()]; + + size_t i(0); + for(const auto &member : contents) + new (members + i++) json::iov::push(content, member); + + accounts.join(user_id, content); +} +catch(const m::ALREADY_MEMBER &e) +{ + throw m::error + { + http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." + }; +} + +void +ircd::m::user::deactivate(const json::members &contents) +{ + json::iov content; + json::iov::push members[contents.size()]; + + size_t i(0); + for(const auto &member : contents) + new (members + i++) json::iov::push(content, member); + + accounts.leave(user_id, content); +} + +void +ircd::m::user::password(const string_view &password) +try +{ + json::iov event; + json::iov::push members[] + { + { event, json::member { "type", "ircd.password" }}, + { event, json::member { "state_key", user_id }}, + { event, json::member { "sender", user_id }}, + { event, json::member { "content", json::members + { + { "plaintext", password } + }}}, + }; + + accounts.send(event); +} +catch(const m::ALREADY_MEMBER &e) +{ + throw m::error + { + http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." + }; +} + +bool +ircd::m::user::is_password(const string_view &supplied_password) +const +{ + const m::event::where::equal member_event + { + { "type", "ircd.password" }, + { "state_key", user_id } + }; + + bool ret{false}; + const m::event::where::test correct_password{[&ret, &supplied_password] + (const auto &event) + { + const json::object &content + { + json::at(event) + }; + + const auto &correct_password + { + unquote(content.at("plaintext")) + }; + + ret = supplied_password == correct_password; + return true; + }}; + + const auto query + { + member_event && correct_password + }; + + // The query to the database is made here. Know that this ircd::ctx + // may suspend and global state may have changed after this call. + const room::events events + { + accounts + }; + + events.query(member_event && correct_password); + return ret; +} + +bool +ircd::m::user::is_active() +const +{ + const m::event::where::equal member_event + { + { "type", "m.room.member" }, + { "state_key", user_id } + }; + + bool ret{false}; + const m::event::where::test is_joined{[&ret] + (const auto &event) + { + const json::object &content + { + json::val(event) + }; + + const auto &membership + { + unquote(content["membership"]) + }; + + ret = membership == "join"; + return true; + }}; + + const room::events events + { + accounts + }; + + events.query(member_event && is_joined); + return ret; +} + /////////////////////////////////////////////////////////////////////////////// // // m/event.h diff --git a/ircd/resource.cc b/ircd/resource.cc index e3c01b1d6..8c488a38e 100644 --- a/ircd/resource.cc +++ b/ircd/resource.cc @@ -19,8 +19,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include - namespace ircd { IRCD_INIT_PRIORITY(STD_CONTAINER) @@ -108,7 +106,7 @@ noexcept namespace ircd { -const m::room accounts +const m::room::events accounts { m::id::room{"!accounts:cdc.z"} }; @@ -119,7 +117,7 @@ authenticate(client &client, resource::request &request) try { - const auto &access_token + const string_view &access_token { request.query.at("access_token") }; @@ -131,7 +129,17 @@ try { "state_key", access_token } }; - if(!m::room::events{accounts}.any(query)) + const bool result + { + accounts.query(query, [&request, &access_token](const m::event &event) + { + assert(at(event) == access_token); + request.user_id = at(event); + return true; + }) + }; + + if(!result) throw m::error { // When credentials are required but missing or invalid, the HTTP call will return with diff --git a/modules/client/account.cc b/modules/client/account.cc index 874f0f2c4..9f3ec20a1 100644 --- a/modules/client/account.cc +++ b/modules/client/account.cc @@ -48,47 +48,36 @@ resource::response account_password(client &client, const resource::request &request) try { - const auto new_password + const string_view &new_password { - request.at("new_password") + unquote(request.at("new_password")) }; - const auto type + const string_view &type { - unquote(request.at("auth.type")) + unquote(request.at({"auth", "type"})) }; - const auto session - { - request["auth.session"] - }; - - if(!type.empty() && type != "m.login.token") - { + if(type != "m.login.password") throw m::error { "M_UNSUPPORTED", "Login type is not supported." }; - } - //db::handle hand(account, "foo"); - //database::snapshot snap(hand); - -/* - account(db::MERGE, "jzk", std::string + const string_view &session { - json::index - {{ - "password", - { - { "plaintext", new_password } - } - }} - }); -*/ + request[{"auth", "session"}] + }; + + m::user user + { + request.user_id + }; + + user.password(new_password); return resource::response { - client + client, http::OK }; } catch(const db::not_found &e) @@ -99,9 +88,49 @@ catch(const db::not_found &e) }; } -resource::method post +resource::method post_password { - account_resource.password, "POST", account_password + account_resource.password, "POST", account_password, + { + post_password.REQUIRES_AUTH + } +}; + +resource::response +account_deactivate(client &client, const resource::request &request) +{ + const string_view &type + { + request.at({"auth", "type"}) + }; + + const string_view &session + { + request[{"auth", "session"}] + }; + + m::user user + { + request.user_id + }; + + user.deactivate(); + + return resource::response + { + client, json::members + { + { "goodbye", "Thanks for visiting. Come back soon!" } + } + }; +} + +resource::method post_deactivate +{ + account_resource.deactivate, "POST", account_deactivate, + { + post_deactivate.REQUIRES_AUTH + } }; mapi::header IRCD_MODULE diff --git a/modules/client/login.cc b/modules/client/login.cc index dac6865df..383d4e1b2 100644 --- a/modules/client/login.cc +++ b/modules/client/login.cc @@ -56,24 +56,6 @@ struct body using super_type::tuple; }; -// Generate !accounts:host which is the MXID of the room where the members -// are the actual account registrations for this homeserver. -const m::id::room::buf accounts_room_id -{ - "accounts", home_server -}; - -// Handle to the accounts room -m::room accounts_room -{ - accounts_room_id -}; - -const m::room::events accounts_room_events -{ - accounts_room -}; - resource::response post_login_password(client &client, const resource::request::object &request) @@ -92,56 +74,21 @@ post_login_password(client &client, const auto &supplied_password { - at(request) + unquote(at(request)) }; - // Sets up the query to find the user_id in the accounts room - const m::event::where::equal member_event + m::user user { - { "type", "m.room.member" }, - { "state_key", user_id } + user_id }; - // Once the query finds the result this closure views the event in the - // database and returns true if the login is authentic. - const m::event::where::test correct_password{[&supplied_password] - (const auto &event) - { - const json::object &content + if(!user.is_password(supplied_password)) + throw m::error { - json::val(event) + http::FORBIDDEN, "M_FORBIDDEN", "Access denied." }; - const auto &membership - { - unquote(content.at("membership")) - }; - - if(membership != "join") - return false; - - const auto &correct_password - { - content.get("password") - }; - - if(!correct_password) - return false; - - if(supplied_password != correct_password) - return false; - - return true; - }}; - - const auto query - { - member_event && correct_password - }; - - // The query to the database is made here. Know that this ircd::ctx - // may suspend and global state may have changed after this call. - if(!accounts_room_events.query(query)) + if(!user.is_active()) throw m::error { http::FORBIDDEN, "M_FORBIDDEN", "Access denied." @@ -159,7 +106,7 @@ post_login_password(client &client, // Log the user in by issuing an event in the accounts room containing // the generated token. When this call completes without throwing the // access_token will be committed and the user will be logged in. - accounts_room.send( + m::user::accounts.send( { { "type", "ircd.access_token" }, { "sender", user_id }, diff --git a/modules/client/register.cc b/modules/client/register.cc index 4ed6a1721..437602837 100644 --- a/modules/client/register.cc +++ b/modules/client/register.cc @@ -47,24 +47,8 @@ struct body using super_type::tuple; }; -// Generate !accounts:host which is the MXID of the room where the members -// are the account registrations on this homeserver. -const m::id::room::buf accounts_room_id -{ - "accounts", home_server -}; - -// This object is a lightweight handle to the accounts room, which is a -// chatroom whose members are the representation of the accounts registered -// to this server itself. -m::room accounts_room -{ - accounts_room_id -}; - static void validate_user_id(const m::id::user &user_id); static void validate_password(const string_view &password); -static void join_accounts_room(const m::id::user &user_id, const json::members &contents); resource::response handle_post_kind_user(client &client, @@ -123,17 +107,25 @@ handle_post_kind_user(client &client, // Check if the password is acceptable for this server or throws validate_password(password); - // Register the user by joining them to the accounts room. The content of - // the join event will store keys from the registration options including - // the password - do not expose this to clients //TODO: store hashed pass - // Once this call completes the join was successful and the user is - // registered, otherwise throws. - join_accounts_room(user_id, + // Represent the user + m::user user + { + user_id + }; + + // Activate the account. Underneath this will join the user to the room + // !accounts:your.domain. If the user_id is already a member then this + // throws 409 Conflict; otherwise the user is registered after this call. + user.activate( { - { "password", password }, { "bind_email", bind_email }, }); + // Set the password for the account. This issues an ircd.password state + // event to the accounts room for the user. If this call completes the + // user will be able to login with m.login.password + user.password(password); + // Send response to user return resource::response { @@ -203,28 +195,6 @@ mapi::header IRCD_MODULE "registers the resource 'client/register' to handle requests" }; -void -join_accounts_room(const m::id::user &user_id, - const json::members &contents) -try -{ - json::iov content; - json::iov::push members[contents.size()]; - - size_t i(0); - for(const auto &member : contents) - new (members + i++) json::iov::push(content, member); - - accounts_room.join(user_id, content); -} -catch(const m::ALREADY_MEMBER &e) -{ - throw m::error - { - http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." - }; -} - void validate_user_id(const m::id::user &user_id) { diff --git a/modules/client/user.cc b/modules/client/user.cc index b3cece38e..fb44d2f9a 100644 --- a/modules/client/user.cc +++ b/modules/client/user.cc @@ -30,16 +30,6 @@ resource user_resource } }; -m::room filters_room -{ - m::room::id{"!filters:cdc.z"} -}; - -const m::room::events filters_room_events -{ - filters_room -}; - resource::response get_filter(client &client, const resource::request &request) try @@ -77,6 +67,11 @@ try return true; }}; + const m::room::events filters_room_events + { + m::filter::filters + }; + if(!filters_room_events.any(query, result)) throw m::NOT_FOUND("No matching filter with that ID"); @@ -166,7 +161,7 @@ try { event, json::member { "content", request.body }} }; - filters_room.send(event); + m::filter::filters.send(event); return resource::response { client, http::CREATED,