0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-06-10 05:58:56 +02:00

ircd::m modules: Matrix reinterface checkpoint.

This commit is contained in:
Jason Volk 2017-09-08 02:32:49 -07:00
parent a89a8dfa5f
commit 1cea631f60
24 changed files with 1135 additions and 463 deletions

View file

@ -25,32 +25,32 @@
#pragma once
#define HAVE_IRCD_M_H
#include "json/parse.h"
namespace ircd {
namespace m {
} // namespace m
} // namespace ircd
#include "m/error.h"
#include "m/id.h"
#include "m/db.h"
#include "m/event.h"
#include "m/events.h"
#include "m/room.h"
#include "m/timeline.h"
#include "m/request.h"
#include "m/accounts.h"
#include "m/session.h"
namespace ircd {
namespace m {
struct init
namespace ircd::m::dbs
{
db::init db;
struct init
{
init();
~init() noexcept;
};
}
init();
~init() noexcept;
};
namespace ircd::m
{
struct init
{
dbs::init dbs;
} // namespace m
} // namespace ircd
init();
~init() noexcept;
};
}

View file

@ -43,35 +43,40 @@
// implied by the total 65 KB limit on events.
//
namespace ircd {
namespace m {
namespace ircd::m
{
struct event;
}
struct event
namespace ircd::m::name
{
constexpr const char *const event_id {"event_id"};
constexpr const char *const content {"content"};
constexpr const char *const origin_server_ts {"origin_server_ts"};
constexpr const char *const sender {"sender"};
constexpr const char *const type {"type"};
constexpr const char *const room_id {"room_id"};
constexpr const char *const state_key {"state_key"};
constexpr const char *const prev_ids {"prev_ids"};
constexpr const char *const unsigned_ {"unsigned"};
constexpr const char *const signatures {"signatures"};
}
struct ircd::m::event
:json::tuple
<
string_view,
time_t,
string_view,
string_view,
string_view,
string_view
json::member<name::event_id, string_view>,
json::member<name::content, string_view>,
json::member<name::origin_server_ts, time_t>,
json::member<name::sender, string_view>,
json::member<name::type, string_view>,
json::member<name::room_id, string_view>,
json::member<name::state_key, string_view>,
json::member<name::prev_ids, string_view>,
json::member<name::unsigned_, string_view>,
json::member<name::signatures, string_view>
>
{
IRCD_MEMBERS
(
"content",
"origin_server_ts",
"sender",
"type",
"unsigned",
"state_key"
)
template<class... A>
explicit event(const json::object &obj)
:tuple{json::make_tuple<decltype(*this)>(obj)}
{}
using super_type::tuple;
using super_type::operator=;
};
} // namespace m
} // namespace ircd

View file

@ -23,10 +23,47 @@
*/
#pragma once
#define HAVE_IRCD_M_ACCOUNTS_H
#define HAVE_IRCD_M_EVENTS_H
namespace ircd {
namespace m {
namespace ircd::m::events
{
IRCD_M_EXCEPTION(m::error, INVALID_TRANSITION, http::BAD_REQUEST)
} // namespace m
} // namespace ircd
struct transition;
using transition_list = std::list<struct transition *>;
extern transition_list transitions;
extern database *events;
using cursor = db::cursor<events, m::event>;
using const_iterator = cursor::const_iterator;
using iterator = const_iterator;
using where = cursor::where_type;
const_iterator find(const id &);
const_iterator begin();
const_iterator end();
void insert(event &);
void insert(const event &);
}
struct ircd::m::events::transition
{
struct method
{
std::function<bool (const event &)> valid;
std::function<void (const event &) noexcept> effects;
};
const char *name;
struct method method;
unique_const_iterator<events::transition_list> it;
virtual bool valid(const event &event) const;
virtual void effects(const event &e);
transition(const char *const &name, struct method method);
transition(const char *const &name);
virtual ~transition() noexcept;
};

74
include/ircd/m/room.h Normal file
View file

@ -0,0 +1,74 @@
/*
* charybdis: 21st Century IRC++d
*
* Copyright (C) 2016 Charybdis Development Team
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#pragma once
#define HAVE_IRCD_M_ROOM_H
namespace ircd::m
{
IRCD_M_EXCEPTION(m::error, CONFLICT, http::CONFLICT);
IRCD_M_EXCEPTION(m::error, NOT_MODIFIED, http::NOT_MODIFIED);
IRCD_M_EXCEPTION(CONFLICT, ALREADY_MEMBER, http::CONFLICT);
struct room;
}
namespace ircd::m::rooms
{
}
struct ircd::m::room
{
struct alias;
string_view room_id;
using event_closure = std::function<void (const event &)>;
void for_each(const events::where &, const event_closure &) const;
void for_each(const event_closure &) const;
using event_closure_bool = std::function<bool (const event &)>;
size_t count(const events::where &, const event_closure_bool &) const;
size_t count(const events::where &) const;
bool any(const events::where &, const event_closure_bool &) const;
bool any(const events::where &) const;
bool is_member(const m::id::user &, const string_view &membership = "join");
void membership(const m::id::user &, const json::builder &content);
void join(const m::id::user &, const json::builder &content);
room(const id::room &room_id);
room(const id::alias &alias);
};
inline
ircd::m::room::room(const id::room &room_id)
:room_id{room_id}
{}
inline
ircd::m::room::room(const id::alias &alias)
:room_id{}
{}

View file

@ -23,22 +23,23 @@
*/
#pragma once
#define HAVE_IRCD_M_DB_H
#define HAVE_IRCD_M_TIMELINE_H
namespace ircd {
namespace m {
namespace db {
namespace ircd {
namespace m {
extern database *events;
extern database *accounts;
extern database *rooms;
struct init
///////////////////////////////////////////////////////////////////////////////
//
// The timeline class represents a sequence of events. This is the preferred
// interface to the events DB. The timeline can be used to compose a selection
// of events and run algorithms over them. The timeline interface can also be
// used to insert new events. Timelines form the basis for composing higher
// level structures like rooms.
//
struct timeline
{
init();
~init() noexcept;
};
} // namespace db
} // namespace m
} // namespace ircd

View file

@ -97,11 +97,7 @@ try
client::init _client_; // Client/Socket Networking
db::init _db_; // RocksDB
js::init _js_; // SpiderMonkey
module matrix
{
"matrix"
};
m::init _matrix_; // Matrix
// This is the main program loop. Right now all it does is sleep until notified to
// break with a clean shutdown. Other subsystems may spawn their own main loops, but

View file

@ -21,6 +21,11 @@
#include <ircd/m.h>
///////////////////////////////////////////////////////////////////////////////
//
// m/session.h
//
ircd::m::session::session(const host_port &host_port)
:client{host_port}
{
@ -67,6 +72,479 @@ ircd::m::session::operator()(parse::buffer &pb,
return object;
}
///////////////////////////////////////////////////////////////////////////////
//
// m.h
//
namespace ircd {
namespace m {
std::map<std::string, ircd::module> modules;
ircd::listener *listener;
void bootstrap();
} // namespace m
} // namespace ircd
ircd::m::init::init()
{
if(db::sequence(*events::events) == 0)
bootstrap();
for(const auto &name : mods::available())
if(startswith(name, "client_"))
modules.emplace(name, name);
modules.emplace("root.so"s, "root.so"s);
listener = new ircd::listener
{
//TODO: conf obviously
json::string
({
{ "name", "Chat Matrix" },
{ "host", "0.0.0.0" },
{ "port", 8447 },
{ "ssl_certificate_file", "/home/jason/zemos.net.tls.crt" },
{ "ssl_certificate_chain_file", "/home/jason/zemos.net.tls.crt" },
{ "ssl_tmp_dh_file", "/home/jason/zemos.net.tls.dh" },
{ "ssl_private_key_file_pem", "/home/jason/zemos.net.tls.key" },
})
};
}
ircd::m::init::~init()
noexcept
{
delete listener;
modules.clear();
}
void
ircd::m::bootstrap()
{
assert(events::events);
assert(db::sequence(*events::events) == 0);
ircd::log::notice
(
"This appears to be your first time running IRCd because the events "
"database is empty. I will be bootstrapping it with initial events now..."
);
// ircd.create event
const m::id::id::room::buf room_id{"ircd", "localhost"};
const m::id::id::user::buf user_id{"ircd", "localhost"};
{
const auto type{"m.room.create"};
char content[512];
json::print(content, sizeof(content),
{
{ "creator", user_id },
});
m::event event
{
{ "room_id", room_id },
{ "type", type },
{ "state_key", "" },
{ "sender", user_id },
{ "content", content },
};
m::events::insert(event);
}
{
const auto type{"m.room.member"};
char content[512];
json::print(content, sizeof(content),
{
{ "membership", "join" },
});
m::event event
{
{ "room_id", room_id },
{ "type", type },
{ "state_key", user_id },
{ "sender", user_id },
{ "content", content },
};
m::events::insert(event);
}
}
///////////////////////////////////////////////////////////////////////////////
//
// m/db.h
//
namespace ircd::m::dbs
{
std::map<std::string, ircd::module> modules;
std::map<std::string, import_shared<database>> databases;
void init_modules();
void init_databases();
}
ircd::m::dbs::init::init()
{
init_modules();
init_databases();
ircd::m::events::events = databases.at("events").get();
}
ircd::m::dbs::init::~init()
noexcept
{
ircd::m::events::events = nullptr;
databases.clear();
modules.clear();
}
void
ircd::m::dbs::init_databases()
{
for(const auto &pair : modules)
{
const auto &name(pair.first);
const auto dbname(mods::unpostfixed(name));
const std::string shortname(lstrip(dbname, "db_"));
const std::string symname(shortname + "_database"s);
databases.emplace(shortname, import_shared<database>
{
dbname, symname
});
}
}
void
ircd::m::dbs::init_modules()
{
for(const auto &name : mods::available())
if(startswith(name, "db_"))
modules.emplace(name, name);
}
///////////////////////////////////////////////////////////////////////////////
//
// m/events.h
//
namespace ircd::m::events
{
void write(const event &);
}
ircd::database *ircd::m::events::events;
void
ircd::m::events::insert(const event &a)
{
event b(a);
insert(b);
}
void
ircd::m::events::insert(event &event)
{
if(!json::val<name::type>(event))
throw BAD_JSON("Required event field: '%s'", name::type);
if(!json::val<name::sender>(event))
throw BAD_JSON("Required event field: '%s'", name::sender);
bool has_event_id
{
!json::val<name::event_id>(event).empty()
};
const id::event::buf generated_event_id
{
has_event_id? id::event::buf{} : id::event::buf{id::generate, "cdc.z"}
};
if(!has_event_id)
json::val<name::event_id>(event) = generated_event_id;
const scope remove_our_event_id([&]
{
if(!has_event_id)
json::val<name::event_id>(event) = {};
});
bool has_origin_server_ts
{
json::val<name::origin_server_ts>(event) != 0
};
if(!has_origin_server_ts)
json::val<name::origin_server_ts>(event) = time<milliseconds>();
for(const auto &transition : events::transitions)
if(!transition->valid(event))
throw INVALID_TRANSITION("Event insertion refused: '%s'",
transition->name);
write(event);
}
ircd::m::events::const_iterator
ircd::m::events::find(const id &id)
{
cursor cur{"!room_id$event_id"};
return cur.find(id);
}
ircd::m::events::const_iterator
ircd::m::events::begin()
{
cursor cur{"!room_id$event_id"};
return cur.begin();
}
ircd::m::events::const_iterator
ircd::m::events::end()
{
cursor cur{"!room_id$event_id"};
return cur.end();
}
ircd::m::events::transition_list
ircd::m::events::transitions
{};
ircd::m::events::transition::transition(const char *const &name)
:name{name}
,it
{
events::transitions, events::transitions.emplace(events::transitions.end(), this)
}
{
}
ircd::m::events::transition::transition(const char *const &name,
struct method method)
:name{name}
,method{std::move(method)}
,it
{
events::transitions, events::transitions.emplace(events::transitions.end(), this)
}
{
}
ircd::m::events::transition::~transition()
noexcept
{
}
bool
ircd::m::events::transition::valid(const event &event)
const
{
return method.valid(event);
}
void
ircd::m::events::transition::effects(const event &event)
{
method.effects(event);
}
void
ircd::m::events::write(const event &event)
{
const auto &master_index
{
at<name::event_id>(event)
};
constexpr const size_t num_indexes(1);
constexpr const size_t num_deltas
{
event.size() + num_indexes
};
size_t i(0);
db::delta deltas[num_deltas];
for_each(event, [&i, &deltas, &master_index]
(const auto &key, const auto &val)
{
deltas[i++] =
{
key, // col
master_index, // key
byte_view<>{val}, // val
};
});
char buf[id::event::buf::SIZE + id::room::buf::SIZE];
if(!json::val<name::room_id>(event).empty())
{
strlcpy(buf, json::val<name::room_id>(event).data(), sizeof(buf));
strlcat(buf, json::val<name::event_id>(event).data(), sizeof(buf));
deltas[i++] = db::delta
{
"!room_id$event_id", // col
buf, // key
};
}
(*events)(begin(deltas), begin(deltas) + i);
}
///////////////////////////////////////////////////////////////////////////////
//
// m/room.h
//
void
ircd::m::room::join(const m::id::user &user_id,
const json::builder &content)
{
const json::builder content_with_membership
{
&content,
{ "membership", "join" }
};
membership(user_id, content_with_membership);
}
void
ircd::m::room::membership(const m::id::user &user_id,
const json::builder &content)
{
if(is_member(user_id, content.at("membership")))
throw m::ALREADY_MEMBER
{
"Already a member with this membership."
};
char cbuf[512];
m::events::insert(m::event
{
{ "room_id", room_id },
{ "type", "m.room.member" },
{ "state_key", user_id },
{ "sender", user_id },
{ "content", json::stringify(cbuf, sizeof(cbuf), content) }
});
}
bool
ircd::m::room::is_member(const m::id::user &user_id,
const string_view &membership)
{
const m::events::where::equal query
{
{ "type", "m.room.member" },
{ "state_key", user_id }
};
return count(query, [](const auto &event)
{
const json::object &content
{
json::val<m::name::content>(event)
};
const auto &membership
{
unquote(content["membership"])
};
return membership == membership;
});
}
bool
ircd::m::room::any(const events::where &where)
const
{
events::cursor cursor{"!room_id$event_id"};
cursor.where = &where;
return bool(cursor.find(room_id));
}
bool
ircd::m::room::any(const events::where &where,
const event_closure_bool &closure)
const
{
events::cursor cursor{"!room_id$event_id"};
cursor.where = &where;
for(auto it(cursor.find(room_id)); bool(it); ++it)
if(closure(*it))
return true;
return false;
}
size_t
ircd::m::room::count(const events::where &where)
const
{
events::cursor cursor{"!room_id$event_id"};
cursor.where = &where;
size_t i(0);
for(auto it(cursor.find(room_id)); bool(it); ++it, i++)
return i;
}
size_t
ircd::m::room::count(const events::where &where,
const event_closure_bool &closure)
const
{
events::cursor cursor{"!room_id$event_id"};
cursor.where = &where;
size_t i(0);
for(auto it(cursor.find(room_id)); bool(it); ++it)
{
const m::event &e(*it);
i += closure(e);
}
return i;
}
void
ircd::m::room::for_each(const events::where &where,
const event_closure &closure)
const
{
events::cursor cursor{"!room_id$event_id"};
cursor.where = &where;
auto it(cursor.find(room_id));
if(!it)
return;
for(; bool(it); ++it)
closure(*it);
}
void
ircd::m::room::for_each(const event_closure &closure)
const
{
const m::events::where::noop where{};
for_each(where, closure);
}
///////////////////////////////////////////////////////////////////////////////
//
// m/id.h

View file

@ -1,11 +1,15 @@
#
#AM_CXXFLAGS = \
# -fno-implicit-templates
#
AM_CPPFLAGS = \
-I$(top_srcdir)/include \
@JS_CPPFLAGS@ \
@BOOST_CPPFLAGS@ \
-include $(top_srcdir)/include/ircd/ircd.h \
-include $(top_srcdir)/include/ircd/mapi.h \
-include $(top_srcdir)/include/ircd/m.h \
-include $(top_srcdir)/include/ircd/db/object.h
-include $(top_srcdir)/include/ircd/m.h
AM_LDFLAGS = \
-L$(top_srcdir)/ircd \
@ -26,10 +30,8 @@ AM_LDFLAGS += \
# -export-symbols-regex *
moduledir=@moduledir@
matrix_la_SOURCES = matrix.cc
root_la_SOURCES = root.cc
module_LTLIBRARIES = \
matrix.la \
root.la \
###
@ -37,12 +39,8 @@ module_LTLIBRARIES = \
# library is client_X.so in the main modules dir.
db_moduledir = @moduledir@
db_db_events_la_SOURCES = db/events.cc
db_db_accounts_la_SOURCES = db/accounts.cc
db_db_rooms_la_SOURCES = db/rooms.cc
db_module_LTLIBRARIES = \
db/db_events.la \
db/db_accounts.la \
db/db_rooms.la \
###
# This puts the source in client/ but the installed

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
@ -106,5 +106,5 @@ resource::method post
mapi::header IRCD_MODULE
{
"registers the resource 'client/login' to handle requests"
"registers the resource 'client/account' to handle requests"
};

View file

@ -41,13 +41,43 @@ try
unquote(request["visibility"])
};
std::string room_id {"!foo@bar.com"};
const m::id::room::buf room_id
{
m::id::generate, "localhost"
};
const m::id::user::buf sender_id
{
m::id::generate, "localhost"
};
const m::id::event::buf create_event_id
{
m::id::generate, "localhost",
};
const time_t origin_server_ts
{
time(NULL)
};
/*
db::object<m::db::events, m::event> event
{
create_event_id
};
db::write
({
{ event["type"], "m.room.create" },
{ event["room_id"], room_id },
{ event["origin_server_ts"], binary_view(origin_server_ts) },
});
*/
return resource::response
{
client, http::CREATED, json::index
{
{ "room_id", room_id }
{ "room_id", string_view{room_id} }
}
};
}

View file

@ -27,12 +27,53 @@ resource events_resource
"Events (6.2.3) (10.x)"
};
const m::id::room::buf accounts_room_id
{
"accounts", "cdc.z"
};
const m::id::room::buf locops_room_id
{
"locops", "cdc.z"
};
const m::id::room::buf ircd_room_id
{
"ircd", "localhost"
};
resource::response
get_events(client &client, const resource::request &request)
{
m::room room{accounts_room_id};
std::vector<std::string> ret;
room.for_each([&ret](const auto &event)
{
ret.emplace_back(json::string(event));
});
std::vector<json::object> jo(ret.size());
std::transform(ret.begin(), ret.end(), jo.begin(), []
(const auto &string) -> json::object
{
return string;
});
char buf[16384];
char *start{buf};
char *const stop{buf + sizeof(buf)};
const auto chunk
{
json::serialize(jo, start, stop)
};
return resource::response
{
client, json::object {}
client, json::index
{
{ "chunk", string_view{chunk} }
}
};
}

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
@ -34,93 +34,109 @@ const auto home_server
"cdc.z"
};
using object = db::object<m::db::accounts>;
template<class T = string_view> using value = db::value<m::db::accounts, T>;
namespace name
{
extern constexpr auto password{"password"};
extern constexpr auto medium{"medium"};
extern constexpr auto type{"type"};
extern constexpr auto user{"user"};
extern constexpr auto address{"address"};
}
struct body
:json::tuple
<
json::member<name::password, string_view>,
json::member<name::medium, time_t>,
json::member<name::type, string_view>,
json::member<name::user, string_view>,
json::member<name::address, string_view>
>
{
using super_type::tuple;
};
// Generate !accounts:host which is the MXID of the room where the members
// are the actual account registrations themselves 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
};
resource::response
post_login_password(client &client, const resource::request &request)
post_login_password(client &client,
const resource::request &request,
const resource::request::body<body> &body)
{
const auto user
const m::id::user::buf user_id
{
// "The fully qualified user ID or just local part of the user ID, to log in."
unquote(request.at("user"))
unquote(at<name::user>(body)), home_server
};
char user_id[m::USER_ID_BUFSIZE]; m::id
{
user, home_server, user_id
};
const auto password
{
// "Required. The user's password."
request.at("password")
};
value<> password_text("password.text", user_id);
if(password_text != password)
if(!user_id.valid() || user_id.host() != home_server)
throw m::error
{
http::FORBIDDEN, "M_FORBIDDEN", "Access denied."
};
// "An access token for the account. This access token can then be used to "
// "authorize other requests. The access token may expire at some point, and if "
// "so, it SHOULD come with a refresh_token. There is no specific error message to "
// "indicate that a request has failed because an access token has expired; "
// "instead, if a client has reason to believe its access token is valid, and "
// "it receives an auth error, they should attempt to refresh for a new token "
// "on failure, and retry the request with the new token."
value<> access_token_text{"access_token.text", user_id};
const auto &supplied_password
{
at<name::password>(body)
};
// Generate access token
char access_token[m::ACCESS_TOKEN_BUFSIZE];
m::access_token_generate(access_token, sizeof(access_token));
const auto check{[&supplied_password]
(const auto &event)
{
const json::object &content
{
json::val<m::name::content>(event)
};
// Write access token to database
access_token_text = access_token;
ircd::resource::tokens.emplace(access_token, &client); //TODO: XXX
value<> token_to_user_id{"token", access_token_text};
token_to_user_id = user_id;
const auto &membership
{
unquote(content.at("membership"))
};
if(membership != "join")
return false;
const auto &correct_password
{
content.at("password")
};
if(supplied_password != correct_password)
return false;
return true;
}};
const m::events::where::equal query
{
{ "type", "m.room.member" },
{ "state_key", user_id }
};
if(!accounts_room.any(query, check))
throw m::error
{
http::FORBIDDEN, "M_FORBIDDEN", "Access denied."
};
// Send response to user
return resource::response
{
client,
{
{ "user_id", user_id },
{ "access_token", access_token },
{ "home_server", home_server },
}
};
}
resource::response
post_login_token(client &client, const resource::request &request)
{
const auto token
{
unquote(request.at("token"))
};
const value<> user_id
{
"token", token
};
if(!user_id)
throw m::error
{
http::FORBIDDEN, "M_FORBIDDEN", "Access denied."
};
return resource::response
{
client,
{
{ "user_id", string_view(user_id) },
{ "access_token", token },
{ "home_server", home_server },
{ "user_id", string_view{user_id} },
{ "home_server", home_server },
// { "access_token", access_token },
}
};
}
@ -128,16 +144,20 @@ post_login_token(client &client, const resource::request &request)
resource::response
post_login(client &client, const resource::request &request)
{
const resource::request::body<body> body
{
request
};
// x.x.x Required. The login type being used.
// Currently only "m.login.password" is supported.
const auto type
{
// "Required. The login type being used. Currently only "m.login.password" is supported."
unquote(request.at("type"))
unquote(at<name::type>(body))
};
if(type == "m.login.password")
return post_login_password(client, request);
else if(type == "m.login.token")
return post_login_token(client, request);
return post_login_password(client, request, body);
else
throw m::error
{

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
@ -21,9 +21,6 @@
using namespace ircd;
using object = db::object<m::db::accounts>;
template<class T = string_view> using value = db::value<m::db::accounts, T>;
resource logout_resource
{
"_matrix/client/r0/logout",

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
@ -21,51 +21,79 @@
using namespace ircd;
using object = db::object<m::db::accounts>;
template<class T = string_view> using value = db::value<m::db::accounts, T>;
const auto home_server
{
// "The hostname of the homeserver on which the account has been registered."
"cdc.z"
};
resource::response
handle_post(client &client,
resource::request &request)
namespace name
{
const auto kind
constexpr auto username{"username"};
constexpr auto bind_email{"bind_email"};
constexpr auto password{"password"};
constexpr auto auth{"auth"};
}
struct body
:json::tuple
<
json::member<name::username, string_view>,
json::member<name::bind_email, bool>,
json::member<name::password, string_view>,
json::member<name::auth, json::object>
>
{
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
};
m::room accounts_room
{
accounts_room_id
};
static void
join_accounts_room(const m::id::user &user_id,
const json::members &contents)
try
{
const json::builder content
{
request.query["kind"]
nullptr, &contents
};
if(kind.empty() || kind == "guest")
accounts_room.join(user_id, content);
}
catch(const m::ALREADY_MEMBER &e)
{
throw m::error
{
char user_id[m::USER_ID_BUFSIZE]; m::id
{
"randy12345", home_server, user_id
};
char access_token[m::ACCESS_TOKEN_BUFSIZE];
m::access_token_generate(access_token, sizeof(access_token));
ircd::resource::tokens.emplace(access_token, &client); //TODO: XXX
value<> token_to_user_id{"token", access_token};
token_to_user_id = user_id;
http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use."
};
}
return resource::response
{
client, http::CREATED, json::index
{
{ "access_token", access_token },
{ "home_server", home_server },
{ "user_id", user_id },
}
};
}
const auto type
resource::response
handle_post_kind_user(client &client,
resource::request &request,
const resource::request::body<body> &body)
{
// 3.3.1 Additional authentication information for the user-interactive authentication API.
const auto &auth
{
// "Required. The login type that the client is attempting to complete."
unquote(request.at("auth.type"))
at<name::auth>(body)
};
// 3.3.1 Required. The login type that the client is attempting to complete.
const auto &type
{
unquote(auth.at("type"))
};
if(type != "m.login.dummy")
@ -74,71 +102,61 @@ handle_post(client &client,
"M_UNSUPPORTED", "Registration '%s' not supported.", type
};
const auto username
// 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
{
// "The local part of the desired Matrix ID. If omitted, the homeserver MUST "
// "generate a Matrix ID local part."
unquote(request.get("username"))
unquote(get<name::username>(body))
};
if(!username.empty() && !m::username_valid(username))
// Generate canonical mxid
const m::id::user::buf user_id
{
username, home_server
};
if(!user_id.valid())
throw m::error
{
"M_INVALID_USERNAME", "The desired user ID is not a valid user name."
};
const auto password
if(user_id.host() != home_server)
throw m::error
{
"M_INVALID_USERNAME", "Can only register with host '%s'", home_server
};
// 3.3.1 Required. The desired password for the account.
const auto &password
{
// "Required. The desired password for the account."
request.at("password")
at<name::password>(body)
};
const auto bind_email
if(password.size() > 255)
throw m::error
{
"M_INVALID_PASSWORD", "The desired password is too long"
};
// 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
{
// "If true, the server binds the email used for authentication to the "
// "Matrix ID with the ID Server."
request.get<bool>("bind_email", false)
get<name::bind_email>(body, false)
};
// Generate fully qualified user id and randomize username if missing
char user_id[m::USER_ID_BUFSIZE]; m::id
// Register the user by joining them to the accounts room. Join the user
// to the accounts room by issuing a join event. The content 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.
char content[384];
join_accounts_room(user_id,
{
username, home_server, user_id
};
// Atomic commitment to registration
value<time_t> registered{"registered", user_id};
{
time_t expected(0); // unregistered == empty == 0
if(!registered.compare_exchange(expected, time(nullptr)))
throw m::error
{
http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already taken."
};
}
// "An access token for the account. This access token can then be used to "
// "authorize other requests. The access token may expire at some point, and if "
// "so, it SHOULD come with a refresh_token. There is no specific error message to "
// "indicate that a request has failed because an access token has expired; "
// "instead, if a client has reason to believe its access token is valid, and "
// "it receives an auth error, they should attempt to refresh for a new token "
// "on failure, and retry the request with the new token."
value<> access_token_text{"access_token.text", user_id};
// Prepare to store password
value<> password_text("password.text", user_id);
// Generate access token
char access_token[m::ACCESS_TOKEN_BUFSIZE];
m::access_token_generate(access_token, sizeof(access_token));
ircd::resource::tokens.emplace(access_token, &client); //TODO: XXX
// Batch transaction to database
db::write
({
{ db::SET, password_text, password },
{ db::SET, access_token_text, access_token },
{ "password", password },
{ "bind_email", bind_email },
});
// Send response to user
@ -146,13 +164,60 @@ handle_post(client &client,
{
client, http::CREATED, json::index
{
{ "access_token", access_token },
{ "home_server", home_server },
{ "user_id", user_id },
{ "home_server", home_server },
// { "access_token", access_token },
}
};
}
resource::response
handle_post_kind_guest(client &client,
resource::request &request,
const resource::request::body<body> &body)
{
const m::id::user::buf user_id
{
m::generate, home_server
};
return resource::response
{
client, http::CREATED, json::index
{
{ "user_id", user_id },
{ "home_server", home_server },
//{ "access_token", access_token },
}
};
}
resource::response
handle_post(client &client,
resource::request &request)
{
const resource::request::body<body> body
{
request
};
const auto kind
{
request.query["kind"]
};
if(kind == "user")
return handle_post_kind_user(client, request, body);
if(kind.empty() || kind == "guest")
return handle_post_kind_guest(client, request, body);
throw m::error
{
http::BAD_REQUEST, "M_UNKNOWN", "Unknown 'kind' of registration specified in query."
};
}
resource register_resource
{
"_matrix/client/r0/register",

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
@ -21,16 +21,16 @@
using namespace ircd;
//using object = db::object<m::db::accounts>;
//template<class T = string_view> using value = db::value<T, m::db::accounts>;
resource sync_resource
{
"_matrix/client/r0/sync",
"Synchronise the client's state with the latest state on the server. "
"Clients use this API when they first log in to get an initial snapshot of "
"the state on the server, and then continue to call this API to get "
"incremental deltas to the state, and to receive new messages. (6.2)"
R"(
Synchronise the client's state with the latest state on the server.
Clients use this API when they first log in to get an initial snapshot of
the state on the server, and then continue to call this API to get
incremental deltas to the state, and to receive new messages. (6.2)
)"
};
resource::response

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*

View file

@ -21,16 +21,6 @@
using namespace ircd;
const database::descriptor accounts_token_descriptor
{
"token",
"An index of access_token to user_id",
{
// readable key // readable value
typeid(string_view), typeid(string_view)
}
};
const database::descriptor accounts_registered_descriptor
{
"registered",
@ -44,10 +34,7 @@ const database::descriptor accounts_registered_descriptor
const database::description accounts_description
{
{ "default" },
accounts_token_descriptor,
accounts_registered_descriptor,
{ "access_token" },
{ "access_token.text" },
{ "password" },
{ "password.text" },
{ "password.hash" },
@ -63,4 +50,3 @@ mapi::header IRCD_MODULE
{
"Hosts the 'accounts' database"
};

View file

@ -21,6 +21,30 @@
using namespace ircd;
const database::descriptor events_event_id_descriptor
{
// name
"event_id",
// explanation
R"(### protocol note:
10.1
The id of event.
10.4
MUST NOT exceed 255 bytes.
### developer note:
key is event_id. This is redundant data but we have to have it for now.
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
}
};
const database::descriptor events_type_descriptor
{
// name
@ -191,9 +215,118 @@ const database::descriptor events_origin_server_ts_descriptor
}
};
const database::descriptor events_prev_ids_descriptor
{
// name
"prev_ids",
// explanation
R"(### protocol note:
FEDERATION 4.1 (INCONSISTENT)
List of (String, String, Object) Triplets
The originating homeserver, PDU ids and hashes of the most recent PDUs the homeserver was
aware of for the room when it made this PDU. ["blue.example.com","99d16afbc8", {"sha256":
"abase64encodedsha256hashshouldbe43byteslong"}]
### developer note:
key is event_id
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
}
};
const database::descriptor events_unsigned_descriptor
{
// name
"unsigned",
// explanation
R"(### protocol note:
### developer note:
key is event_id
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
}
};
const database::descriptor events_signatures_descriptor
{
// name
"signatures",
// explanation
R"(### protocol note:
### developer note:
key is event_id
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
}
};
const database::descriptor index_room_id_to_event_id
{
// name
"!room_id$event_id",
// explanation
R"(### developer note:
key is "!room_id$event_id"
the prefix transform is in effect. this column indexes events by
room_id offering an iterable bound of the index prefixed by room_id
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
},
// options
{},
// comparator
{},
// prefix transform
{
"!room_id$event_id"s,
[](const string_view &key)
{
return key.find('$') != key.npos;
},
[](const string_view &key)
{
return split(key, '$').first;
}
}
// hooks
// {
// }
};
const database::description events_description
{
{ "default" },
events_event_id_descriptor,
events_type_descriptor,
events_content_descriptor,
events_room_id_descriptor,
@ -201,6 +334,10 @@ const database::description events_description
events_state_key_descriptor,
events_origin_descriptor,
events_origin_server_ts_descriptor,
events_prev_ids_descriptor,
events_unsigned_descriptor,
events_signatures_descriptor,
index_room_id_to_event_id,
};
std::shared_ptr<database> events_database

View file

@ -1,60 +0,0 @@
/*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk <jason@zemos.net>
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using namespace ircd;
const database::descriptor rooms_head_descriptor
{
// name
"head",
// notes
R"(
### developer note:
The latest event for a room.
key is room_id
value is event_id
)",
// typing for key and value
{
typeid(string_view), typeid(string_view)
}
};
const database::description rooms_description
{
{ "default" },
rooms_head_descriptor,
};
std::shared_ptr<database> rooms_database
{
std::make_shared<database>("room"s, ""s, rooms_description)
};
mapi::header IRCD_MODULE
{
"Hosts the 'rooms' database"
};

View file

@ -1,133 +0,0 @@
/*
* Copyright (C) 2016 Charybdis Development Team
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using namespace ircd;
mapi::header IRCD_MODULE
{
"Chat Matrix Protocol"
};
std::map<std::string, module> modules;
void test();
//TODO: XXX very temporary stuff around here
// The path root (serves static assets for web etc); this pointer
// exists for now to easily find and reload that specifically.
module *root_module;
const auto _init_([]
{
// These modules host databases and have to be loaded first.
modules.emplace("client_events.so"s, "client_events.so"s);
modules.emplace("client_account.so"s, "client_account.so"s);
modules.emplace("client_rooms.so"s, "client_rooms.so"s);
for(const auto &name : mods::available())
if(name != "matrix.so")
modules.emplace(name, name);
root_module = &modules.at("root.so"s);
test();
return true;
}());
listener matrices
{
std::string { json::index
{
{ "name", "Chat Matrix" },
{ "host", "0.0.0.0" },
{
"ssl_certificate_file", "/home/jason/newcert.pem"
},
{
"ssl_certificate_chain_file", "/home/jason/newcert.pem"
},
{
"ssl_tmp_dh_file", "/home/jason/dh1024.pem"
},
{
"ssl_private_key_file_pem", "/home/jason/privkey.pem"
},
{
"port", 8448
}
}}
};
void test()
{
json::object test
{
R"({"content":"hello","origin_server_ts":12345,"sender":"@foo:bar.com"})"
};
/*
const m::event ev0
{
"some content", 0, "some sender", "some type", "some unsigned", "statie"
};
*/
const m::event ev
{
test
};
std::cout << "size: " << sizeof(ev) << std::endl;
json::for_each(ev, []
(auto&& key, auto&& val)
{
std::cout << key << " => " << val << std::endl;
});
std::cout << "----" << std::endl;
json::rfor_each(ev, []
(const string_view &key, auto&& val)
{
std::cout << key << " => " << val << std::endl;
});
std::cout << "----" << std::endl;
json::until(ev, []
(const string_view &key, auto&& val)
{
std::cout << key << " => " << val << std::endl;
return true;
});
std::cout << "----" << std::endl;
json::runtil(ev, []
(const string_view &key, auto&& val)
{
std::cout << key << " => " << val << std::endl;
return true;
});
std::cout << std::endl;
std::cout << json::indexof(ev, "origin_server_ts") << std::endl;
std::cout << std::endl;
}

View file

@ -25,7 +25,7 @@ using namespace ircd;
mapi::header IRCD_MODULE
{
"Chat Matrix Protocol"
"Web root content resource",
};
IRCD_INIT_PRIORITY(STD_CONTAINER)