mirror of
https://github.com/matrix-construct/construct
synced 2024-12-25 23:14:13 +01:00
ircd:Ⓜ️ Add user::tokens convenience interface; consolidate various direct room access.
This commit is contained in:
parent
f026110b15
commit
d052249573
16 changed files with 352 additions and 242 deletions
|
@ -123,7 +123,7 @@ struct ircd::m::device
|
|||
using closure_bool = std::function<bool (const string_view &)>;
|
||||
|
||||
// util
|
||||
static bool access_token_to_id(const string_view &token, const closure &); //nothrow
|
||||
static id::buf access_token_to_id(std::nothrow_t, const string_view &token);
|
||||
static id::buf access_token_to_id(const string_view &token);
|
||||
|
||||
// primary interface
|
||||
|
@ -145,3 +145,16 @@ struct ircd::m::device
|
|||
using super_type::tuple;
|
||||
using super_type::operator=;
|
||||
};
|
||||
|
||||
inline ircd::m::device::id::buf
|
||||
ircd::m::device::access_token_to_id(const string_view &token)
|
||||
{
|
||||
return m::user::tokens::device(token);
|
||||
}
|
||||
|
||||
inline ircd::m::device::id::buf
|
||||
ircd::m::device::access_token_to_id(std::nothrow_t,
|
||||
const string_view &token)
|
||||
{
|
||||
return m::user::tokens::device(std::nothrow, token);
|
||||
}
|
||||
|
|
35
include/ircd/m/user/tokens.h
Normal file
35
include/ircd/m/user/tokens.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
// The Construct
|
||||
//
|
||||
// Copyright (C) The Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2020 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. The
|
||||
// full license for this software is available in the LICENSE file.
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_M_USER_TOKENS_H
|
||||
|
||||
struct ircd::m::user::tokens
|
||||
{
|
||||
using closure = std::function<void (const event::idx &, const string_view &)>;
|
||||
using closure_bool = std::function<bool (const event::idx &, const string_view &)>;
|
||||
|
||||
static string_view generate(const mutable_buffer &out);
|
||||
static id::device::buf device(std::nothrow_t, const string_view &token);
|
||||
static id::device::buf device(const string_view &token);
|
||||
static id::user::buf get(std::nothrow_t, const string_view &token);
|
||||
static id::user::buf get(const string_view &token);
|
||||
|
||||
m::user user;
|
||||
|
||||
bool for_each(const closure_bool &) const;
|
||||
bool check(const string_view &token) const;
|
||||
bool del(const string_view &token, const string_view &reason) const;
|
||||
size_t del(const string_view &reason) const;
|
||||
|
||||
tokens(const m::user &user)
|
||||
:user{user}
|
||||
{}
|
||||
};
|
|
@ -45,6 +45,7 @@ struct ircd::m::user
|
|||
struct pushrules;
|
||||
struct pushers;
|
||||
struct notifications;
|
||||
struct tokens;
|
||||
|
||||
using id = m::id::user;
|
||||
using closure = std::function<void (const user &)>;
|
||||
|
@ -54,9 +55,6 @@ struct ircd::m::user
|
|||
|
||||
operator const id &() const;
|
||||
|
||||
static string_view gen_access_token(const mutable_buffer &out);
|
||||
static id::device::buf get_device_from_access_token(const string_view &token);
|
||||
|
||||
id::room room_id(const mutable_buffer &) const;
|
||||
id::room::buf room_id() const;
|
||||
|
||||
|
@ -96,3 +94,4 @@ const
|
|||
#include "pushrules.h"
|
||||
#include "pushers.h"
|
||||
#include "notifications.h"
|
||||
#include "tokens.h"
|
||||
|
|
|
@ -110,6 +110,7 @@ libircd_matrix_la_SOURCES += user_account_data.cc
|
|||
libircd_matrix_la_SOURCES += user_events.cc
|
||||
libircd_matrix_la_SOURCES += user_rooms.cc
|
||||
libircd_matrix_la_SOURCES += user_filter.cc
|
||||
libircd_matrix_la_SOURCES += user_ignores.cc
|
||||
libircd_matrix_la_SOURCES += user_mitsein.cc
|
||||
libircd_matrix_la_SOURCES += user_notifications.cc
|
||||
libircd_matrix_la_SOURCES += user_profile.cc
|
||||
|
@ -118,7 +119,7 @@ libircd_matrix_la_SOURCES += user_pushrules.cc
|
|||
libircd_matrix_la_SOURCES += user_register.cc
|
||||
libircd_matrix_la_SOURCES += user_room_account_data.cc
|
||||
libircd_matrix_la_SOURCES += user_room_tags.cc
|
||||
libircd_matrix_la_SOURCES += user_ignores.cc
|
||||
libircd_matrix_la_SOURCES += user_tokens.cc
|
||||
libircd_matrix_la_SOURCES += breadcrumb_rooms.cc
|
||||
libircd_matrix_la_SOURCES += device.cc
|
||||
libircd_matrix_la_SOURCES += display_name.cc
|
||||
|
|
|
@ -335,63 +335,6 @@ ircd::m::device::for_each(const m::user &user,
|
|||
});
|
||||
}
|
||||
|
||||
ircd::m::device::id::buf
|
||||
ircd::m::device::access_token_to_id(const string_view &token)
|
||||
{
|
||||
id::buf ret;
|
||||
access_token_to_id(token, [&ret]
|
||||
(const string_view &device_id)
|
||||
{
|
||||
ret = device_id;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::device::access_token_to_id(const string_view &token,
|
||||
const closure &closure)
|
||||
{
|
||||
const m::room::id::buf tokens
|
||||
{
|
||||
"tokens", origin(my())
|
||||
};
|
||||
|
||||
const m::room::state &state
|
||||
{
|
||||
tokens
|
||||
};
|
||||
|
||||
const m::event::idx &event_idx
|
||||
{
|
||||
state.get(std::nothrow, "ircd.access_token", token)
|
||||
};
|
||||
|
||||
bool ret{false};
|
||||
const auto device_id{[&closure, &ret]
|
||||
(const json::object &content)
|
||||
{
|
||||
const json::string &device_id
|
||||
{
|
||||
content["device_id"]
|
||||
};
|
||||
|
||||
if(likely(device_id))
|
||||
{
|
||||
closure(device_id);
|
||||
ret = true;
|
||||
}
|
||||
}};
|
||||
|
||||
if(!event_idx)
|
||||
return ret;
|
||||
|
||||
if(!m::get(std::nothrow, event_idx, "content", device_id))
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::device_list_update::send(json::iov &content)
|
||||
try
|
||||
|
|
|
@ -118,51 +118,6 @@ const
|
|||
};
|
||||
}
|
||||
|
||||
ircd::m::device::id::buf
|
||||
ircd::m::user::get_device_from_access_token(const string_view &token)
|
||||
{
|
||||
const m::room::id::buf tokens_room_id
|
||||
{
|
||||
"tokens", origin(my())
|
||||
};
|
||||
|
||||
const m::room tokens
|
||||
{
|
||||
tokens_room_id
|
||||
};
|
||||
|
||||
const event::idx event_idx
|
||||
{
|
||||
tokens.get("ircd.access_token", token)
|
||||
};
|
||||
|
||||
device::id::buf ret;
|
||||
m::get(event_idx, "content", [&ret]
|
||||
(const json::object &content)
|
||||
{
|
||||
ret = unquote(content.at("device_id"));
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::user::gen_access_token(const mutable_buffer &buf)
|
||||
{
|
||||
static const size_t token_max{32};
|
||||
static const auto &token_dict
|
||||
{
|
||||
rand::dict::alpha
|
||||
};
|
||||
|
||||
const mutable_buffer out
|
||||
{
|
||||
data(buf), std::min(token_max, size(buf))
|
||||
};
|
||||
|
||||
return rand::string(token_dict, out);
|
||||
}
|
||||
|
||||
ircd::m::event::id::buf
|
||||
ircd::m::user::activate()
|
||||
{
|
||||
|
|
|
@ -135,7 +135,7 @@ const
|
|||
const string_view access_token
|
||||
{
|
||||
!json::get<"inhibit_login"_>(*this)?
|
||||
m::user::gen_access_token(access_token_buf):
|
||||
m::user::tokens::generate(access_token_buf):
|
||||
string_view{}
|
||||
};
|
||||
|
||||
|
|
256
matrix/user_tokens.cc
Normal file
256
matrix/user_tokens.cc
Normal file
|
@ -0,0 +1,256 @@
|
|||
// The Construct
|
||||
//
|
||||
// Copyright (C) The Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2020 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. The
|
||||
// full license for this software is available in the LICENSE file.
|
||||
|
||||
size_t
|
||||
ircd::m::user::tokens::del(const string_view &reason)
|
||||
const
|
||||
{
|
||||
size_t ret(0);
|
||||
for_each([this, &ret, &reason]
|
||||
(const event::idx &event_idx, const string_view &token)
|
||||
{
|
||||
ret += del(token, reason);
|
||||
return true;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::user::tokens::del(const string_view &token,
|
||||
const string_view &reason)
|
||||
const
|
||||
{
|
||||
const m::room::id::buf tokens_room_id
|
||||
{
|
||||
"tokens", origin(my())
|
||||
};
|
||||
|
||||
const m::room tokens
|
||||
{
|
||||
tokens_room_id
|
||||
};
|
||||
|
||||
const auto event_idx
|
||||
{
|
||||
tokens.get(std::nothrow, "ircd.access_token", token)
|
||||
};
|
||||
|
||||
const auto match
|
||||
{
|
||||
[this](const string_view &sender)
|
||||
{
|
||||
return sender == this->user.user_id;
|
||||
}
|
||||
};
|
||||
|
||||
if(unlikely(!m::query(std::nothrow, event_idx, "sender", match)))
|
||||
return false;
|
||||
|
||||
const auto event_id
|
||||
{
|
||||
m::event_id(std::nothrow, event_idx)
|
||||
};
|
||||
|
||||
if(unlikely(!event_id))
|
||||
return false;
|
||||
|
||||
const auto redact_id
|
||||
{
|
||||
m::redact(tokens, user.user_id, event_id, reason)
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::user::tokens::check(const string_view &token)
|
||||
const
|
||||
{
|
||||
const m::room::id::buf tokens_room_id
|
||||
{
|
||||
"tokens", origin(my())
|
||||
};
|
||||
|
||||
const m::room tokens
|
||||
{
|
||||
tokens_room_id
|
||||
};
|
||||
|
||||
const auto event_idx
|
||||
{
|
||||
tokens.get(std::nothrow, "ircd.access_token", token)
|
||||
};
|
||||
|
||||
return event_idx && m::query(std::nothrow, event_idx, "sender", [this]
|
||||
(const string_view &sender)
|
||||
{
|
||||
return sender == this->user.user_id;
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::user::tokens::for_each(const closure_bool &closure)
|
||||
const
|
||||
{
|
||||
const m::room::id::buf tokens_room_id
|
||||
{
|
||||
"tokens", origin(my())
|
||||
};
|
||||
|
||||
const m::room tokens
|
||||
{
|
||||
tokens_room_id
|
||||
};
|
||||
|
||||
const m::room::state state
|
||||
{
|
||||
tokens
|
||||
};
|
||||
|
||||
return state.for_each("ircd.access_token", [this, &closure]
|
||||
(const auto &type, const auto &state_key, const auto &event_idx)
|
||||
{
|
||||
const auto match
|
||||
{
|
||||
[this](const string_view &sender)
|
||||
{
|
||||
return sender == this->user.user_id;
|
||||
}
|
||||
};
|
||||
|
||||
if(!m::query(std::nothrow, event_idx, "sender", match))
|
||||
return true;
|
||||
|
||||
if(!closure(event_idx, state_key))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
ircd::m::user::id::buf
|
||||
ircd::m::user::tokens::get(const string_view &token)
|
||||
{
|
||||
const auto ret
|
||||
{
|
||||
get(std::nothrow, token)
|
||||
};
|
||||
|
||||
if(!ret)
|
||||
throw m::error
|
||||
{
|
||||
http::UNAUTHORIZED, "M_UNKNOWN_TOKEN",
|
||||
"Credentials for this method are required but invalid."
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ircd::m::user::id::buf
|
||||
ircd::m::user::tokens::get(std::nothrow_t,
|
||||
const string_view &token)
|
||||
{
|
||||
const m::room::id::buf tokens_room_id
|
||||
{
|
||||
"tokens", origin(my())
|
||||
};
|
||||
|
||||
const m::room tokens
|
||||
{
|
||||
tokens_room_id
|
||||
};
|
||||
|
||||
const event::idx event_idx
|
||||
{
|
||||
tokens.get(std::nothrow, "ircd.access_token", token)
|
||||
};
|
||||
|
||||
m::user::id::buf ret;
|
||||
m::get(std::nothrow, event_idx, "sender", [&ret]
|
||||
(const string_view &sender)
|
||||
{
|
||||
ret = sender;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ircd::m::device::id::buf
|
||||
ircd::m::user::tokens::device(const string_view &token)
|
||||
{
|
||||
const auto ret
|
||||
{
|
||||
device(std::nothrow, token)
|
||||
};
|
||||
|
||||
if(unlikely(!ret))
|
||||
throw m::NOT_FOUND
|
||||
{
|
||||
"No device for this access_token"
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ircd::m::device::id::buf
|
||||
ircd::m::user::tokens::device(std::nothrow_t,
|
||||
const string_view &token)
|
||||
{
|
||||
const m::room::id::buf tokens_room_id
|
||||
{
|
||||
"tokens", origin(my())
|
||||
};
|
||||
|
||||
const m::room tokens
|
||||
{
|
||||
tokens_room_id
|
||||
};
|
||||
|
||||
const event::idx event_idx
|
||||
{
|
||||
tokens.get(std::nothrow, "ircd.access_token", token)
|
||||
};
|
||||
|
||||
device::id::buf ret;
|
||||
m::get(std::nothrow, event_idx, "content", [&ret]
|
||||
(const json::object &content)
|
||||
{
|
||||
const json::string &device_id
|
||||
{
|
||||
content.at("device_id")
|
||||
};
|
||||
|
||||
ret = device_id;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::user::tokens::generate(const mutable_buffer &buf)
|
||||
{
|
||||
static const size_t token_max
|
||||
{
|
||||
32
|
||||
};
|
||||
|
||||
static const auto &token_dict
|
||||
{
|
||||
rand::dict::alpha
|
||||
};
|
||||
|
||||
const mutable_buffer out
|
||||
{
|
||||
data(buf), std::min(token_max, size(buf))
|
||||
};
|
||||
|
||||
return rand::string(token_dict, out);
|
||||
}
|
|
@ -66,7 +66,7 @@ ircd::m::post_keys_device_signing_upload(client &client,
|
|||
|
||||
const m::device::id::buf device_id
|
||||
{
|
||||
m::user::get_device_from_access_token(request.access_token)
|
||||
m::user::tokens::device(request.access_token)
|
||||
};
|
||||
|
||||
const m::user::room user_room
|
||||
|
|
|
@ -45,7 +45,7 @@ ircd::m::post_keys_signatures_upload(client &client,
|
|||
{
|
||||
const m::device::id::buf device_id
|
||||
{
|
||||
m::user::get_device_from_access_token(request.access_token)
|
||||
m::user::tokens::device(request.access_token)
|
||||
};
|
||||
|
||||
const m::user::room user_room
|
||||
|
|
|
@ -86,7 +86,7 @@ post__keys_upload(client &client,
|
|||
|
||||
const m::device::id::buf device_id
|
||||
{
|
||||
m::user::get_device_from_access_token(request.access_token)
|
||||
m::user::tokens::device(request.access_token)
|
||||
};
|
||||
|
||||
if(!empty(device_keys))
|
||||
|
|
|
@ -101,7 +101,7 @@ post__login_password(client &client,
|
|||
char access_token_buf[32];
|
||||
const string_view access_token
|
||||
{
|
||||
m::user::gen_access_token(access_token_buf)
|
||||
m::user::tokens::generate(access_token_buf)
|
||||
};
|
||||
|
||||
char remote_buf[96];
|
||||
|
|
|
@ -10,10 +10,6 @@
|
|||
|
||||
using namespace ircd;
|
||||
|
||||
static bool
|
||||
do_logout(const m::user::id &user_id,
|
||||
const m::event::idx &token_event_idx);
|
||||
|
||||
static m::resource::response
|
||||
post__logout(client &,
|
||||
const m::resource::request &);
|
||||
|
@ -76,23 +72,16 @@ post__logout(client &client,
|
|||
request.access_token
|
||||
};
|
||||
|
||||
const m::room::id::buf tokens_room_id
|
||||
const m::user::tokens tokens
|
||||
{
|
||||
"tokens", origin(m::my())
|
||||
request.user_id
|
||||
};
|
||||
|
||||
const m::room::state tokens
|
||||
const bool deleted
|
||||
{
|
||||
tokens_room_id
|
||||
tokens.del(access_token, "client logout")
|
||||
};
|
||||
|
||||
const auto token_event_idx
|
||||
{
|
||||
tokens.get("ircd.access_token", access_token)
|
||||
};
|
||||
|
||||
do_logout(request.user_id, token_event_idx);
|
||||
|
||||
return m::resource::response
|
||||
{
|
||||
client, http::OK
|
||||
|
@ -103,79 +92,21 @@ m::resource::response
|
|||
post__logout_all(client &client,
|
||||
const m::resource::request &request)
|
||||
{
|
||||
const m::room::id::buf tokens_room_id
|
||||
const m::user::tokens tokens
|
||||
{
|
||||
"tokens", origin(m::my())
|
||||
request.user_id
|
||||
};
|
||||
|
||||
const m::room::state tokens
|
||||
const size_t invalidations
|
||||
{
|
||||
tokens_room_id
|
||||
tokens.del("client logout all")
|
||||
};
|
||||
|
||||
long count(0);
|
||||
tokens.for_each("ircd.access_token", m::event::closure_idx{[&request, &count]
|
||||
(const m::event::idx &event_idx)
|
||||
{
|
||||
bool match(false);
|
||||
m::get(std::nothrow, event_idx, "sender", [&request, &match]
|
||||
(const string_view &sender)
|
||||
{
|
||||
match = request.user_id == sender;
|
||||
});
|
||||
|
||||
if(match)
|
||||
{
|
||||
do_logout(request.user_id, event_idx);
|
||||
++count;
|
||||
}
|
||||
}});
|
||||
|
||||
return m::resource::response
|
||||
{
|
||||
client, json::members
|
||||
{
|
||||
{ "invalidations", count }
|
||||
{ "invalidations", long(invalidations) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool
|
||||
do_logout(const m::user::id &user_id,
|
||||
const m::event::idx &token_event_idx)
|
||||
try
|
||||
{
|
||||
static const string_view reason
|
||||
{
|
||||
"logout"
|
||||
};
|
||||
|
||||
const auto token_event_id
|
||||
{
|
||||
m::event_id(token_event_idx)
|
||||
};
|
||||
|
||||
const m::room::id::buf tokens_room_id
|
||||
{
|
||||
"tokens", origin(m::my())
|
||||
};
|
||||
|
||||
const auto redaction_event_id
|
||||
{
|
||||
m::redact(tokens_room_id, user_id, token_event_id, reason)
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log::error
|
||||
{
|
||||
m::log, "Error logging out user '%s' token event_idx:%lu :%s",
|
||||
string_view{user_id},
|
||||
token_event_idx,
|
||||
e.what()
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -165,10 +165,10 @@ post__register_guest(client &client,
|
|||
m::generate, my_host()
|
||||
};
|
||||
|
||||
char access_token_buf[32];
|
||||
char access_token_buf[64];
|
||||
const string_view access_token
|
||||
{
|
||||
m::user::gen_access_token(access_token_buf)
|
||||
m::user::tokens::generate(access_token_buf)
|
||||
};
|
||||
|
||||
return resource::response
|
||||
|
|
|
@ -80,7 +80,7 @@ ircd::m::post_room_keys_version(client &client,
|
|||
|
||||
const m::device::id::buf device_id
|
||||
{
|
||||
m::user::get_device_from_access_token(request.access_token)
|
||||
m::user::tokens::device(request.access_token)
|
||||
};
|
||||
|
||||
const m::user::room user_room
|
||||
|
|
|
@ -11547,42 +11547,28 @@ console_cmd__user__tokens(opt &out, const string_view &line)
|
|||
param["clear"] == "clear"
|
||||
};
|
||||
|
||||
const m::room::id::buf tokens_room
|
||||
const m::user::tokens tokens
|
||||
{
|
||||
"tokens", origin(m::my())
|
||||
user
|
||||
};
|
||||
|
||||
const m::room::state &tokens
|
||||
if(clear)
|
||||
{
|
||||
tokens_room
|
||||
};
|
||||
|
||||
tokens.for_each("ircd.access_token", m::event::closure_idx{[&out, &user, &clear]
|
||||
(const m::event::idx &event_idx)
|
||||
{
|
||||
bool match(false);
|
||||
m::get(std::nothrow, event_idx, "sender", [&match, &user]
|
||||
(const string_view &sender)
|
||||
const size_t count
|
||||
{
|
||||
match = sender == user.user_id;
|
||||
});
|
||||
|
||||
if(!match)
|
||||
return;
|
||||
|
||||
const m::event::fetch event
|
||||
{
|
||||
event_idx
|
||||
tokens.del("Invalidated by administrator console.")
|
||||
};
|
||||
|
||||
const string_view &token
|
||||
{
|
||||
at<"state_key"_>(event)
|
||||
};
|
||||
out << "Invalidated " << count << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
tokens.for_each([&out]
|
||||
(const m::event::idx &event_idx, const string_view &token)
|
||||
{
|
||||
const milliseconds ost
|
||||
{
|
||||
at<"origin_server_ts"_>(event)
|
||||
m::get<long>(event_idx, "origin_server_ts")
|
||||
};
|
||||
|
||||
const milliseconds now
|
||||
|
@ -11590,31 +11576,22 @@ console_cmd__user__tokens(opt &out, const string_view &line)
|
|||
time<milliseconds>()
|
||||
};
|
||||
|
||||
out << token
|
||||
<< " "
|
||||
<< ost
|
||||
<< " "
|
||||
<< pretty(now - ost) << " ago"
|
||||
<< " "
|
||||
<< string_view{event.event_id};
|
||||
|
||||
if(clear)
|
||||
const auto event_id
|
||||
{
|
||||
const m::room::id::buf tokens_room
|
||||
{
|
||||
"tokens", origin(m::my())
|
||||
};
|
||||
m::event_id(std::nothrow, event_idx)
|
||||
};
|
||||
|
||||
const auto eid
|
||||
{
|
||||
m::redact(tokens_room, user.user_id, event.event_id, "cleared")
|
||||
};
|
||||
|
||||
out << " - cleared by " << eid;
|
||||
}
|
||||
|
||||
out << std::endl;
|
||||
}});
|
||||
out
|
||||
<< token
|
||||
<< " "
|
||||
<< ost
|
||||
<< " "
|
||||
<< pretty(now - ost) << " ago"
|
||||
<< " "
|
||||
<< string_view{event_id}
|
||||
<< std::endl;
|
||||
return true;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue