1 #pragma once 2 3 #include <nlohmann/json.hpp> 4 #include <pam_authenticate.hpp> 5 #include <webassets.hpp> 6 #include <random> 7 #include <crow/app.h> 8 #include <crow/http_request.h> 9 #include <crow/http_response.h> 10 #include <boost/container/flat_map.hpp> 11 #include <boost/uuid/uuid.hpp> 12 #include <boost/uuid/uuid_generators.hpp> 13 #include <boost/uuid/uuid_io.hpp> 14 15 namespace crow { 16 17 namespace PersistentData { 18 19 enum class PersistenceType { 20 TIMEOUT, // User session times out after a predetermined amount of time 21 SINGLE_REQUEST // User times out once this request is completed. 22 }; 23 24 struct UserSession { 25 std::string unique_id; 26 std::string session_token; 27 std::string username; 28 std::string csrf_token; 29 std::chrono::time_point<std::chrono::steady_clock> last_updated; 30 PersistenceType persistence; 31 }; 32 33 void to_json(nlohmann::json& j, const UserSession& p) { 34 if (p.persistence != PersistenceType::SINGLE_REQUEST) { 35 j = nlohmann::json{{"unique_id", p.unique_id}, 36 {"session_token", p.session_token}, 37 {"username", p.username}, 38 {"csrf_token", p.csrf_token}}; 39 } 40 } 41 42 void from_json(const nlohmann::json& j, UserSession& p) { 43 try { 44 p.unique_id = j.at("unique_id").get<std::string>(); 45 p.session_token = j.at("session_token").get<std::string>(); 46 p.username = j.at("username").get<std::string>(); 47 p.csrf_token = j.at("csrf_token").get<std::string>(); 48 // For now, sessions that were persisted through a reboot get their timer 49 // reset. This could probably be overcome with a better understanding of 50 // wall clock time and steady timer time, possibly persisting values with 51 // wall clock time instead of steady timer, but the tradeoffs of all the 52 // corner cases involved are non-trivial, so this is done temporarily 53 p.last_updated = std::chrono::steady_clock::now(); 54 } catch (std::out_of_range) { 55 // do nothing. Session API incompatibility, leave sessions empty 56 } 57 } 58 59 class Middleware; 60 61 class SessionStore { 62 public: 63 const UserSession& generate_user_session( 64 const std::string& username, 65 PersistenceType persistence = PersistenceType::TIMEOUT) { 66 // TODO(ed) find a secure way to not generate session identifiers if 67 // persistence is set to SINGLE_REQUEST 68 static constexpr std::array<char, 62> alphanum = { 69 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 70 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 71 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 72 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 73 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 74 75 // entropy: 30 characters, 62 possibilities. log2(62^30) = 178 bits of 76 // entropy. OWASP recommends at least 60 77 // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy 78 std::string session_token; 79 session_token.resize(20, '0'); 80 std::uniform_int_distribution<int> dist(0, alphanum.size() - 1); 81 for (int i = 0; i < session_token.size(); ++i) { 82 session_token[i] = alphanum[dist(rd)]; 83 } 84 // Only need csrf tokens for cookie based auth, token doesn't matter 85 std::string csrf_token; 86 csrf_token.resize(20, '0'); 87 for (int i = 0; i < csrf_token.size(); ++i) { 88 csrf_token[i] = alphanum[dist(rd)]; 89 } 90 91 std::string unique_id; 92 unique_id.resize(10, '0'); 93 for (int i = 0; i < unique_id.size(); ++i) { 94 unique_id[i] = alphanum[dist(rd)]; 95 } 96 97 const auto session_it = auth_tokens.emplace( 98 session_token, 99 std::move(UserSession{unique_id, session_token, username, csrf_token, 100 std::chrono::steady_clock::now(), persistence})); 101 const UserSession& user = (session_it).first->second; 102 // Only need to write to disk if session isn't about to be destroyed. 103 need_write_ = persistence == PersistenceType::TIMEOUT; 104 return user; 105 } 106 107 const UserSession* login_session_by_token(const std::string& token) { 108 apply_session_timeouts(); 109 auto session_it = auth_tokens.find(token); 110 if (session_it == auth_tokens.end()) { 111 return nullptr; 112 } 113 UserSession& foo = session_it->second; 114 foo.last_updated = std::chrono::steady_clock::now(); 115 return &foo; 116 } 117 118 const UserSession* get_session_by_uid(const std::string& uid) { 119 apply_session_timeouts(); 120 // TODO(Ed) this is inefficient 121 auto session_it = auth_tokens.begin(); 122 while (session_it != auth_tokens.end()) { 123 if (session_it->second.unique_id == uid) { 124 return &session_it->second; 125 } 126 session_it++; 127 } 128 return nullptr; 129 } 130 131 void remove_session(const UserSession* session) { 132 auth_tokens.erase(session->session_token); 133 need_write_ = true; 134 } 135 136 std::vector<const std::string*> get_unique_ids( 137 bool getAll = true, 138 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) { 139 apply_session_timeouts(); 140 141 std::vector<const std::string*> ret; 142 ret.reserve(auth_tokens.size()); 143 for (auto& session : auth_tokens) { 144 if (getAll || type == session.second.persistence) { 145 ret.push_back(&session.second.unique_id); 146 } 147 } 148 return ret; 149 } 150 151 bool needs_write() { return need_write_; } 152 153 // Persistent data middleware needs to be able to serialize our auth_tokens 154 // structure, which is private 155 friend Middleware; 156 157 private: 158 void apply_session_timeouts() { 159 std::chrono::minutes timeout(60); 160 auto time_now = std::chrono::steady_clock::now(); 161 if (time_now - last_timeout_update > std::chrono::minutes(1)) { 162 last_timeout_update = time_now; 163 auto auth_tokens_it = auth_tokens.begin(); 164 while (auth_tokens_it != auth_tokens.end()) { 165 if (time_now - auth_tokens_it->second.last_updated >= timeout) { 166 auth_tokens_it = auth_tokens.erase(auth_tokens_it); 167 need_write_ = true; 168 } else { 169 auth_tokens_it++; 170 } 171 } 172 } 173 } 174 std::chrono::time_point<std::chrono::steady_clock> last_timeout_update; 175 boost::container::flat_map<std::string, UserSession> auth_tokens; 176 std::random_device rd; 177 bool need_write_{false}; 178 }; 179 180 } // namespaec PersistentData 181 } // namespace crow 182