#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(); } };