0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-06-24 12:58:21 +02:00

Checkpoint matrix application basis.

This commit is contained in:
Jason Volk 2017-09-24 18:05:42 -07:00
parent 196d158398
commit c032c686f6
21 changed files with 1835 additions and 617 deletions

View file

@ -248,15 +248,104 @@ try
break;
}
const auto args(tokens_after(line, " ", 0));
const params token{args, " ", {"room_id"}};
const auto room_id{token.at(0, "!ircd:cdc.z"s)};
m::request request
{
"GET", "_matrix/client/r0/events"
"GET", "_matrix/client/r0/events", {}, json::members
{
{ "room_id", string_view{room_id} }
}
};
static char buf[1024];
static char buf[65536];
ircd::parse::buffer pb{buf};
const auto doc((*moi)(pb, request));
std::cout << doc << std::endl;
const json::object response{(*moi)(pb, request)};
const json::array chunk(response["chunk"]);
for(const string_view &chunk : chunk)
std::cout << chunk << std::endl;
break;
}
case hash("context"):
{
if(!moi)
{
std::cerr << "No current session" << std::endl;
break;
}
const auto args(tokens_after(line, " ", 0));
const params token{args, " ", {"room_id", "event_id"}};
const auto &room_id{token.at(0)};
const auto &event_id{token.at(1)};
char url[512]; const auto url_len
{
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/context/%s",
room_id,
event_id)
};
m::request request
{
"GET", string_view{url, size_t(url_len)}, {}
};
static char buf[65536];
ircd::parse::buffer pb{buf};
const json::object response{(*moi)(pb, request)};
for(const auto &member : response)
std::cout << member.first << " " << member.second << std::endl;
break;
}
case hash("state"):
{
if(!moi)
{
std::cerr << "No current session" << std::endl;
break;
}
const auto args(tokens_after(line, " ", 0));
const params token{args, " ", {"room_id", "event_type", "state_key"}};
const auto &room_id{token.at(0)};
const auto event_type{token[1]};
const auto state_key{token[2]};
char url[512]; const auto url_len
{
event_type && state_key?
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/state/%s/%s",
room_id,
event_type,
state_key):
event_type?
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/state/%s",
room_id,
event_type):
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/state",
room_id)
};
m::request request
{
"GET", string_view{url, size_t(url_len)}, {}
};
static char buf[65536];
ircd::parse::buffer pb{buf};
const json::array response{(*moi)(pb, request)};
for(const auto &event : response)
std::cout << event << std::endl;
break;
}
@ -336,7 +425,7 @@ try
static char buf[4096];
ircd::parse::buffer pb{buf};
const auto doc((*moi)(pb, request));
const string_view doc((*moi)(pb, request));
std::cout << doc << std::endl;
break;
}
@ -350,7 +439,32 @@ try
}
const auto args(tokens_after(line, " ", 0));
const params token{args, " ", {"username", "password"}};
if(args.empty())
{
m::request request
{
"GET", "_matrix/client/r0/login", {}, {}
};
static char buf[4096];
ircd::parse::buffer pb{buf};
const json::object doc((*moi)(pb, request));
const json::array flows(doc.at("flows"));
size_t i(0);
for(const auto &flow : flows)
std::cout << i++ << ": " << flow << std::endl;
break;
}
const params token
{
args, " ",
{
"username", "password"
}
};
m::request request
{
@ -364,7 +478,7 @@ try
static char buf[4096];
ircd::parse::buffer pb{buf};
const auto doc((*moi)(pb, request));
const json::object doc((*moi)(pb, request));
std::cout << doc << std::endl;
moi->access_token = std::string(unquote(doc.at("access_token")));
break;
@ -475,6 +589,102 @@ try
break;
}
*/
case hash("setfilter"):
{
if(!moi)
{
std::cerr << "No current session" << std::endl;
break;
}
const auto args
{
tokens_after(line, " ", 0)
};
const auto user_id
{
token(args, " ", 0)
};
const json::object filter
{
tokens_after(args, " ", 0)
};
static char url[128]; const auto url_len
{
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/user/%s/filter", user_id)
};
static char query[512]; const auto query_len
{
fmt::snprintf(query, sizeof(query), "%s=%s",
"access_token",
moi->access_token)
};
m::request request
{
"POST", url, query, filter
};
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("getfilter"):
{
if(!moi)
{
std::cerr << "No current session" << std::endl;
break;
}
const auto args
{
tokens_after(line, " ", 0)
};
const auto user_id
{
token(args, " ", 0)
};
const auto filter_id
{
tokens_after(args, " ", 0)
};
static char url[128]; const auto url_len
{
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/user/%s/filter/%s",
user_id,
filter_id)
};
static char query[512]; const auto query_len
{
fmt::snprintf(query, sizeof(query), "%s=%s",
"access_token",
moi->access_token)
};
m::request request
{
"GET", url, query, {}
};
static char buf[4096];
ircd::parse::buffer pb{buf};
const json::object response{(*moi)(pb, request)};
std::cout << string_view{response} << std::endl;
break;
}
default:
std::cerr << "Bad command or filename" << std::endl;
}

View file

@ -29,9 +29,9 @@
#include "m/error.h"
#include "m/id.h"
#include "m/event.h"
#include "m/events.h"
#include "m/room.h"
#include "m/timeline.h"
#include "m/user.h"
#include "m/filter.h"
#include "m/request.h"
#include "m/session.h"

View file

@ -50,33 +50,47 @@ namespace ircd::m
namespace ircd::m::name
{
extern constexpr const char *const content {"content"};
extern constexpr const char *const event_id {"event_id"};
extern constexpr const char *const origin_server_ts {"origin_server_ts"};
extern constexpr const char *const prev_ids {"prev_ids"};
extern constexpr const char *const room_id {"room_id"};
extern constexpr const char *const sender {"sender"};
extern constexpr const char *const signatures {"signatures"};
extern constexpr const char *const state_key {"state_key"};
extern constexpr const char *const type {"type"};
extern constexpr const char *const unsigned_ {"unsigned"};
constexpr const char *const content {"content"};
constexpr const char *const event_id {"event_id"};
constexpr const char *const origin_server_ts {"origin_server_ts"};
constexpr const char *const prev_ids {"prev_ids"};
constexpr const char *const room_id {"room_id"};
constexpr const char *const sender {"sender"};
constexpr const char *const signatures {"signatures"};
constexpr const char *const state_key {"state_key"};
constexpr const char *const type {"type"};
constexpr const char *const unsigned_ {"unsigned"};
}
struct ircd::m::event
:json::tuple
<
json::property<name::event_id, string_view>,
json::property<name::content, string_view>,
json::property<name::event_id, string_view>,
json::property<name::origin_server_ts, time_t>,
json::property<name::sender, string_view>,
json::property<name::type, string_view>,
json::property<name::room_id, string_view>,
json::property<name::state_key, string_view>,
json::property<name::prev_ids, string_view>,
json::property<name::unsigned_, string_view>,
json::property<name::signatures, string_view>
json::property<name::room_id, string_view>,
json::property<name::sender, string_view>,
json::property<name::signatures, string_view>,
json::property<name::state_key, string_view>,
json::property<name::type, string_view>,
json::property<name::unsigned_, string_view>
>
{
using id = m::id::event;
static database *events;
using cursor = db::cursor<events, event>;
using const_iterator = cursor::const_iterator;
using iterator = const_iterator;
using where = cursor::where_type;
// Queue of contexts waiting to see the next inserted event
static ctx::view<const event> inserted;
static const_iterator find(const id &);
static void insert(json::iov &);
using super_type::tuple;
using super_type::operator=;
};

View file

@ -1,69 +0,0 @@
/*
* 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_EVENTS_H
namespace ircd::m::events
{
IRCD_M_EXCEPTION(m::error, INVALID_TRANSITION, http::BAD_REQUEST)
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(const event &);
void insert(json::iov &);
}
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;
};

114
include/ircd/m/filter.h Normal file
View file

@ -0,0 +1,114 @@
/*
* 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_FILTER_H
namespace ircd::m
{
struct filter;
struct room_filter;
struct event_filter;
struct room_event_filter;
}
namespace ircd::m::name
{
constexpr const char *const event_fields {"event_fields"};
constexpr const char *const event_format {"event_format"};
constexpr const char *const account_data {"account_data"};
constexpr const char *const presence {"presence"};
constexpr const char *const room {"room"};
constexpr const char *const timeline {"timeline"};
constexpr const char *const ephemeral {"ephemeral"};
constexpr const char *const state {"state"};
constexpr const char *const rooms {"rooms"};
constexpr const char *const not_rooms {"not_rooms"};
constexpr const char *const include_leave {"include_leave"};
constexpr const char *const types {"types"};
constexpr const char *const not_types {"not_types"};
constexpr const char *const senders {"senders"};
constexpr const char *const not_senders {"not_senders"};
constexpr const char *const limit {"limit"};
}
struct ircd::m::event_filter
:json::tuple
<
json::property<name::limit, uint>,
json::property<name::types, json::array>,
json::property<name::senders, json::array>,
json::property<name::not_types, json::array>,
json::property<name::not_senders, json::array>
>
{
using super_type::tuple;
using super_type::operator=;
};
struct ircd::m::room_event_filter
:json::tuple
<
json::property<name::limit, uint>,
json::property<name::types, json::array>,
json::property<name::rooms, json::array>,
json::property<name::senders, json::array>,
json::property<name::not_types, json::array>,
json::property<name::not_rooms, json::array>,
json::property<name::not_senders, json::array>
>
{
using super_type::tuple;
using super_type::operator=;
};
struct ircd::m::room_filter
:json::tuple
<
json::property<name::rooms, json::array>,
json::property<name::not_rooms, json::array>,
json::property<name::state, room_event_filter>,
json::property<name::timeline, room_event_filter>,
json::property<name::ephemeral, room_event_filter>,
json::property<name::account_data, room_event_filter>,
json::property<name::include_leave, bool>
>
{
using super_type::tuple;
using super_type::operator=;
};
struct ircd::m::filter
:json::tuple
<
json::property<name::event_fields, json::array>,
json::property<name::event_format, string_view>,
json::property<name::account_data, event_filter>,
json::property<name::room, room_filter>,
json::property<name::presence, event_filter>
>
{
using super_type::tuple;
using super_type::operator=;
};

View file

@ -100,7 +100,10 @@ template<class T,
struct ircd::m::id::buf
:T
{
static constexpr const size_t &SIZE{MAX};
static constexpr const size_t SIZE
{
MAX
};
private:
std::array<char, SIZE> b;

View file

@ -34,42 +34,85 @@ namespace ircd::m
struct room;
}
namespace ircd::m::rooms
{
}
struct ircd::m::room
{
struct alias;
struct events;
struct state;
string_view room_id;
using id = m::id::room;
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;
id room_id;
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 send(json::iov &event);
void send(const json::members &event);
bool is_member(const m::id::user &, const string_view &membership = "join");
void membership(const m::id::user &, json::iov &content);
void leave(const m::id::user &, json::iov &content);
void join(const m::id::user &, json::iov &content);
room(const id::room &room_id);
room(const id::alias &alias);
room(const id &room_id)
:room_id{room_id}
{}
room(const id::alias &alias)
:room_id{}
{}
};
inline
ircd::m::room::room(const id::room &room_id)
:room_id{room_id}
{}
struct ircd::m::room::events
{
id room_id;
inline
ircd::m::room::room(const id::alias &alias)
:room_id{}
{}
using event_closure = std::function<void (const event &)>;
using event_closure_bool = std::function<bool (const event &)>;
bool query(const event::where &, const event_closure_bool &) const;
bool rquery(const event::where &, const event_closure_bool &) const;
void for_each(const event::where &, const event_closure &) const;
void rfor_each(const event::where &, const event_closure &) const;
size_t count(const event::where &, const event_closure_bool &) const;
bool any(const event::where &, const event_closure_bool &) const;
bool query(const event_closure_bool &) const;
bool rquery(const event_closure_bool &) const;
void for_each(const event_closure &) const;
void rfor_each(const event_closure &) const;
size_t count(const event::where &) const;
bool any(const event::where &) const;
events(const id &room_id)
:room_id{room_id}
{}
events(const room &room)
:room_id{room.room_id}
{}
};
struct ircd::m::room::state
{
id room_id;
using event_closure = std::function<void (const event &)>;
using event_closure_bool = std::function<bool (const event &)>;
bool query(const event::where &, const event_closure_bool &) const;
void for_each(const event::where &, const event_closure &) const;
size_t count(const event::where &, const event_closure_bool &) const;
bool any(const event::where &, const event_closure_bool &) const;
bool query(const event_closure_bool &) const;
void for_each(const event_closure &) const;
size_t count(const event::where &) const;
bool any(const event::where &) const;
state(const id &room_id)
:room_id{room_id}
{}
state(const room &room)
:room_id{room.room_id}
{}
};

View file

@ -23,23 +23,27 @@
*/
#pragma once
#define HAVE_IRCD_M_TIMELINE_H
#define HAVE_IRCD_M_USER_H
namespace ircd {
namespace m {
///////////////////////////////////////////////////////////////////////////////
//
// 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
namespace ircd::m
{
struct user;
}
namespace ircd::m::users
{
}
struct ircd::m::user
{
using id = m::id::user;
id user_id;
user(const id &user_id);
};
} // namespace m
} // namespace ircd
inline
ircd::m::user::user(const id &user_id)
:user_id{user_id}
{}

File diff suppressed because it is too large Load diff

View file

@ -131,7 +131,7 @@ try
{ "state_key", access_token }
};
if(!accounts.any(query))
if(!m::room::events{accounts}.any(query))
throw m::error
{
// When credentials are required but missing or invalid, the HTTP call will return with
@ -215,6 +215,13 @@ catch(const json::error &e)
http::BAD_REQUEST, "M_NOT_JSON", "%s", e.what()
};
}
catch(const std::out_of_range &e)
{
throw m::error
{
http::NOT_FOUND, "M_NOT_FOUND", "%s", e.what()
};
}
ircd::resource::method::method(struct resource &resource,
const string_view &name,

View file

@ -39,33 +39,44 @@ const m::id::room::buf locops_room_id
const m::id::room::buf ircd_room_id
{
"ircd", "localhost"
"ircd", "cdc.z"
};
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)
m::room room
{
ret.emplace_back(json::string(event));
m::id::room
{
unquote(request.at("room_id"))
}
};
const m::room::events events
{
room
};
size_t i(0);
events.for_each([&i](const auto &event)
{
++i;
});
std::vector<json::object> jo(ret.size());
std::transform(ret.begin(), ret.end(), jo.begin(), []
(const auto &string) -> json::object
size_t j(0);
json::value ret[i];
events.for_each([&i, &j, &ret](const m::event &event)
{
return string;
if(j < i)
ret[j++] = event;
});
char buf[16384];
return resource::response
{
client,
client, json::members
{
{ "chunk", json::stringify(buf, jo) }
{ "chunk", { ret, j } }
}
};
}

View file

@ -34,14 +34,14 @@ const auto home_server
"cdc.z"
};
namespace name
namespace { 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"};
}
constexpr const auto password{"password"};
constexpr const auto medium{"medium"};
constexpr const auto type{"type"};
constexpr const auto user{"user"};
constexpr const auto address{"address"};
}}
struct body
:json::tuple
@ -69,15 +69,19 @@ 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 &request,
const resource::request::body<body> &body)
const resource::request::object<body> &request)
{
// Build a canonical MXID from a the user field
const m::id::user::buf user_id
{
unquote(at<name::user>(body)), home_server
unquote(at<name::user>(request)), home_server
};
if(!user_id.valid() || user_id.host() != home_server)
@ -88,14 +92,23 @@ post_login_password(client &client,
const auto &supplied_password
{
at<name::password>(body)
at<name::password>(request)
};
const auto check{[&supplied_password]
// Sets up the query to find the user_id in the accounts room
const m::event::where::equal member_event
{
{ "type", "m.room.member" },
{ "state_key", 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
{
{
json::val<m::name::content>(event)
};
@ -109,56 +122,79 @@ post_login_password(client &client,
const auto &correct_password
{
content.at("password")
content.get("password")
};
if(!correct_password)
return false;
if(supplied_password != correct_password)
return false;
return true;
}};
const m::events::where::equal query
const auto query
{
{ "type", "m.room.member" },
{ "state_key", user_id }
member_event && correct_password
};
if(!accounts_room.any(query, check))
// 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))
throw m::error
{
http::FORBIDDEN, "M_FORBIDDEN", "Access denied."
};
// Generate the access token
static constexpr const auto token_len{127};
static const auto token_dict{rand::dict::alpha};
char token_buf[token_len + 1];
const string_view access_token
{
rand::string(token_dict, token_len, token_buf, sizeof(token_buf))
};
// 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(
{
{ "type", "ircd.access_token" },
{ "sender", user_id },
{ "state_key", access_token },
{ "content", json::members
{
{ "ip", string(remote_addr(client)) },
{ "device", "unknown" },
}}
});
// Send response to user
return resource::response
{
client,
{
{ "user_id", string_view{user_id} },
{ "home_server", home_server },
// { "access_token", access_token },
{ "user_id", user_id },
{ "home_server", home_server },
{ "access_token", access_token },
}
};
}
resource::response
post_login(client &client, const resource::request &request)
post_login(client &client, const resource::request::object<body> &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
{
unquote(at<name::type>(body))
unquote(at<name::type>(request))
};
if(type == "m.login.password")
return post_login_password(client, request, body);
return post_login_password(client, request);
else
throw m::error
{
@ -174,14 +210,22 @@ resource::method method_post
resource::response
get_login(client &client, const resource::request &request)
{
static const json::object flows
json::member login_password
{
R"({"flows":[{"type":"m.login.password"}]})"
"type", "m.login.password"
};
json::value flows[1]
{
{ login_password }
};
return resource::response
{
client, flows
client, json::members
{
{ "flows", { flows, 1 } }
}
};
}

View file

@ -31,18 +31,14 @@ resource logout_resource
resource::response
logout(client &client, const resource::request &request)
{
const auto &access_token(request.query.at("access_token"));
const auto it(resource::tokens.find(access_token));
if(unlikely(it == end(resource::tokens)))
throw http::error{http::INTERNAL_SERVER_ERROR};
const auto &access_token
{
request.query.at("access_token")
};
resource::tokens.erase(it);
return resource::response
{
client,
{
{ }
}
client, http::OK
};
}

View file

@ -33,10 +33,7 @@ get_publicrooms(client &client, const resource::request &request)
{
return resource::response
{
client,
{
{ }
}
client, http::OK
};
}

View file

@ -36,10 +36,7 @@ try
{
return resource::response
{
client,
{
{ }
}
client, http::OK
};
}
catch(...)

View file

@ -27,13 +27,13 @@ const auto home_server
"cdc.z"
};
namespace name
namespace { namespace name
{
constexpr auto username{"username"};
constexpr auto bind_email{"bind_email"};
constexpr auto password{"password"};
constexpr auto auth{"auth"};
}
constexpr const auto username {"username"};
constexpr const auto bind_email {"bind_email"};
constexpr const auto password {"password"};
constexpr const auto auth {"auth"};
}}
struct body
:json::tuple
@ -44,7 +44,7 @@ struct body
json::property<name::auth, json::object>
>
{
using super_type::tuple;
using super_type::tuple;
};
// Generate !accounts:host which is the MXID of the room where the members
@ -54,40 +54,26 @@ 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
join_accounts_room(const m::id::user &user_id,
const json::members &contents)
try
{
const json::builder content
{
nullptr, &contents
};
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."
};
}
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,
resource::request &request,
const resource::request::body<body> &body)
const resource::request::object<body> &request)
{
// 3.3.1 Additional authentication information for the user-interactive authentication API.
const auto &auth
{
at<name::auth>(body)
at<name::auth>(request)
};
// 3.3.1 Required. The login type that the client is attempting to complete.
@ -96,6 +82,7 @@ handle_post_kind_user(client &client,
unquote(auth.at("type"))
};
// We only support this for now, for some reason. TODO: XXX
if(type != "m.login.dummy")
throw m::error
{
@ -106,53 +93,41 @@ handle_post_kind_user(client &client,
// generate a Matrix ID local part.
const auto &username
{
unquote(get<name::username>(body))
unquote(get<name::username>(request))
};
// Generate canonical mxid
// 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, home_server
};
if(!user_id.valid())
throw m::error
{
"M_INVALID_USERNAME", "The desired user ID is not a valid user name."
};
if(user_id.host() != home_server)
throw m::error
{
"M_INVALID_USERNAME", "Can only register with host '%s'", home_server
};
// 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
{
at<name::password>(body)
at<name::password>(request)
};
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
{
get<name::bind_email>(body, false)
get<name::bind_email>(request, false)
};
// 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];
// 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,
{
{ "password", password },
@ -173,8 +148,7 @@ handle_post_kind_user(client &client,
resource::response
handle_post_kind_guest(client &client,
resource::request &request,
const resource::request::body<body> &body)
const resource::request::object<body> &request)
{
const m::id::user::buf user_id
{
@ -187,30 +161,25 @@ handle_post_kind_guest(client &client,
{
{ "user_id", user_id },
{ "home_server", home_server },
//{ "access_token", access_token },
// { "access_token", access_token },
}
};
}
resource::response
handle_post(client &client,
resource::request &request)
const resource::request::object<body> &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);
return handle_post_kind_user(client, request);
if(kind.empty() || kind == "guest")
return handle_post_kind_guest(client, request, body);
return handle_post_kind_guest(client, request);
throw m::error
{
@ -233,3 +202,51 @@ 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)
{
if(!user_id.valid())
throw m::error
{
"M_INVALID_USERNAME", "The desired user ID is not a valid user name."
};
if(user_id.host() != home_server)
throw m::error
{
"M_INVALID_USERNAME", "Can only register with host '%s'", home_server
};
}
void
validate_password(const string_view &password)
{
if(password.size() > 255)
throw m::error
{
"M_INVALID_PASSWORD", "The desired password is too long"
};
}

View file

@ -24,15 +24,188 @@ using namespace ircd;
struct room
:resource
{
static constexpr const auto base_url
{
"_matrix/client/r0/rooms/"
};
using resource::resource;
}
rooms_resource
{
"_matrix/client/r0/rooms/",
"Rooms (7.0)"
room::base_url, resource::opts
{
resource::DIRECTORY,
"Rooms (7.0)"
}
};
mapi::header IRCD_MODULE
{
"registers the resource 'client/rooms'"
};
resource::response
get_state(client &client,
const resource::request &request,
const string_view &params,
const m::room::state &state,
const m::event::where &query)
{
const auto count
{
state.count(query)
};
size_t j(0);
json::value ret[count];
state.for_each(query, [&count, &j, &ret]
(const auto &event)
{
if(j < count)
ret[j++] = event;
});
return resource::response
{
client, json::value
{
ret, j
}
};
}
resource::response
get_state(client &client,
const resource::request &request,
const string_view &params,
const m::room::state &state,
const string_view &type,
const string_view &state_key)
{
const m::event::where::equal query
{
{ "type", type },
{ "state_key", state_key },
};
return get_state(client, request, params, state, query);
}
resource::response
get_state(client &client,
const resource::request &request,
const string_view &params,
const m::room::state &state,
const string_view &type)
{
const m::event::where::equal query
{
{ "type", type }
};
return get_state(client, request, params, state, query);
}
resource::response
get_state(client &client,
const resource::request &request,
const string_view &params,
const m::room::id &room_id)
{
string_view token[4];
tokens(params, "/", token);
const string_view &type
{
token[2]
};
const string_view &state_key
{
token[3]
};
const m::room::state state
{
room_id
};
if(type && state_key)
return get_state(client, request, params, state, type, state_key);
if(type)
return get_state(client, request, params, state, type);
const m::event::where::noop query;
return get_state(client, request, params, state, query);
}
resource::response
get_context(client &client,
const resource::request &request,
const string_view &params,
const m::room::id &room_id)
{
const m::event::id &event_id
{
token(params, "/", 2)
};
const auto it
{
m::event::find(event_id)
};
if(!it)
throw http::error(http::NOT_FOUND, "event not found");
const auto event
{
json::string(*it)
};
return resource::response
{
client, json::members
{
{ "event", event }
}
};
}
resource::response
get_rooms(client &client, const resource::request &request)
{
const auto params
{
lstrip(request.head.path, room::base_url)
};
string_view token[2];
if(tokens(params, "/", token) != 2)
throw http::error(http::code::NOT_FOUND, "/rooms command required");
const m::room::id &room_id
{
token[0]
};
const string_view &cmd
{
token[1]
};
if(cmd == "context")
return get_context(client, request, params, room_id);
if(cmd == "state")
return get_state(client, request, params, room_id);
throw http::error(http::code::NOT_FOUND, "/rooms command not found");
}
resource::method method_get
{
rooms_resource, "GET", get_rooms
};

View file

@ -26,13 +26,27 @@ resource sync_resource
"_matrix/client/r0/sync",
R"(
Synchronise the client's state with the latest state on the server.
6.2. 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)
incremental deltas to the state, and to receive new messages.
)"
};
struct polldata
{
std::weak_ptr<ircd::client> client;
steady_point timeout;
};
std::deque<polldata>
polling
{};
ircd::ctx::dock
polldock
{};
resource::response
sync(client &client, const resource::request &request)
{
@ -72,12 +86,22 @@ sync(client &client, const resource::request &request)
request.get("set_presence", "offline")
};
const auto timeout
const milliseconds timeout
{
// 6.2.1 The maximum time to poll in milliseconds before returning this request.
request.get<time_t>("timeout", -1)
request.get<time_t>("timeout", 30 * 1000)
};
// A reference to the client is saved. We save a weak reference to still
// allow the client to disconnect.
polling.emplace_back(polldata
{
weak_from(client),
now<steady_point>() + timeout
});
// This handler returns no response. As long as this handler doesn't throw
// an exception IRCd will keep the client alive.
return {};
}
@ -89,7 +113,81 @@ resource::method get_sync
}
};
void worker();
ircd::context synchronizer_context
{
"synchronizer",
1_MiB,
&worker,
ircd::context::POST,
};
const auto on_unload{[]
{
synchronizer_context.interrupt();
synchronizer_context.join();
}};
mapi::header IRCD_MODULE
{
"registers the resource 'client/sync' to handle requests."
"registers the resource 'client/sync' to handle requests.",
nullptr,
on_unload
};
void
handle_event(const m::event &event,
const polldata &request)
try
{
const life_guard<client> client
{
request.client
};
resource::response
{
*client, json::members
{
{ "event", json::string(event) }
}
};
}
catch(const std::exception &e)
{
log::error("%s", e.what());
}
void
synchronize(const m::event &event)
{
if(polling.empty())
return;
const auto &request
{
polling.front()
};
handle_event(event, request);
polling.pop_front();
}
void
worker()
try
{
for(;; ctx::interruption_point())
{
const auto &event
{
m::event::inserted.wait()
};
synchronize(event);
}
}
catch(const ircd::ctx::interrupted &e)
{
ircd::log::debug("Synchronizer interrupted");
}

View file

@ -30,35 +30,58 @@ resource user_resource
}
};
namespace ircd
m::room filters_room
{
static string_view extract_user_id(const auto &path, const auto &suffix);
}
m::room::id{"!filters:cdc.z"}
};
ircd::string_view
ircd::extract_user_id(const auto &path,
const auto &suffix)
const m::room::events filters_room_events
{
// Extract the user_id from _matrix/client/r0/user/$user/filter
return lstrip(between(path, user_resource.path, suffix), '/');
}
filters_room
};
resource::response
get_filter(client &client, const resource::request &request)
try
{
const auto user_id
const m::user::id user_id
{
extract_user_id(request.head.path, "/filter")
token(request.head.path, '/', 4)
};
return resource::response
const auto filter_id
{
client,
{
{ }
}
token(request.head.path, '/', 6)
};
const m::event::where::equal query
{
{ "type", "ircd.filter" },
{ "sender", user_id },
{ "state_key", filter_id }
};
const auto result{[&client]
(const m::event &event)
{
const json::object &filter
{
json::at<m::name::content>(event)
};
resource::response
{
client, filter
};
return true;
}};
if(!filters_room_events.any(query, result))
throw m::NOT_FOUND("No matching filter with that ID");
// Response already made
return {};
}
catch(...)
{
@ -67,65 +90,88 @@ catch(...)
resource::method get
{
user_resource, "GET", get_filter
user_resource, "GET", get_filter,
{
resource::method::REQUIRES_AUTH
}
};
// (5.2) Uploads a new filter definition to the homeserver. Returns a filter ID that
// may be used in future requests to restrict which events are returned to the client.
resource::response
post_filter(client &client, const resource::request &request)
post_filter(client &client, const resource::request::object<const m::filter> &request)
try
{
const auto user_id
const auto &user_id
{
// (5.2) Required. The id of the user uploading the filter. The access
// token must be authorized to make requests for this user id.
extract_user_id(request.head.path, "/filter")
token(request.head.path, '/', 4)
};
const auto event_fields
const auto &event_fields
{
// (5.2) List of event fields to include. If this list is absent then all fields are
// included. The entries may include '.' charaters to indicate sub-fields. So
// ['content.body'] will include the 'body' field of the 'content' object. A literal '.'
// character in a field name may be escaped using a '\'. A server may include more
// fields than were requested.
request["event_fields"]
json::get<m::name::event_fields>(request)
};
const auto event_format
const auto &event_format
{
// (5.2) The format to use for events. 'client' will return the events in a format suitable
// for clients. 'federation' will return the raw event as receieved over federation.
// The default is 'client'. One of: ["client", "federation"]
request["event_format"]
json::get<m::name::event_format>(request)
};
const auto account_data
const auto &account_data
{
// (5.2) The user account data that isn't associated with rooms to include.
request["account_data"]
json::val<m::name::account_data>(request)
};
const auto room
const auto &room
{
// (5.2) Filters to be applied to room data.
request["room"]
json::val<m::name::room>(request)
};
const auto presence
const auto &state
{
json::val<m::name::state>(room)
};
std::cout << json::get<m::name::limit>(state) << std::endl;
const auto &presence
{
// (5.2) The presence updates to include.
request["presence"]
json::val<m::name::presence>(request)
};
std::cout << "do filter: " << request.content << std::endl;
const auto filter_id
{
"abc123"
};
json::iov event;
json::iov::push members[]
{
{ event, json::member { "type", "ircd.filter" }},
{ event, json::member { "state_key", filter_id }},
{ event, json::member { "sender", user_id }},
{ event, json::member { "content", request.body }}
};
filters_room.send(event);
return resource::response
{
client, http::CREATED,
{
{ "filter_id", "abc321" }
{ "filter_id", filter_id }
}
};
}
@ -136,7 +182,10 @@ catch(...)
resource::method post
{
user_resource, "POST", post_filter
user_resource, "POST", post_filter,
{
post.REQUIRES_AUTH
}
};
mapi::header IRCD_MODULE

View file

@ -38,10 +38,7 @@ get_turnserver(client &client, const resource::request &request)
{
return resource::response
{
client,
{
{ },
}
client, http::OK
};
}

View file

@ -278,10 +278,58 @@ const database::descriptor events_signatures_descriptor
}
};
const database::descriptor index_room_id_to_event_id
/// prefix transform for event_id suffixes
///
/// This transform expects a concatenation ending with an event_id which means
/// the prefix can be the same for multiple event_id's; therefor we can find
/// or iterate "event_id in X" where X is some key like a room_id
///
const ircd::db::prefix_transform event_id_in
{
"event_id in",
[](const string_view &key)
{
return key.find('$') != key.npos;
},
[](const string_view &key)
{
return rsplit(key, '$').first;
}
};
const database::descriptor event_id_in_sender
{
// name
"!room_id$event_id",
"event_id in sender",
// explanation
R"(### developer note:
key is "@sender$event_id"
the prefix transform is in effect. this column indexes events by
sender offering an iterable bound of the index prefixed by sender
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
},
// options
{},
// comparator
{},
// prefix transform
event_id_in,
};
const database::descriptor event_id_in_room_id
{
// name
"event_id in room_id",
// explanation
R"(### developer note:
@ -300,27 +348,99 @@ const database::descriptor index_room_id_to_event_id
// options
{},
// comparator - sorts from highest to lowest
ircd::db::reverse_cmp_string_view{},
// prefix transform
event_id_in,
};
/// prefix transform for room_id
///
/// This transform expects a concatenation ending with a room_id which means
/// the prefix can be the same for multiple room_id's; therefor we can find
/// or iterate "room_id in X" where X is some repeated prefix
///
const ircd::db::prefix_transform room_id_in
{
"room_id in",
[](const string_view &key)
{
return key.find('!') != key.npos;
},
[](const string_view &key)
{
return rsplit(key, '!').first;
}
};
const database::descriptor event_id_for_room_id_in_type
{
// name
"event_id for room_id in type",
// explanation
R"(### developer note:
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
},
// options
{},
// comparator
{},
// prefix transform
room_id_in,
};
/// prefix transform for type,state_key in room_id
///
/// This transform is special for concatenating room_id with type and state_key
/// in that order with prefix being the room_id (this may change to room_id+
/// type
///
const ircd::db::prefix_transform type_state_key_in_room_id
{
"type,state_key in room_id",
[](const string_view &key)
{
"!room_id$event_id"s,
[](const string_view &key)
{
return key.find('$') != key.npos;
},
[](const string_view &key)
{
return split(key, '$').first;
}
return key.find("..") != key.npos;
},
[](const string_view &key)
{
return split(key, "..").first;
}
};
// hooks
// {
const database::descriptor event_id_for_type_state_key_in_room_id
{
// name
"event_id for type,state_key in room_id",
// explanation
R"(### developer note:
// }
)",
// typing (key, value)
{
typeid(string_view), typeid(string_view)
},
// options
{},
// comparator
{},
// prefix transform
type_state_key_in_room_id
};
const database::description events_description
@ -337,7 +457,10 @@ const database::description events_description
events_prev_ids_descriptor,
events_unsigned_descriptor,
events_signatures_descriptor,
index_room_id_to_event_id,
event_id_in_sender,
event_id_in_room_id,
event_id_for_room_id_in_type,
event_id_for_type_state_key_in_room_id,
};
std::shared_ptr<database> events_database