Compare commits

...

3 Commits

8 changed files with 254 additions and 3 deletions

10
include/hash/hash.hpp Normal file
View File

@ -0,0 +1,10 @@
#ifndef HASH_HPP
#define HASH_HPP
#include <string>
#include <string_view>
auto generate_hash(const std::string_view password, std::size_t rounds) -> std::string;
auto validate_password(const std::string_view password, const std::string_view hash) -> bool;
#endif // HASH_HPP

19
include/jwt/jwt.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef JWT_H
#define JWT_H
#ifdef __cplusplus
extern "C"
{
#endif
char* create_token(const char* user_id, const char* secret);
char* get_payload(const char* token);
int verify_token(const char* token, const char* secret);
#ifdef __cplusplus
}
#endif
#endif // JWT_H

6
include/server/auth.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef SERVER_AUTH_H
#define SERVER_AUTH_H
// Your code here
#endif // SERVER_AUTH_H

View File

@ -1,5 +1,5 @@
#ifndef KQM_TYPES_H
#define KQM_TYPES_H
#ifndef SERVER_TYPES_H
#define SERVER_TYPES_H
#include <civetweb.h>
@ -8,4 +8,4 @@ typedef struct mg_context mg_context;
typedef struct mg_connection mg_connection;
typedef struct mg_request_info mg_request_info;
#endif // KQM_TYPES_H
#endif // SERVER_TYPES_H

79
src/db/auth.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "auth.h"
#include "hash/hash.hpp"
#include <print>
#include <leveldb/db.h>
using leveldb_ptr = leveldb::DB*;
auto user_db = leveldb_ptr{nullptr};
auto iter_round = 10000;
extern "C"
{
auto open_user_db() -> int
{
auto opts = leveldb::Options{};
opts.create_if_missing = true;
auto status = leveldb::DB::Open(opts, "db/user", &user_db);
if (!status.ok()) {
std::println(stderr, "Failed to open user database: {}", status.ToString());
return 0;
}
return 1;
}
auto close_user_db() -> void
{
delete user_db;
}
auto check_user_exists(const char* user_id, int* result) -> int
{
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, user_id, &value);
if (status.ok()) {
*result = 1;
} else if (status.IsNotFound()) {
*result = 0;
} else {
std::println(stderr, "Failed to check user existence: {}", status.ToString());
return 0;
}
return 1;
}
auto set_user_password(const char* user_id, const char* password) -> int
{
auto status = user_db->Put(leveldb::WriteOptions{}, user_id, generate_hash(password, iter_round));
if (!status.ok()) {
std::println(stderr, "Failed to set user password: {}", status.ToString());
return 0;
}
return 1;
}
auto login(const char* user_id, const char* password, int* result) -> int
{
auto value = std::string{};
auto status = user_db->Get(leveldb::ReadOptions{}, user_id, &value);
if (!status.ok()) {
std::println(stderr, "Failed to login: {}", status.ToString());
return 0;
}
*result = validate_password(password, value.data());
return 1;
}
auto logout(const char* user_id) -> int
{
leveldb::WriteOptions write_options;
leveldb::Status status = user_db->Delete(write_options, user_id);
return status.ok();
}
}

91
src/hash/hash.cpp Normal file
View File

@ -0,0 +1,91 @@
#include <hash.hpp>
#include <cryptopp/algparam.h>
#include <cryptopp/cryptlib.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include <cryptopp/modes.h>
#include <cryptopp/osrng.h>
#include <cryptopp/pwdbased.h>
#include <cryptopp/sha.h>
#include <array>
#include <format>
#include <vector>
using namespace CryptoPP;
auto to_byte_ptr(auto* p) noexcept -> const byte*
{
return reinterpret_cast<const byte*>(p);
}
auto generate_salt(size_t length = 16) -> std::string
{
auto prng = AutoSeededRandomPool{};
auto salt = std::vector<byte>(length);
prng.GenerateBlock(salt.data(), length);
auto salt_str = std::string{};
auto encoder = HexEncoder{new StringSink(salt_str)};
encoder.Put(salt.data(), length);
encoder.MessageEnd();
return salt_str;
}
auto hash_password_with_pbkdf2(const std::string_view password, const std::string_view salt, std::size_t iterations)
-> std::string
{
auto decoded_salt = std::string{};
auto decoder = HexDecoder{};
decoder.Attach(new StringSink(decoded_salt));
decoder.Put(to_byte_ptr(salt.data()), salt.size());
decoder.MessageEnd();
auto pbkdf2 = PKCS5_PBKDF2_HMAC<SHA256>{};
auto derived_key = std::array<byte, SHA256::DIGESTSIZE>{};
pbkdf2.DeriveKey(derived_key.data(),
derived_key.size(),
0,
to_byte_ptr(password.data()),
password.size(),
to_byte_ptr(decoded_salt.data()),
decoded_salt.size(),
iterations,
0);
auto hashed_password = std::string{};
auto encoder = HexEncoder{new StringSink(hashed_password)};
encoder.Put(derived_key.data(), derived_key.size());
encoder.MessageEnd();
return hashed_password;
}
auto generate_hash(const std::string_view password, std::size_t rounds) -> std::string
{
auto salt = generate_salt();
auto hashed_password = hash_password_with_pbkdf2(password, salt, rounds);
return std::format("{}${}${}", salt, rounds, hashed_password);
}
auto validate_password(const std::string_view password, const std::string_view hash) -> bool
{
auto first_delimiter = hash.find('$');
auto second_delimiter = hash.find('$', first_delimiter + 1);
if (first_delimiter == std::string::npos || second_delimiter == std::string::npos) {
return false;
}
auto stored_salt = hash.substr(0, first_delimiter);
auto stored_rounds = std::size_t{};
{
auto first = hash.data() + first_delimiter + 1;
auto last = hash.data() + second_delimiter;
auto [ptr, ec] = std::from_chars(first, last, stored_rounds);
if (ec != std::errc{}) return false;
}
auto stored_hashed_password = hash.substr(second_delimiter + 1);
auto entered_hashed_password = hash_password_with_pbkdf2(password, stored_salt, stored_rounds);
return entered_hashed_password == stored_hashed_password;
}

46
src/jwt/jwt.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "jwt.h"
#include <chrono>
#include <jwt-cpp/jwt.h>
using namespace std::literals::chrono_literals;
extern "C"
{
auto create_token(const char* user_id, const char* secret) -> char*
{
auto token = jwt::create()
.set_type("JWS")
.set_issuer("KeqingMoe")
.set_subject("user_id")
.set_audience("web_client")
.set_expires_at(std::chrono::system_clock::now() + 3s)
.set_payload_claim("user_id", jwt::claim(std::string{user_id}))
.sign(jwt::algorithm::hs256{secret});
return strdup(token.c_str());
}
auto get_payload(const char* token) -> char*
{
auto decoded_token = jwt::decode(token);
auto payload = decoded_token.get_payload_claim("user_id").as_string();
return strdup(payload.c_str());
}
auto verify_token(const char* token, const char* secret) -> int
{
try {
auto decoded_token = jwt::decode(token);
auto verifier = jwt::verify()
.allow_algorithm(jwt::algorithm::hs256{secret})
.with_issuer("KeqingMoe")
.with_subject("user_id");
verifier.verify(decoded_token);
return 1;
} catch (...) {
return 0;
}
}
}