// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2019 Jason Volk // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. ircd::json::object IRCD_MODULE_EXPORT ircd::m::user::registar::operator()(const mutable_buffer &out, const net::ipport &remote) const { // 3.3.1 Additional authentication information for the user-interactive authentication API. const json::object &auth { json::get<"auth"_>(*this) }; // 3.3.1 The login type that the client is attempting to complete. const string_view &type { !empty(auth)? unquote(auth.at("type")) : string_view{} }; // We only support this for now, for some reason. TODO: XXX if(type && type != "m.login.dummy") throw m::UNSUPPORTED { "Registration '%s' not supported.", type }; // 3.3.1 The local part of the desired Matrix ID. If omitted, the homeserver MUST // generate a Matrix ID local part. const auto &username { json::get<"username"_>(*this) }; // Generate canonical mxid. The home_server is appended if one is not // specified. We do not generate a user_id here if the local part is not // specified. TODO: isn't that guest reg? const m::id::user::buf user_id { username, my_host() }; // Check if the the user_id is acceptably formed for this server or throws validate_user_id(user_id); // 3.3.1 Required. The desired password for the account. const auto &password { json::at<"password"_>(*this) }; // (r0.3.0) 3.4.1 ID of the client device. If this does not correspond to a // known client device, a new device will be created. The server will auto- // generate a device_id if this is not specified. const auto requested_device_id { json::get<"device_id"_>(*this) }; const m::id::device::buf device_id { requested_device_id? m::id::device::buf{requested_device_id, my_host()}: !json::get<"inhibit_login"_>(*this)? m::id::device::buf{m::id::generate, my_host()}: m::id::device::buf{} }; const auto &initial_device_display_name { json::get<"initial_device_display_name"_>(*this) }; // 3.3.1 If true, the server binds the email used for authentication to the // Matrix ID with the ID Server. Defaults to false. const auto &bind_email { json::get<"bind_email"_>(*this, false) }; // Check if the password is acceptable for this server or throws validate_password(password); //TODO: ABA if(exists(user_id)) throw m::error { http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use." }; //TODO: ABA / TXN // Represent the user m::user user { m::create(user_id) }; // Activate the account. Underneath this will create a special room // for this user in the form of !@user:host and set a key in !users:host // If the user_id is taken this throws 409 Conflict because those assets // will already exist; otherwise the user is registered after this call. //TODO: ABA / TXN user.activate(); // Set the password for the account. This issues an ircd.password state // event to the user's room. User will be able to login with // m.login.password user.password(password); // Represent the user's room; was created in m::create(user_id) m::user::room user_room { user }; // Store the options from registration. const m::event::id::buf account_options_id { send(user_room, user.user_id, "ircd.account.options", "registration", json::members { { "bind_email", bind_email }, }) }; // Optionally generate an access_token for login. char access_token_buf[32]; const string_view access_token { !json::get<"inhibit_login"_>(*this)? m::user::gen_access_token(access_token_buf): string_view{} }; // Log the user in by issuing an event in the tokens room containing // the generated token. When this call completes without throwing the // access_token will be committed and the user will be logged in. if(!json::get<"inhibit_login"_>(*this)) { char remote_buf[96]; const json::value last_seen_ip { remote? string(remote_buf, remote): string_view{}, json::STRING }; const m::event::id::buf access_token_id { m::send(m::user::tokens, user_id, "ircd.access_token", access_token, json::members { { "ip", last_seen_ip }, { "device_id", device_id }, }) }; const json::members device { { "device_id", device_id }, { "display_name", initial_device_display_name }, { "last_seen_ts", ircd::time() }, { "last_seen_ip", last_seen_ip }, { "access_token_id", access_token_id }, }; m::device::set(user_id, device); } // Send response to user return json::stringify(mutable_buffer{out}, json::members { { "user_id", user_id }, { "home_server", my_host() }, { "access_token", access_token }, { "device_id", device_id }, }); } void IRCD_MODULE_EXPORT ircd::m::user::registar::validate_password(const string_view &password) { static const size_t &max { 255 }; if(password.size() > max) throw m::error { http::BAD_REQUEST, "M_INVALID_PASSWORD", "The desired password exceeds %zu characters", max, }; } void IRCD_MODULE_EXPORT ircd::m::user::registar::validate_user_id(const m::user::id &user_id) { if(user_id.host() != my_host()) throw m::error { http::BAD_REQUEST, "M_INVALID_USERNAME", "Can only register with host '%s'", my_host() }; }