187 lines
4.1 KiB
C++

#pragma once
struct AppUser
{
Request& context;
explicit AppUser(Request& request)
: context(request)
{
}
static String session_key()
{
return("app_user_id");
}
static String normalize_email(String email)
{
return(to_lower(trim(email)));
}
static String hash_id(String raw)
{
raw = normalize_email(raw);
return(gen_sha1("uce-app:" + raw).substr(0, 12));
}
String root_path()
{
return(first(context.cfg.get_by_path("filebase/path").to_string(), "/tmp/uce-app-data"));
}
String dir(String email)
{
String bucket = hash_id(email);
return(path_join(root_path(), "users/" + bucket.substr(0, 2) + "/" + bucket));
}
String file_name(String email)
{
return(path_join(dir(email), "account.json"));
}
static String password_hash(String password, String salt)
{
String hash = "uce-app:" + password + ":" + salt;
for(s32 i = 0; i < 2048; i++)
hash = gen_sha1(hash + ":" + std::to_string(i));
return(hash);
}
static DValue read_json_file(String file_name)
{
DValue result;
if(!file_exists(file_name))
return(result);
String raw = trim(file_get_contents(file_name));
if(raw == "")
return(result);
return(json_decode(raw));
}
static bool write_json_file(String file_name, DValue data)
{
mkdir(dirname(file_name));
return(file_put_contents(file_name, json_encode(data)));
}
DValue load(String email)
{
DValue user = read_json_file(file_name(email));
if(user.get_type_name() != "array")
return(DValue());
user["id"] = normalize_email(email);
return(user);
}
DValue create(String email, String password)
{
DValue result;
email = normalize_email(email);
password = trim(password);
result["message"] = "";
result["result"].set_bool(false);
if(email == "" || password == "")
{
result["message"] = "email_and_password_required";
return(result);
}
if(email.find("@") == String::npos)
{
result["message"] = "invalid_email";
return(result);
}
DValue existing = load(email);
if(existing.get_type_name() == "array" && existing["email"].to_string() != "")
{
result["message"] = "user_exists";
return(result);
}
String salt = gen_sha1(std::to_string((u64)time()) + ":" + std::to_string((u64)(time_precise() * 1000000.0)) + ":" + session_id_create()).substr(0, 24);
DValue user;
user["email"] = email;
user["salt"] = salt;
user["password_hash"] = password_hash(password, salt);
user["created"] = std::to_string((u64)time());
DValue role;
role = "user";
user["roles"].push(role);
write_json_file(file_name(email), user);
user["id"] = email;
result["result"].set_bool(true);
result["id"] = email;
result["profile"] = user;
return(result);
}
DValue sign_in(String email, String password)
{
DValue result;
result["result"].set_bool(false);
email = normalize_email(email);
password = trim(password);
DValue user = load(email);
if(user.get_type_name() != "array" || user["email"].to_string() == "")
{
result["message"] = "no_such_user";
return(result);
}
if(user["password_hash"].to_string() == "")
{
result["message"] = "no_password_set";
return(result);
}
String expected = password_hash(password, user["salt"].to_string());
if(expected != user["password_hash"].to_string())
{
result["message"] = "invalid_password";
return(result);
}
session_start();
context.session[session_key()] = email;
context.call["app"]["current_user"] = user;
result["result"].set_bool(true);
result["profile"] = user;
return(result);
}
bool is_signed_in()
{
if(context.call["app"]["current_user"]["email"].to_string() != "")
return(true);
String user_id = context.session[session_key()];
if(user_id == "")
return(false);
DValue user = load(user_id);
if(user["email"].to_string() == "")
{
context.session.erase(session_key());
return(false);
}
context.call["app"]["current_user"] = user;
return(true);
}
DValue current()
{
if(is_signed_in())
return(context.call["app"]["current_user"]);
return(DValue());
}
void logout()
{
context.session.erase(session_key());
context.call["app"]["current_user"].clear();
}
};