mirror of
https://gitlab.com/niansa/asbots.git
synced 2025-03-06 20:48:25 +01:00
First basic NickServ implementation has arrived!!!
This commit is contained in:
parent
def0dc6715
commit
773369dab7
8 changed files with 108 additions and 37 deletions
|
@ -20,7 +20,8 @@ find_package(PkgConfig REQUIRED)
|
|||
pkg_check_modules(libasync-uv REQUIRED IMPORTED_TARGET libasync-uv)
|
||||
pkg_check_modules(fmt REQUIRED IMPORTED_TARGET fmt)
|
||||
pkg_check_modules(sqlite3 REQUIRED IMPORTED_TARGET sqlite3)
|
||||
pkg_check_modules(cryptopp REQUIRED IMPORTED_TARGET cryptopp)
|
||||
|
||||
target_link_libraries(asbots PRIVATE PkgConfig::libasync-uv PkgConfig::fmt PkgConfig::sqlite3)
|
||||
target_link_libraries(asbots PRIVATE PkgConfig::libasync-uv PkgConfig::fmt PkgConfig::sqlite3 PkgConfig::cryptopp)
|
||||
|
||||
configure_file(config.inil config.inil COPYONLY)
|
||||
|
|
|
@ -14,6 +14,6 @@ description: A test service!!
|
|||
uid: 23X
|
||||
end
|
||||
|
||||
[testservice]
|
||||
channel: #testservice
|
||||
[NickServ]
|
||||
salt: changeme!!!
|
||||
end
|
||||
|
|
|
@ -425,7 +425,7 @@ async::result<void> Instance::process(Event event) {
|
|||
// Update login name
|
||||
if (event.args.size() > 3) {
|
||||
auto loginName = event.args[3];
|
||||
if (loginName == "*" || loginName == "0") {
|
||||
if (loginName.empty() || loginName == "*" || loginName == "0") {
|
||||
res->get()->loginName.reset();
|
||||
} else {
|
||||
res->get()->loginName = event.args[3];
|
||||
|
|
|
@ -156,10 +156,10 @@ struct User {
|
|||
};
|
||||
}
|
||||
|
||||
Event get_encap_su(std::string_view login_name) {
|
||||
Event get_encap_su(std::string_view login_name, const SUID& my_sid) {
|
||||
//:<my_sid> ENCAP * SU <user_uid> <new_account_name>
|
||||
return {
|
||||
.sender = server,
|
||||
.sender = my_sid,
|
||||
.name = "ENCAP",
|
||||
.raw_args = fmt::format("* SU {} {}", uid.str(), login_name)
|
||||
};
|
||||
|
|
|
@ -59,11 +59,17 @@ async::result<void> ServiceBase::on_direct_privmsg(std::string_view msg, u_User&
|
|||
co_await i->send_event(user.get_notice("Error: Command not found", author->uid));
|
||||
} else {
|
||||
split.erase(split.begin());
|
||||
auto& [fnc, usage, minArgs] = res->second;
|
||||
if (split.size() < minArgs) {
|
||||
co_await i->send_event(user.get_notice("Error: Usage: {}"_format(usage), author->uid));
|
||||
auto& [fnc, usage, minArgs, requiresAuth] = res->second;
|
||||
if (requiresAuth && !author->loginName.has_value()) {
|
||||
co_await i->send_event(user.get_notice("Error: You need to identify first", author->uid));
|
||||
} else if (split.size() < minArgs) {
|
||||
co_await i->send_event(user.get_notice("Usage: {}"_format(usage), author->uid));
|
||||
} else {
|
||||
co_await fnc(author, split);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async::result<void> ServiceBase::on_event(const Event& event) {
|
||||
co_return;
|
||||
}
|
||||
|
|
|
@ -38,10 +38,10 @@ public:
|
|||
UUID uuid;
|
||||
Instance *i;
|
||||
bool ready = false;
|
||||
std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (u_User& sender, const std::vector<std::string_view>& args)>, std::string_view, size_t>> commands;
|
||||
std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (u_User& sender, const std::vector<std::string_view>& args)>, std::string_view, size_t, bool>> commands;
|
||||
|
||||
virtual async::result<void> intitialize() = 0;
|
||||
virtual async::result<void> on_event(const Event& event) = 0;
|
||||
virtual async::result<void> on_event(const Event& event);
|
||||
virtual async::result<void> on_direct_privmsg(std::string_view msg, u_User& author);
|
||||
|
||||
async::result<void> mark_ready(const User& user);
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
#include "../instance.hpp"
|
||||
|
||||
#include <async/result.hpp>
|
||||
#include <cryptopp/hex.h>
|
||||
#include <cryptopp/sha.h>
|
||||
#include <cryptopp/pwdbased.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
@ -26,35 +29,95 @@ async::result<void> NickServ::intitialize() {
|
|||
make_table("users",
|
||||
make_column("id", &Account::id, autoincrement(), primary_key()),
|
||||
make_column("name", &Account::name),
|
||||
make_column("email", &Account::email),
|
||||
make_column("password_hash", &Account::password_hash))
|
||||
));
|
||||
storage->sync_schema();
|
||||
|
||||
commands = {
|
||||
{"test", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> {
|
||||
co_await i->send_event(user.get_notice("Hello world! Works", sender->uid));
|
||||
}, "", 0}
|
||||
},
|
||||
{"identify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> {
|
||||
// Get user from storage
|
||||
auto accounts_found = storage->get_all<Account>(where(c(&Account::name) == sender->nick));
|
||||
if (accounts_found.empty()) {
|
||||
co_await i->send_event(user.get_notice("Error: Your nick is not registered!", sender->uid));
|
||||
co_return;
|
||||
}
|
||||
auto& account = accounts_found[0];
|
||||
// Check password
|
||||
if (account.password_hash != args[0]) {
|
||||
co_await i->send_event(user.get_notice("Error: Invalid password!", sender->uid));
|
||||
co_return;
|
||||
}
|
||||
// Login
|
||||
co_await i->send_event(sender->get_encap_su(account.name));
|
||||
}, "<password>", 1}
|
||||
}
|
||||
// Check that user is not already identified
|
||||
if (sender->loginName.has_value()) {
|
||||
co_await i->send_event(user.get_notice("Error: You've already been identified!", sender->uid));
|
||||
co_return;
|
||||
}
|
||||
// Get account name and password to use
|
||||
std::string_view accountName, accountPwd;
|
||||
if (args.size() > 1) {
|
||||
accountName = args[0];
|
||||
accountPwd = args[1];
|
||||
} else {
|
||||
accountName = sender->nick;
|
||||
accountPwd = args[0];
|
||||
}
|
||||
// Get account from storage
|
||||
auto accounts_found = storage->get_all<Account>(where(c(&Account::name) == std::string(accountName)));
|
||||
if (accounts_found.empty()) {
|
||||
co_await i->send_event(user.get_notice("Error: '"+std::string(accountName)+"' is not registered!", sender->uid));
|
||||
co_return;
|
||||
}
|
||||
auto& account = accounts_found[0];
|
||||
// Check password
|
||||
if (account.password_hash != pwdHash(accountPwd)) {
|
||||
co_await i->send_event(user.get_notice("Error: Invalid password!", sender->uid));
|
||||
co_return;
|
||||
}
|
||||
// Login
|
||||
co_await i->send_event(sender->get_encap_su(account.name, i->config.server.uid));
|
||||
// Report success
|
||||
co_await i->send_event(user.get_notice("You have been identified!", sender->uid));
|
||||
}, "[username] <password>", 1, false}
|
||||
},
|
||||
{"deidentify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> {
|
||||
co_await i->send_event(sender->get_encap_su("", i->config.server.uid));
|
||||
co_await i->send_event(user.get_notice("You have been deidentified!", sender->uid));
|
||||
}, "", 0, true}
|
||||
},
|
||||
{"register", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> {
|
||||
// Check args
|
||||
if (args.size() != 2) {
|
||||
co_await i->send_event(user.get_notice("Error: Passwords may not contain spaces!", sender->uid));
|
||||
co_return;
|
||||
}
|
||||
// Check that user does not exist already
|
||||
{
|
||||
auto accounts_found = storage->get_all<Account>(where(c(&Account::name) == sender->nick));
|
||||
if (!accounts_found.empty()) {
|
||||
co_await i->send_event(user.get_notice("Error: This nick has already been registered!", sender->uid));
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
// Insert new account into database
|
||||
storage->insert(Account{
|
||||
.name = sender->nick,
|
||||
.email = std::string(args[1]),
|
||||
.password_hash = pwdHash(args[0])
|
||||
});
|
||||
// Report success
|
||||
co_await i->send_event(user.get_notice("Your nickname has been registered!", sender->uid));
|
||||
}, "<password> <email>", 2, false}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async::result<void> NickServ::on_event(const Event& event) {
|
||||
co_return;
|
||||
std::string NickServ::pwdHash(std::string_view pwd) {
|
||||
using namespace CryptoPP;
|
||||
|
||||
auto salt = getConfig("NickServ", "salt").value_or("defaultsalt");
|
||||
|
||||
static_assert(sizeof(pwd[0]) == 1, "std::string_view character width must be 1 for this to work... You might have a bad STL implementation.");
|
||||
|
||||
int c = 1;
|
||||
byte derived[20];
|
||||
|
||||
PKCS5_PBKDF2_HMAC<SHA1> pbkdf2;
|
||||
pbkdf2.DeriveKey(derived, sizeof(derived), 0, reinterpret_cast<const byte*>(pwd.data()), pwd.size(), reinterpret_cast<const byte*>(salt.data()), salt.size(), c);
|
||||
|
||||
std::string fres;
|
||||
HexEncoder encoder(new StringSink(fres));
|
||||
|
||||
encoder.Put(derived, sizeof(derived));
|
||||
encoder.MessageEnd();
|
||||
|
||||
return fres;
|
||||
}
|
||||
|
|
|
@ -29,11 +29,11 @@
|
|||
|
||||
|
||||
struct Account {
|
||||
size_t id;
|
||||
std::string name, password_hash;
|
||||
int id = -1;
|
||||
std::string name, email, password_hash;
|
||||
};
|
||||
|
||||
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<Account, sqlite_orm::internal::column_t<Account, long unsigned int, const long unsigned int& (Account::*)() const, void (Account::*)(long unsigned int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
|
||||
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<Account, sqlite_orm::internal::column_t<Account, int, const int& (Account::*)() const, void (Account::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
|
||||
|
||||
class NickServ : public ServiceBase {
|
||||
std::unique_ptr<Storage> storage;
|
||||
|
@ -41,6 +41,7 @@ class NickServ : public ServiceBase {
|
|||
User user;
|
||||
|
||||
virtual async::result<void> intitialize() override;
|
||||
virtual async::result<void> on_event(const Event& event) override;
|
||||
|
||||
std::string pwdHash(std::string_view pwd);
|
||||
};
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue