0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-29 18:22:50 +01:00

ircd:Ⓜ️/modules/client: Add device ID generation; use token/password helpers; various comments/cleanup.

This commit is contained in:
Jason Volk 2018-02-15 13:11:51 -08:00
parent 6ece5db391
commit eab4aef7e6
4 changed files with 128 additions and 63 deletions

View file

@ -14,6 +14,8 @@
namespace ircd::m namespace ircd::m
{ {
struct user; struct user;
bool exists(const id::user &);
} }
struct ircd::m::user struct ircd::m::user

View file

@ -10,6 +10,8 @@
#include <ircd/m/m.h> #include <ircd/m/m.h>
/// ID of the room which indexes all users (an instance of the room is
/// provided below).
const ircd::m::room::id::buf const ircd::m::room::id::buf
users_room_id users_room_id
{ {
@ -27,17 +29,19 @@ ircd::m::user::users
users_room_id users_room_id
}; };
/// The tokens room serves as a key-value lookup for various tokens to /// ID of the room which stores ephemeral tokens (an instance of the room is
/// users, etc. It primarily serves to store access tokens for users. This /// provided below).
/// is a separate room from the users room because in the future it may
/// have an optimized configuration as well as being more easily cleared.
///
const ircd::m::room::id::buf const ircd::m::room::id::buf
tokens_room_id tokens_room_id
{ {
"tokens", ircd::my_host() "tokens", ircd::my_host()
}; };
/// The tokens room serves as a key-value lookup for various tokens to
/// users, etc. It primarily serves to store access tokens for users. This
/// is a separate room from the users room because in the future it may
/// have an optimized configuration as well as being more easily cleared.
///
ircd::m::room ircd::m::room
ircd::m::user::tokens ircd::m::user::tokens
{ {
@ -92,22 +96,16 @@ void
ircd::m::user::password(const string_view &password) ircd::m::user::password(const string_view &password)
try try
{ {
//TODO: ADD SALT char buf[64];
const sha256::buf hash const auto supplied
{ {
sha256{password} gen_password_hash(buf, password)
}; };
char b64[64]; const user::room user_room{*this};
const auto digest send(user_room, user_id, "ircd.password", user_id,
{ {
b64encode_unpadded(b64, hash) { "sha256", supplied }
};
const auto room_id{this->room_id()};
send(room_id, user_id, "ircd.password", user_id,
{
{ "sha256", digest }
}); });
} }
catch(const m::ALREADY_MEMBER &e) catch(const m::ALREADY_MEMBER &e)
@ -119,24 +117,18 @@ catch(const m::ALREADY_MEMBER &e)
} }
bool bool
ircd::m::user::is_password(const string_view &supplied_password) ircd::m::user::is_password(const string_view &password)
const const
{ {
//TODO: ADD SALT char buf[64];
const sha256::buf hash const auto supplied
{ {
sha256{supplied_password} gen_password_hash(buf, password)
};
char b64[64];
const auto supplied_hash
{
b64encode_unpadded(b64, hash)
}; };
bool ret{false}; bool ret{false};
const auto room_id{this->room_id()}; const user::room user_room{*this};
m::room{room_id}.get("ircd.password", user_id, [&supplied_hash, &ret] user_room.get("ircd.password", user_id, [&supplied, &ret]
(const m::event &event) (const m::event &event)
{ {
const json::object &content const json::object &content
@ -144,12 +136,12 @@ const
json::at<"content"_>(event) json::at<"content"_>(event)
}; };
const auto &correct_hash const auto &correct
{ {
unquote(content.at("sha256")) unquote(content.at("sha256"))
}; };
ret = supplied_hash == correct_hash; ret = supplied == correct;
}); });
return ret; return ret;

View file

@ -28,29 +28,56 @@ login_resource
namespace { namespace name namespace { namespace name
{ {
constexpr const auto password{"password"};
constexpr const auto medium{"medium"};
constexpr const auto type{"type"}; constexpr const auto type{"type"};
constexpr const auto user{"user"}; constexpr const auto user{"user"};
constexpr const auto medium{"medium"};
constexpr const auto address{"address"}; constexpr const auto address{"address"};
constexpr const auto password{"password"};
constexpr const auto token{"token"};
constexpr const auto device_id{"device_id"};
constexpr const auto initial_device_display_name{"initial_device_display_name"};
}} }}
struct body struct body
:json::tuple :json::tuple
< <
json::property<name::password, string_view>, /// Required. The login type being used. One of: ["m.login.password",
json::property<name::medium, time_t>, /// "m.login.token"]
json::property<name::type, string_view>, json::property<name::type, string_view>,
/// The fully qualified user ID or just local part of the user ID, to
/// log in.
json::property<name::user, string_view>, json::property<name::user, string_view>,
json::property<name::address, string_view>
/// When logging in using a third party identifier, the medium of the
/// identifier. Must be 'email'.
json::property<name::medium, string_view>,
/// Third party identifier for the user.
json::property<name::address, string_view>,
/// Required when type is m.login.password. The user's password.
json::property<name::password, string_view>,
/// Required when type is m.login.token. The login token.
json::property<name::token, string_view>,
/// 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.
json::property<name::device_id, string_view>,
/// A display name to assign to the newly-created device. Ignored if
/// device_id corresponds to a known device.
json::property<name::initial_device_display_name, string_view>
> >
{ {
using super_type::tuple; using super_type::tuple;
}; };
resource::response resource::response
post_login_password(client &client, post__login_password(client &client,
const resource::request::object<body> &request) const resource::request::object<body> &request)
{ {
// Build a canonical MXID from a the user field // Build a canonical MXID from a the user field
const m::id::user::buf user_id const m::id::user::buf user_id
@ -80,13 +107,22 @@ post_login_password(client &client,
"Access denied." "Access denied."
}; };
// Generate the access token const auto requested_device_id
static constexpr const auto token_len{127}; {
static const auto token_dict{rand::dict::alpha}; unquote(json::get<"device_id"_>(request))
char token_buf[token_len + 1]; };
const m::id::device device_id
{
requested_device_id?
m::id::device::buf{requested_device_id, my_host()}:
m::id::device::buf{m::id::generate, my_host()}
};
char access_token_buf[32];
const string_view access_token const string_view access_token
{ {
rand::string(token_dict, token_len, token_buf, sizeof(token_buf)) m::user::gen_access_token(access_token_buf)
}; };
// Log the user in by issuing an event in the tokens room containing // Log the user in by issuing an event in the tokens room containing
@ -95,49 +131,51 @@ post_login_password(client &client,
m::send(m::user::tokens, user_id, "ircd.access_token", access_token, m::send(m::user::tokens, user_id, "ircd.access_token", access_token,
{ {
{ "ip", string(remote(client)) }, { "ip", string(remote(client)) },
{ "device", "unknown" }, { "device", device_id },
}); });
// Send response to user // Send response to user
return resource::response return resource::response
{ {
client, client, json::members
{ {
{ "user_id", user_id }, { "user_id", user_id },
{ "home_server", my_host() }, { "home_server", my_host() },
{ "access_token", access_token }, { "access_token", access_token },
{ "device_id", device_id },
} }
}; };
} }
resource::response resource::response
post_login(client &client, const resource::request::object<body> &request) post_login(client &client,
const resource::request::object<body> &request)
{ {
// x.x.x Required. The login type being used. const auto &type
// Currently only "m.login.password" is supported.
const auto type
{ {
unquote(at<"type"_>(request)) unquote(at<"type"_>(request))
}; };
if(type == "m.login.password") if(type == "m.login.password")
return post_login_password(client, request); return post__login_password(client, request);
else
throw m::error throw m::UNSUPPORTED
{ {
"M_UNSUPPORTED", "Login type is not supported." "Login type is not supported."
}; };
} }
resource::method method_post resource::method
method_post
{ {
login_resource, "POST", post_login login_resource, "POST", post_login
}; };
resource::response resource::response
get_login(client &client, const resource::request &request) get_login(client &client,
const resource::request &request)
{ {
json::member login_password const json::member login_password
{ {
"type", "m.login.password" "type", "m.login.password"
}; };

View file

@ -22,6 +22,7 @@ namespace { namespace name
constexpr const auto bind_email {"bind_email"}; constexpr const auto bind_email {"bind_email"};
constexpr const auto password {"password"}; constexpr const auto password {"password"};
constexpr const auto auth {"auth"}; constexpr const auto auth {"auth"};
constexpr const auto device_id {"device_id"};
}} }}
struct body struct body
@ -30,7 +31,8 @@ struct body
json::property<name::username, string_view>, json::property<name::username, string_view>,
json::property<name::bind_email, bool>, json::property<name::bind_email, bool>,
json::property<name::password, string_view>, json::property<name::password, string_view>,
json::property<name::auth, json::object> json::property<name::auth, json::object>,
json::property<name::device_id, string_view>
> >
{ {
using super_type::tuple; using super_type::tuple;
@ -45,13 +47,13 @@ handle_post_kind_user(client &client,
try try
{ {
// 3.3.1 Additional authentication information for the user-interactive authentication API. // 3.3.1 Additional authentication information for the user-interactive authentication API.
const json::object auth const json::object &auth
{ {
json::get<"auth"_>(request) json::get<"auth"_>(request)
}; };
// 3.3.1 Required. The login type that the client is attempting to complete. // 3.3.1 The login type that the client is attempting to complete.
const string_view type const string_view &type
{ {
!empty(auth)? unquote(auth.at("type")) : string_view{} !empty(auth)? unquote(auth.at("type")) : string_view{}
}; };
@ -87,6 +89,21 @@ try
unquote(at<"password"_>(request)) unquote(at<"password"_>(request))
}; };
// (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
{
unquote(json::get<"device_id"_>(request))
};
const m::id::device device_id
{
requested_device_id?
m::id::device::buf{requested_device_id, my_host()}:
m::id::device::buf{m::id::generate, my_host()}
};
// 3.3.1 If true, the server binds the email used for authentication to the // 3.3.1 If true, the server binds the email used for authentication to the
// Matrix ID with the ID Server. Defaults to false. // Matrix ID with the ID Server. Defaults to false.
const auto &bind_email const auto &bind_email
@ -117,6 +134,21 @@ try
// m.login.password // m.login.password
user.password(password); user.password(password);
char access_token_buf[32];
const string_view access_token
{
m::user::gen_access_token(access_token_buf)
};
// 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.
m::send(m::user::tokens, user_id, "ircd.access_token", access_token,
{
{ "ip", string(remote(client)) },
{ "device", device_id },
});
// Send response to user // Send response to user
return resource::response return resource::response
{ {
@ -124,7 +156,8 @@ try
{ {
{ "user_id", user_id }, { "user_id", user_id },
{ "home_server", my_host() }, { "home_server", my_host() },
// { "access_token", access_token }, { "access_token", access_token },
{ "device_id", device_id },
} }
}; };
} }