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(libasync-uv REQUIRED IMPORTED_TARGET libasync-uv)
|
||||||
pkg_check_modules(fmt REQUIRED IMPORTED_TARGET fmt)
|
pkg_check_modules(fmt REQUIRED IMPORTED_TARGET fmt)
|
||||||
pkg_check_modules(sqlite3 REQUIRED IMPORTED_TARGET sqlite3)
|
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)
|
configure_file(config.inil config.inil COPYONLY)
|
||||||
|
|
|
@ -14,6 +14,6 @@ description: A test service!!
|
||||||
uid: 23X
|
uid: 23X
|
||||||
end
|
end
|
||||||
|
|
||||||
[testservice]
|
[NickServ]
|
||||||
channel: #testservice
|
salt: changeme!!!
|
||||||
end
|
end
|
||||||
|
|
|
@ -425,7 +425,7 @@ async::result<void> Instance::process(Event event) {
|
||||||
// Update login name
|
// Update login name
|
||||||
if (event.args.size() > 3) {
|
if (event.args.size() > 3) {
|
||||||
auto loginName = event.args[3];
|
auto loginName = event.args[3];
|
||||||
if (loginName == "*" || loginName == "0") {
|
if (loginName.empty() || loginName == "*" || loginName == "0") {
|
||||||
res->get()->loginName.reset();
|
res->get()->loginName.reset();
|
||||||
} else {
|
} else {
|
||||||
res->get()->loginName = event.args[3];
|
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>
|
//:<my_sid> ENCAP * SU <user_uid> <new_account_name>
|
||||||
return {
|
return {
|
||||||
.sender = server,
|
.sender = my_sid,
|
||||||
.name = "ENCAP",
|
.name = "ENCAP",
|
||||||
.raw_args = fmt::format("* SU {} {}", uid.str(), login_name)
|
.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));
|
co_await i->send_event(user.get_notice("Error: Command not found", author->uid));
|
||||||
} else {
|
} else {
|
||||||
split.erase(split.begin());
|
split.erase(split.begin());
|
||||||
auto& [fnc, usage, minArgs] = res->second;
|
auto& [fnc, usage, minArgs, requiresAuth] = res->second;
|
||||||
if (split.size() < minArgs) {
|
if (requiresAuth && !author->loginName.has_value()) {
|
||||||
co_await i->send_event(user.get_notice("Error: Usage: {}"_format(usage), author->uid));
|
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 {
|
} else {
|
||||||
co_await fnc(author, split);
|
co_await fnc(author, split);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async::result<void> ServiceBase::on_event(const Event& event) {
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
|
@ -38,10 +38,10 @@ public:
|
||||||
UUID uuid;
|
UUID uuid;
|
||||||
Instance *i;
|
Instance *i;
|
||||||
bool ready = false;
|
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> 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);
|
virtual async::result<void> on_direct_privmsg(std::string_view msg, u_User& author);
|
||||||
|
|
||||||
async::result<void> mark_ready(const User& user);
|
async::result<void> mark_ready(const User& user);
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
#include "../instance.hpp"
|
#include "../instance.hpp"
|
||||||
|
|
||||||
#include <async/result.hpp>
|
#include <async/result.hpp>
|
||||||
|
#include <cryptopp/hex.h>
|
||||||
|
#include <cryptopp/sha.h>
|
||||||
|
#include <cryptopp/pwdbased.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -26,35 +29,95 @@ async::result<void> NickServ::intitialize() {
|
||||||
make_table("users",
|
make_table("users",
|
||||||
make_column("id", &Account::id, autoincrement(), primary_key()),
|
make_column("id", &Account::id, autoincrement(), primary_key()),
|
||||||
make_column("name", &Account::name),
|
make_column("name", &Account::name),
|
||||||
|
make_column("email", &Account::email),
|
||||||
make_column("password_hash", &Account::password_hash))
|
make_column("password_hash", &Account::password_hash))
|
||||||
));
|
));
|
||||||
storage->sync_schema();
|
storage->sync_schema();
|
||||||
|
|
||||||
commands = {
|
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> {
|
{"identify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> {
|
||||||
// Get user from storage
|
// Check that user is not already identified
|
||||||
auto accounts_found = storage->get_all<Account>(where(c(&Account::name) == sender->nick));
|
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()) {
|
if (accounts_found.empty()) {
|
||||||
co_await i->send_event(user.get_notice("Error: Your nick is not registered!", sender->uid));
|
co_await i->send_event(user.get_notice("Error: '"+std::string(accountName)+"' is not registered!", sender->uid));
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
auto& account = accounts_found[0];
|
auto& account = accounts_found[0];
|
||||||
// Check password
|
// Check password
|
||||||
if (account.password_hash != args[0]) {
|
if (account.password_hash != pwdHash(accountPwd)) {
|
||||||
co_await i->send_event(user.get_notice("Error: Invalid password!", sender->uid));
|
co_await i->send_event(user.get_notice("Error: Invalid password!", sender->uid));
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
// Login
|
// Login
|
||||||
co_await i->send_event(sender->get_encap_su(account.name));
|
co_await i->send_event(sender->get_encap_su(account.name, i->config.server.uid));
|
||||||
}, "<password>", 1}
|
// 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) {
|
std::string NickServ::pwdHash(std::string_view pwd) {
|
||||||
co_return;
|
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 {
|
struct Account {
|
||||||
size_t id;
|
int id = -1;
|
||||||
std::string name, password_hash;
|
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 {
|
class NickServ : public ServiceBase {
|
||||||
std::unique_ptr<Storage> storage;
|
std::unique_ptr<Storage> storage;
|
||||||
|
@ -41,6 +41,7 @@ class NickServ : public ServiceBase {
|
||||||
User user;
|
User user;
|
||||||
|
|
||||||
virtual async::result<void> intitialize() override;
|
virtual async::result<void> intitialize() override;
|
||||||
virtual async::result<void> on_event(const Event& event) override;
|
|
||||||
|
std::string pwdHash(std::string_view pwd);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Reference in a new issue