fileserver/src/server/auth.cxx

230 lines
6.6 KiB
C++
Raw Normal View History

#include <botan/argon2fmt.h>
#include <botan/hex.h>
#include <botan/mac.h>
#include <botan/base32.h>
#include "server_internal.hxx"
std::string hash_password(const std::string &password) {
return Botan::argon2_generate_pwhash(password.c_str(), password.size(), *auth_rng, 1, 64*1024, 4);
}
std::optional<std::string> Server::Auth_signup(std::string &&username, std::string &&password) {
std::unique_lock lock{user_lock};
for (const auto &user : users) {
if (user.second->name == username)
return "User already exists";
}
if (password.length() < 6)
return "Password must be 6 characters or longer";
auto hash = hash_password(password);
auto id = next_user_id++;
std::shared_ptr<Node> root{new Node{
.id = 0,
.name = "",
.file = false,
.preview = false,
.parent = nullptr,
.size = 0
}};
std::shared_ptr<User> user{new User{
.id = id,
.name = username,
.password = hash,
.enabled = false,
.admin = false,
.tfa_enabled = false
}};
user->nodes.emplace(0, std::move(root));
user->user_dir = files_dir / std::to_string(user->id);
std::filesystem::create_directory(user->user_dir);
users.emplace(id, std::move(user));
save();
return std::nullopt;
}
mrpc::Response<mrpc::LoginResponse> Server::Auth_login(std::string &&username, std::string &&password, std::optional<std::string> &&otp) {
std::shared_lock lock{user_lock};
std::shared_ptr<User> user = nullptr;
for (const auto &u : users) {
if (u.second->name == username) {
user = u.second;
break;
}
}
if (user == nullptr)
return { .e = "Invalid username or password" };
if (!Botan::argon2_check_pwhash(password.c_str(), password.size(), user->password))
return { .e = "Invalid username or password" };
if (!user->enabled)
return { .e = "User is disabled" };
if (user->tfa_enabled) {
if (otp.has_value()) {
bool ok = user->tfa_mail ? check_mail_code(user, otp.value()) : check_tfa_code(user, otp.value());
if (!ok)
return { .e = "Invalid code" };
} else {
if (user->tfa_mail)
send_tfa_mail(user);
mrpc::LoginResponse response { .otp_needed = true };
return { .o = std::move(response) };
}
}
auto token = Botan::hex_encode(auth_rng->random_vec(16), false);
{
std::unique_lock tlock{token_lock};
tokens.emplace(token, std::make_unique<Token>(user));
}
mrpc::LoginResponse response {
.otp_needed = false,
.token = token
};
return { .o = std::move(response) };
}
void Server::Auth_logout(std::string &&token) {
std::unique_lock lock{token_lock};
tokens.erase(token);
}
std::optional<std::string> Server::Auth_logout_all(std::string &&token) {
check_user_optional();
logout_user(user->id);
return std::nullopt;
}
void Server::Auth_send_recovery_key(std::string &&username) {
std::shared_lock lock{user_lock};
User *user = nullptr;
for (const auto &u : users) {
if (u.second->name == username) {
user = u.second.get();
break;
}
}
if (user == nullptr)
return;
auto code = Botan::hex_encode(auth_rng->random_vec(5), true);
{
std::lock_guard rlock{recovery_lock};
recovery_keys.emplace(code, std::make_pair(user->id, Token::clock::now() + std::chrono::minutes{5}));
}
send_mail(user->name, "MFileserver - Password recovery", "Your recovery key is: " + code + "\r\nIt is valid for 5 minutes");
}
std::optional<std::string> Server::Auth_reset_password(std::string &&key, std::string &&password) {
std::lock_guard lock{recovery_lock};
auto now = Token::clock::now();
for (auto it = recovery_keys.begin(); it != recovery_keys.end();) {
if (now >= it->second.second)
recovery_keys.erase(it++);
else
++it;
}
auto entry = recovery_keys.find(key);
if (entry == recovery_keys.end())
return "Invalid key";
auto user = get_user(entry->second.first);
if (!user)
return "Invalid key";
if (password.length() < 6)
return "Password must be 6 characters or longer";
recovery_keys.erase(key);
logout_user(user->id);
{
std::shared_lock ulock{user_lock};
user->password = hash_password(password);
}
save();
return std::nullopt;
}
std::optional<std::string> Server::Auth_change_password(std::string &&token, std::string &&old_pw, std::string &&new_pw) {
check_user_optional();
std::shared_lock lock{user_lock};
if (!Botan::argon2_check_pwhash(old_pw.c_str(), old_pw.size(), user->password))
return "Old password is wrong";
if (new_pw.length() < 6)
return "Password must be 6 characters or longer";
user->password = hash_password(new_pw);
save();
return std::nullopt;
}
std::optional<std::string> Server::Auth_tfa_setup_mail(std::string &&token) {
check_user_optional();
if (user->tfa_enabled)
return "Tfa is already enabled";
user->tfa_mail = true;
send_tfa_mail(user);
return std::nullopt;
}
mrpc::Response<std::string> Server::Auth_tfa_setup_totp(std::string &&token) {
check_user_response();
if (user->tfa_enabled)
return { .e = "Tfa is already enabled" };
user->tfa_mail = false;
Botan::OctetString secret{*auth_rng, 16};
user->tfa_secret = secret.to_string();
return { .o = Botan::base32_encode(secret) };
}
std::optional<std::string> Server::Auth_tfa_complete(std::string &&token, std::string &&otp) {
check_user_optional();
bool ok = user->tfa_mail ? check_mail_code(user, otp) : check_tfa_code(user, otp);
if (!ok)
return "Invalid code";
user->tfa_enabled = true;
logout_user(user->id);
save();
return std::nullopt;
}
std::optional<std::string> Server::Auth_tfa_disable(std::string &&token) {
check_user_optional();
user->tfa_enabled = false;
logout_user(user->id);
save();
return std::nullopt;
}
std::optional<std::string> Server::Auth_delete_user(std::string &&token) {
check_user_optional();
delete_user(user);
save();
return std::nullopt;
}
mrpc::Response<mrpc::Session> Server::Auth_session_info(std::string &&token) {
check_user_response();
auto t = get_token(token);
if (!t)
return { .e = "Invalid token" };
mrpc::Session info {
.name = user->name,
.tfa_enabled = user->tfa_enabled,
.admin = user->admin,
.sudo = t->sudo_original_user != nullptr
};
return { .o = info };
}