Fixed some Cookie bugs, rewrote everything to nytl, added session system, added ugly login page
This commit is contained in:
		
							parent
							
								
									3632ade86d
								
							
						
					
					
						commit
						a6f4bd6c88
					
				| @ -1,3 +1,4 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="ru"> | ||||
| <head> | ||||
| @ -36,3 +37,4 @@ | ||||
| <script src="/assets/js/chat.js"></script> | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF %} | ||||
| @ -1,3 +1,4 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="ru"> | ||||
| <head> | ||||
| @ -7,6 +8,7 @@ | ||||
|     <link rel="stylesheet" href="/assets/css/list-rooms.css"> | ||||
| </head> | ||||
| <body> | ||||
| {% PUT pass-userinfo userinfo %} | ||||
| <div class="container"> | ||||
|     <h1 style="color: white;">Выберите Чат-Комнату</h1> | ||||
|     <ul class="room-list"> | ||||
| @ -60,6 +62,7 @@ | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| <script src="/assets/js/list-rooms.js"></script> | ||||
| <!--<script src="/assets/js/list-rooms.js"></script>--> | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF %} | ||||
| @ -1,4 +1,4 @@ | ||||
| {% ELDEF main JSON pres %} | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
|     <!DOCTYPE html> | ||||
|     <html lang="{% WRITE pres.lang %}"> | ||||
|     <head> | ||||
| @ -10,11 +10,12 @@ | ||||
|     </head> | ||||
| 
 | ||||
|     <body> | ||||
|     {% PUT pass-userinfo userinfo %} | ||||
|     <div class="form-container"> | ||||
|         <h1 class="hide-cursor no-select">{% WRITE pres.phr.decl.enter %}</h1> | ||||
|         <form action="/" method="post"> | ||||
|             <label for="username">{% WRITE pres.phr.decl.nickname %}</label> | ||||
|             <input type="text" name="username" id="username"><br> | ||||
|         <form action="/login" method="post" enctype="application/x-www-form-urlencoded"> | ||||
|             <label for="nickname">{% WRITE pres.phr.decl.nickname %}</label> | ||||
|             <input type="text" name="nickname" id="nickname"><br> | ||||
|             <label for="password">{% WRITE pres.phr.decl.password %}</label> | ||||
|             <input type="password" name="password"  id="password"><br> | ||||
|             <button type="submit" class="hide-cursor no-select">{% WRITE pres.phr.act.enter %}</button> | ||||
|  | ||||
							
								
								
									
										5
									
								
								assets/HypertextPages/pass-userinfo.nytl.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								assets/HypertextPages/pass-userinfo.nytl.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| {% ELDEF main JSON userinfo %} | ||||
|     <input type="hidden" id="hidden_field_uid" name="uid" value="{% WRITE userinfo.uid %}"> | ||||
|     <input type="hidden" id="hidden_field_nickname" name="nickname" value="{% WRITE userinfo.nickname %}"> | ||||
|     <input type="hidden" id="hidden_field_name" name="name" value="{% WRITE userinfo.name %}"> | ||||
| {% ENDELDEF %} | ||||
| @ -1,3 +1,4 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
| @ -9,7 +10,7 @@ | ||||
| <div class="main-container"> | ||||
|     <div class="profile-header"> | ||||
|         <h1 style="color: white; text-align: center;">Профиль пользователя</h1> | ||||
|         <a class="return" href="chat.html">Назад</a> | ||||
|         <a class="return" href="chat.nytl.html">Назад</a> | ||||
|     </div> | ||||
|     <form> | ||||
|         <div class="columns"> | ||||
| @ -35,3 +36,4 @@ | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF%} | ||||
| @ -1,3 +1,4 @@ | ||||
| {% ELDEF main JSON pres JSON userinfo %} | ||||
| <!DOCTYPE html> | ||||
| <html lang="ru"> | ||||
| <head> | ||||
| @ -23,3 +24,4 @@ | ||||
| <script src="assets/js/registration.js"></script> | ||||
| </body> | ||||
| </html> | ||||
| {% ENDELDEF %} | ||||
| @ -146,6 +146,7 @@ struct CAWebChat { | ||||
|                 "str_fields.cpp", | ||||
|                 "find_db.cpp", | ||||
|                 "sqlite3_wrapper.cpp", | ||||
|                 "login_cookie.cpp", | ||||
|             }; | ||||
|             for (std::string& u: T.units) | ||||
|                 u = "web_chat/iu9_ca_web_chat_lib/" + u; | ||||
|  | ||||
| @ -27,7 +27,7 @@ namespace een9 { | ||||
|             if (ch <= 32 || ch == 0x22 || ch == 0x2C || ch == 0x3B || ch == 0x5C || ch >= 0x7F) | ||||
|                 return false; | ||||
|         } | ||||
|         return !str.empty(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::pair<std::string, std::string>> parseCookieHeader(const std::string &hv) { | ||||
| @ -37,18 +37,25 @@ namespace een9 { | ||||
|             while (hv.size() > pos && isSPACE(hv[pos])) | ||||
|                 pos++; | ||||
|         }; | ||||
|         auto read_to_space_or_ch = [&](char sch) -> std::string { | ||||
|         auto read_to_space_or_eq = [&]() -> std::string { | ||||
|             size_t S = pos; | ||||
|             while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != sch) | ||||
|             while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '=') | ||||
|                 pos++; | ||||
|             return hv.substr(S, pos - S); | ||||
|         }; | ||||
|         auto read_to_space_or_dq_or_semc = [&]() -> std::string { | ||||
|             size_t S = pos; | ||||
|             while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '"' && hv[pos] != ';') | ||||
|                 pos++; | ||||
|             return hv.substr(S, pos - S); | ||||
|         }; | ||||
| 
 | ||||
|         auto isThis = [&](char ch) -> bool { | ||||
|             return pos < hv.size() && hv[pos] == ch; | ||||
|         }; | ||||
|         skip_ows(); | ||||
|         while (pos < hv.size()) { | ||||
|             std::string name_of_pechenye = read_to_space_or_ch('='); | ||||
|             std::string name_of_pechenye = read_to_space_or_eq(); | ||||
|             ASSERT(isCookieName(name_of_pechenye), "Incorrect Cookie name"); | ||||
|             skip_ows(); | ||||
|             ASSERT(isThis('='), "Incorrect Cookie header line, missing ="); | ||||
| @ -57,11 +64,11 @@ namespace een9 { | ||||
|             std::string value_of_pechenye; | ||||
|             if (isThis('"')) { | ||||
|                 pos++; | ||||
|                 value_of_pechenye = read_to_space_or_ch('"'); | ||||
|                 value_of_pechenye = read_to_space_or_dq_or_semc(); | ||||
|                 ASSERT(isThis('"'), "Incorrect Cookie header line, missing \""); | ||||
|                 pos++; | ||||
|             } else { | ||||
|                 value_of_pechenye = read_to_space_or_ch('"');; | ||||
|                 value_of_pechenye = read_to_space_or_dq_or_semc(); | ||||
|             } | ||||
|             ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value"); | ||||
|             if (result.empty()) | ||||
| @ -73,11 +80,24 @@ namespace een9 { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::pair<std::string, std::string>> | ||||
|     findAllClientCookies(const std::vector<std::pair<std::string, std::string>>& header) { | ||||
|         std::vector<std::pair<std::string, std::string>> result; | ||||
|         for (const std::pair<std::string, std::string>& line: header) { | ||||
|             if (line.first == "Cookie") { | ||||
|                 std::vector<std::pair<std::string, std::string>> new_cookies = parseCookieHeader(line.second); | ||||
|                 result.reserve(result.size() + new_cookies.size()); | ||||
|                 result.insert(result.end(), new_cookies.begin(), new_cookies.end()); | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     void set_cookie(const std::vector<std::pair<std::string, std::string>>& new_cookies, | ||||
|         std::vector<std::pair<std::string, std::string>>& res_header_lines) { | ||||
|                     std::vector<std::pair<std::string, std::string>>& res_header_lines) { | ||||
|         for (const std::pair<std::string, std::string>& cookie : new_cookies) { | ||||
|             ASSERT_pl(isCookieName(cookie.first) && isCookieValue(cookie.second)); | ||||
|             res_header_lines.emplace_back("Set-Cookie", cookie.first + "=" + cookie.second + ";SameSite=Strict"); | ||||
|             res_header_lines.emplace_back("Set-Cookie", cookie.first + "=\"" + cookie.second + "\";SameSite=Strict;Path=/"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -14,6 +14,10 @@ namespace een9 { | ||||
|     /* Throws een9::ServerError on failure */ | ||||
|     std::vector<std::pair<std::string, std::string>> parseCookieHeader(const std::string& hv); | ||||
| 
 | ||||
|     /* Header is header. Throws een9::ServerError on failure. Concatenates output of een9::parseCookieHeader */ | ||||
|     std::vector<std::pair<std::string, std::string>> | ||||
|     findAllClientCookies(const std::vector<std::pair<std::string, std::string>>& header); | ||||
| 
 | ||||
|     /* Can throw een9::ServerError (if check for a value failed). res_header_lines is mutated accordingle */ | ||||
|     void set_cookie(const std::vector<std::pair<std::string, std::string>>& new_cookies, | ||||
|         std::vector<std::pair<std::string, std::string>>& res_header_lines); | ||||
|  | ||||
| @ -5,7 +5,8 @@ | ||||
| 
 | ||||
| 
 | ||||
| namespace een9 { | ||||
|     std::string form_http_server_response_header(const char* code, const std::map<std::string, std::string>& headers) { | ||||
|     std::string form_http_server_response_header(const char* code, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers) { | ||||
|         assert(strlen(code) == 3); | ||||
|         std::string result = std::string("HTTP/1.0 ") + code + " " + (code[0] < '4' ? "OK" : "ERROR") + "\r\n"; | ||||
|         for (auto& p: headers) | ||||
| @ -13,12 +14,13 @@ namespace een9 { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     std::string form_http_server_reponse_header_only(const char* code, const std::map<std::string, std::string>& headers) { | ||||
|     std::string form_http_server_reponse_header_only(const char* code, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers) { | ||||
|         return form_http_server_response_header(code, headers) + "\r\n"; | ||||
|     } | ||||
| 
 | ||||
|     std::string form_http_server_response_with_body(const char* code, | ||||
|         const std::map<std::string, std::string>& headers, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers, | ||||
|         const std::string& body) | ||||
|     { | ||||
|         std::string result = form_http_server_response_header(code, headers) | ||||
| @ -38,4 +40,15 @@ namespace een9 { | ||||
|             {"Content-Type", Content_Type} | ||||
|         }, body); | ||||
|     } | ||||
| 
 | ||||
|     std::string form_http_server_response_307(const std::string& Location) { | ||||
|         return form_http_server_reponse_header_only("307", {{"Location", Location}}); | ||||
|     } | ||||
| 
 | ||||
|     std::string form_http_server_response_307_spec_head(const std::string &Location, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers) { | ||||
|         std::vector<std::pair<std::string, std::string>> cp = headers; | ||||
|         cp.emplace_back("Location", Location); | ||||
|         return form_http_server_reponse_header_only("307", cp); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,21 +1,29 @@ | ||||
| #ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H | ||||
| #define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H | ||||
| 
 | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace een9 { | ||||
|     std::string form_http_server_response_header(const char* code, const std::map<std::string, std::string>& headers); | ||||
| 
 | ||||
|     std::string form_http_server_reponse_header_only(const char* code, const std::map<std::string, std::string>& headers); | ||||
| namespace een9 { | ||||
|     std::string form_http_server_response_header(const char* code, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers); | ||||
| 
 | ||||
|     std::string form_http_server_reponse_header_only(const char* code, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers); | ||||
| 
 | ||||
|     std::string form_http_server_response_with_body(const char* code, | ||||
|         const std::map<std::string, std::string>& headers, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers, | ||||
|         const std::string& body); | ||||
| 
 | ||||
|     std::string form_http_server_response_200(const std::string& Content_Type, const std::string& body); | ||||
| 
 | ||||
|     std::string form_http_server_response_404(const std::string& Content_Type, const std::string& body); | ||||
| 
 | ||||
|     std::string form_http_server_response_307(const std::string& Location); | ||||
| 
 | ||||
|     std::string form_http_server_response_307_spec_head(const std::string &Location, | ||||
|         const std::vector<std::pair<std::string, std::string>>& headers); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
| @ -35,31 +35,31 @@ namespace iu9cawebchat { | ||||
|          * If chat.lastMsgId is NULL, chat is empty | ||||
|          * If message.previous is NULL, this message is first in it's chat | ||||
|          */ | ||||
|         sqlite_single_statement(conn.hand, "PRAGMA foreign_keys = true"); | ||||
|         sqlite_single_statement(conn.hand, "BEGIN"); | ||||
|         sqlite_single_statement(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); | ||||
|         sqlite_single_statement(conn.hand, "CREATE TABLE `user` (" | ||||
|         sqlite_nooutput(conn.hand, "PRAGMA foreign_keys = true"); | ||||
|         sqlite_nooutput(conn.hand, "BEGIN"); | ||||
|         sqlite_nooutput(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); | ||||
|         sqlite_nooutput(conn.hand, "CREATE TABLE `user` (" | ||||
|                                          "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," | ||||
|                                          "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," | ||||
|                                          "`name` TEXT NOT NULL," | ||||
|                                          "`chatList_HistoryId` INTEGER NOT NULL," | ||||
|                                          "`password` TEXT NOT NULL" | ||||
|                                          ")"); | ||||
|         sqlite_single_statement(conn.hand, "CREATE TABLE `chat` (" | ||||
|         sqlite_nooutput(conn.hand, "CREATE TABLE `chat` (" | ||||
|                                          "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," | ||||
|                                          "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," | ||||
|                                          "`name` TEXT NOT NULL," | ||||
|                                          "`it_HistoryId` INTEGER NOT NULL," | ||||
|                                          "`lastMsgId` INTEGER REFERENCES `message`" | ||||
|                                          ")"); | ||||
|         sqlite_single_statement(conn.hand, "CREATE TABLE `user_chat_membership` (" | ||||
|         sqlite_nooutput(conn.hand, "CREATE TABLE `user_chat_membership` (" | ||||
|                                          "`userId` INTEGER REFERENCES `user` NOT NULL," | ||||
|                                          "`chatId` INTEGER REFERENCES `chat` NOT NULL," | ||||
|                                          "`user_chatList_IncHistoryId` INTEGER NOT NULL," | ||||
|                                          "`chat_IncHistoryId` INTEGER NOT NULL," | ||||
|                                          "`role` INTEGER NOT NULL" | ||||
|                                          ")"); | ||||
|         sqlite_single_statement(conn.hand, "CREATE TABLE `message` (" | ||||
|         sqlite_nooutput(conn.hand, "CREATE TABLE `message` (" | ||||
|                                          "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," | ||||
|                                          "`chatId` INTEGER REFERENCES `chat` NOT NULL," | ||||
|                                          "`previous` INTEGER REFERENCES `message`," | ||||
| @ -69,10 +69,10 @@ namespace iu9cawebchat { | ||||
|                                          "`text` TEXT NOT NULL," | ||||
|                                          "`chat_IncHistoryId` INTEGER NOT NULL" | ||||
|                                          ")"); | ||||
|         sqlite_single_statement(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); | ||||
|         sqlite_single_statement(conn.hand, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " | ||||
|         sqlite_nooutput(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); | ||||
|         sqlite_nooutput(conn.hand, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " | ||||
|                                          "(0, ?1, ?2, 0, ?3)", {}, | ||||
|                                          {{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}}); | ||||
|         sqlite_single_statement(conn.hand, "END"); | ||||
|         sqlite_nooutput(conn.hand, "END"); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										89
									
								
								src/web_chat/iu9_ca_web_chat_lib/login_cookie.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/web_chat/iu9_ca_web_chat_lib/login_cookie.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| #include "login_cookie.h" | ||||
| #include <jsonincpp/string_representation.h> | ||||
| #include "str_fields.h" | ||||
| #include <engine_engine_number_9/baza_throw.h> | ||||
| #include <engine_engine_number_9/http_structures/cookies.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     bool is_login_cookie(const std::pair<std::string, std::string>& any_cookie_encoded) { | ||||
|         return een9::beginsWith(any_cookie_encoded.first, "login_") && !any_cookie_encoded.second.empty(); | ||||
|     } | ||||
| 
 | ||||
|     /* Should be verified with iu9cawebchat::is_login_cookie. Can throw std::exception anyway */ | ||||
|     LoginCookie decode_login_cookie(const std::pair<std::string, std::string>& login_cookie_encoded) { | ||||
|         const std::string& ft = login_cookie_encoded.first; | ||||
|         size_t bg = strlen("login_"); | ||||
|         een9_ASSERT_pl(ft.size() >= bg); | ||||
|         size_t s_ = bg; | ||||
|         while (s_ < ft.size() && ft[s_] != '_') | ||||
|             s_++; | ||||
|         een9_ASSERT_pl(s_ + 1 < ft.size()) | ||||
|         uint64_t sec = std::stoull(ft.substr(bg, s_ - bg)); | ||||
|         uint64_t nsec = std::stoull(ft.substr(s_ + 1, ft.size() - s_ - 1)); | ||||
|         een9_ASSERT_pl(nsec < 1000000000); | ||||
|         const json::JSON cnt = json::parse_str_flawless(base64_decode(login_cookie_encoded.second)); | ||||
|         std::string nickname = cnt[0].g().asString(); | ||||
|         std::string password = cnt[1].g().asString(); | ||||
|         return LoginCookie{{(time_t)sec, (time_t)nsec}, nickname, password}; | ||||
|     } | ||||
| 
 | ||||
|     LoginCookie create_login_cookie(const std::string& nickname, const std::string& password) { | ||||
|         ns_time moment; | ||||
|         int ret = clock_gettime(CLOCK_REALTIME, &moment); | ||||
|         een9_ASSERT_on_iret(ret, "Can't get time"); | ||||
|         return {moment, nickname, password}; | ||||
|     } | ||||
| 
 | ||||
|     std::pair<std::string, std::string> encode_login_cookie(const LoginCookie& cookie) { | ||||
|             json::JSON cnt; | ||||
|             cnt[1].g() = cookie.password; | ||||
|             cnt[0].g() = cookie.nickname; | ||||
|         return {"login_" + std::to_string(cookie.login_time.tv_sec) + "_" + std::to_string(cookie.login_time.tv_nsec), | ||||
|             base64_encode(json::generate_str(cnt, json::print_compact))}; | ||||
|     } | ||||
| 
 | ||||
|     bool login_cookie_age_less(const LoginCookie& A, const LoginCookie& B) { | ||||
|         if (A.login_time.tv_sec < B.login_time.tv_sec) | ||||
|             return true; | ||||
|         if (A.login_time.tv_sec > B.login_time.tv_sec) | ||||
|             return false; | ||||
|         return A.login_time.tv_nsec < B.login_time.tv_nsec; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<LoginCookie> select_login_cookies(const std::vector<std::pair<std::string, std::string>> &cookie_list) { | ||||
|         std::vector<LoginCookie> needed; | ||||
|         try { | ||||
|             for (const std::pair<std::string, std::string>& C: cookie_list) | ||||
|                 if (is_login_cookie(C)) | ||||
|                     needed.push_back(decode_login_cookie(C)); | ||||
|         } catch (const std::exception& e) { | ||||
|             return {}; | ||||
|         } | ||||
|         return needed; | ||||
|     } | ||||
| 
 | ||||
|     size_t select_oldest_login_cookie(const std::vector<LoginCookie> &login_cokie_list) { | ||||
|         size_t oldest = 0; | ||||
|         for (size_t i = 1; i < login_cokie_list.size(); i++) | ||||
|             if (login_cookie_age_less(login_cokie_list[i], login_cokie_list[oldest])) | ||||
|                 oldest = i; | ||||
|         return login_cokie_list.empty() ? 0 : oldest; | ||||
|     } | ||||
| 
 | ||||
|     void add_set_cookie_headers_to_login(const std::vector<LoginCookie> &old_login_cookies, | ||||
|         std::vector<std::pair<std::string, std::string>> &response_headers, const LoginCookie& new_login_cookie) { | ||||
|         add_set_cookie_headers_to_logout(old_login_cookies, response_headers); | ||||
|         een9::set_cookie({encode_login_cookie(new_login_cookie)}, response_headers); | ||||
|     } | ||||
| 
 | ||||
|     void add_set_cookie_headers_to_logout(const std::vector<LoginCookie> &old_login_cookies, | ||||
|         std::vector<std::pair<std::string, std::string>> &response_headers) { | ||||
|         std::vector<std::pair<std::string, std::string>> anti_cookies(old_login_cookies.size()); | ||||
|         for (size_t i = 0; i < old_login_cookies.size(); i++) { | ||||
|             anti_cookies[i] = {"login_" + std::to_string(old_login_cookies[i].login_time.tv_sec) + | ||||
|                 "_" + std::to_string(old_login_cookies[i].login_time.tv_nsec), ""}; | ||||
|         } | ||||
|         een9::set_cookie(anti_cookies, response_headers); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/web_chat/iu9_ca_web_chat_lib/login_cookie.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/web_chat/iu9_ca_web_chat_lib/login_cookie.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| #ifndef IU9_CA_WEB_CHAT_LIB_LOGIN_COOKIE_H | ||||
| #define IU9_CA_WEB_CHAT_LIB_LOGIN_COOKIE_H | ||||
| 
 | ||||
| #include <time.h> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     typedef struct timespec ns_time; | ||||
| 
 | ||||
|     struct LoginCookie { | ||||
|         ns_time login_time; | ||||
|         std::string nickname; | ||||
|         std::string password; | ||||
|     }; | ||||
| 
 | ||||
|     bool is_login_cookie(const std::pair<std::string, std::string>& any_cookie_encoded); | ||||
|     LoginCookie decode_login_cookie(const std::pair<std::string, std::string>& login_cookie_encoded); | ||||
|     LoginCookie create_login_cookie(const std::string& nickname, const std::string& password); | ||||
|     std::pair<std::string, std::string> encode_login_cookie(const LoginCookie& cookie); | ||||
| 
 | ||||
|     /* (incorrect cookie set is treated as unloginned user ) */ | ||||
|     std::vector<LoginCookie> select_login_cookies(const std::vector<std::pair<std::string, std::string>> &cookie_list); | ||||
|     size_t select_oldest_login_cookie(const std::vector<LoginCookie>& login_cokie_list); | ||||
| 
 | ||||
|     /* Populates response headers */ | ||||
|     void add_set_cookie_headers_to_login(const std::vector<LoginCookie>& old_login_cookies, | ||||
|         std::vector<std::pair<std::string, std::string>>& response_headers, const LoginCookie& new_login_cookie); | ||||
|     void add_set_cookie_headers_to_logout(const std::vector<LoginCookie>& old_login_cookies, | ||||
|         std::vector<std::pair<std::string, std::string>>& response_headers); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -13,6 +13,8 @@ | ||||
| #include "sqlite3_wrapper.h" | ||||
| #include "str_fields.h" | ||||
| #include "find_db.h" | ||||
| #include "login_cookie.h" | ||||
| #include <engine_engine_number_9/http_structures/cookies.h> | ||||
| 
 | ||||
| namespace iu9cawebchat { | ||||
|     bool termination = false; | ||||
| @ -64,25 +66,94 @@ namespace iu9cawebchat { | ||||
|             nytl::Templater& templater = *wgd.templater; | ||||
|             een9::StaticAsset sa; | ||||
|             int ret; | ||||
|             auto rteee = [&](const std::string& el_name, bool pass_phr) -> std::string { | ||||
|                 std::string page = templater.render(el_name, | ||||
|                     pass_phr ? std::vector<const json::JSON*>{&config_presentation} : std::vector<const json::JSON*>{}); | ||||
|             auto find_user_by_credentials = [&](const std::string& nickname, const std::string& password) -> int64_t { | ||||
|                 SqliteStatement sql_req(*wgd.db, | ||||
|                     "SELECT `id` FROM `user` WHERE `nickname` = ?1 AND `password` = ?2", | ||||
|                     {}, {{1, nickname}, {2, password}}); | ||||
|                 fsql_integer_or_null id_col; | ||||
|                 int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {}); | ||||
|                 if (status == SQLITE_ROW) { | ||||
|                     een9_ASSERT_pl(id_col.exist & id_col.value >= 0); | ||||
|                     return id_col.value; | ||||
|                 } | ||||
|                 return -1; | ||||
|             }; | ||||
|             auto find_user_name = [&](int64_t uid) -> std::string { | ||||
|                 een9_ASSERT(uid >= 0, "Are you crazy?"); | ||||
|                 SqliteStatement sql_req(*wgd.db, | ||||
|                    "SELECT `name` FROM `user` WHERE `id` = ?1", | ||||
|                    {{1, uid}}, {}); | ||||
|                 fsql_text8_or_null name_col; | ||||
|                 int status = sqlite_stmt_step(sql_req, {}, {{0, &name_col}}); | ||||
|                 if (status == SQLITE_ROW) { | ||||
|                     een9_ASSERT_pl(name_col.exist); | ||||
|                     return name_col.value; | ||||
|                 } | ||||
|                 return ""; | ||||
|             }; | ||||
| 
 | ||||
|             std::vector<std::pair<std::string, std::string>> cookies = een9::findAllClientCookies(req.headers); | ||||
|             std::vector<LoginCookie> login_cookies = select_login_cookies(cookies); | ||||
|             json::JSON userinfo; | ||||
|             userinfo["uid"] = json::JSON("-1"); | ||||
|             userinfo["nickname"] = json::JSON(""); | ||||
|             userinfo["name"] = json::JSON(""); | ||||
|             int64_t logged_in_user = -1; /* Negative means that user is not logged in */ | ||||
|             if (!login_cookies.empty()){ | ||||
|                 size_t oldest_ind = select_oldest_login_cookie(login_cookies); | ||||
|                 LoginCookie& tried = login_cookies[oldest_ind]; | ||||
|                 logged_in_user = find_user_by_credentials(tried.nickname, tried.password); | ||||
|                 if (logged_in_user >= 0) { | ||||
|                     userinfo["uid"] = json::JSON(std::to_string(logged_in_user)); | ||||
|                     userinfo["nickname"] = json::JSON(tried.nickname); | ||||
|                     userinfo["name"] = json::JSON(find_user_name(logged_in_user)); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             auto RTEE = [&](const std::string& el_name) -> std::string { | ||||
|                 std::string page = templater.render(el_name, {&config_presentation, &userinfo}); | ||||
|                 return een9::form_http_server_response_200("text/html", page); | ||||
|             }; | ||||
| 
 | ||||
|             if (req.uri_path == "/" || req.uri_path == "/list-rooms") { | ||||
|                 return rteee("list-rooms", false); | ||||
|                 printf("DEBUG:::: %d\n", logged_in_user); | ||||
|                 if (logged_in_user < 0) | ||||
|                     return een9::form_http_server_response_307("/login"); | ||||
|                 return RTEE("list-rooms"); | ||||
|             } | ||||
|             if (req.uri_path == "/login") { | ||||
|                 return rteee("login", true); | ||||
|                 if (req.method == "POST") { | ||||
|                     std::vector<std::pair<std::string, std::string>> query = een9::split_html_query(req.body); | ||||
|                     std::string nickname; | ||||
|                     std::string password; | ||||
|                     for (const std::pair<std::string, std::string>& cmp: query) { | ||||
|                         if (cmp.first == "nickname") | ||||
|                             nickname = cmp.second; | ||||
|                         if (cmp.first == "password") | ||||
|                             password = cmp.second; | ||||
|                     } | ||||
|                     een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname"); | ||||
|                     een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password"); | ||||
|                     int64_t uid = find_user_by_credentials(nickname, password); | ||||
|                     if (uid < 0) { | ||||
|                         /* todo: Here I need to tell somehow to user (through fancy red box, maybe), that login was incorrect */ | ||||
|                         return RTEE("login"); | ||||
|                     } | ||||
|                     std::vector<std::pair<std::string, std::string>> response_hlines; | ||||
|                     LoginCookie new_login_cookie = create_login_cookie(nickname, password); | ||||
|                     add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); | ||||
|                     return een9::form_http_server_response_307_spec_head("/", response_hlines); | ||||
|                 } | ||||
|                 return RTEE("login"); | ||||
|             } | ||||
|             if (req.uri_path == "/chat") { | ||||
|                 return rteee("chat", false); | ||||
|                 return RTEE("chat"); | ||||
|             } | ||||
|             if (req.uri_path == "/profile") { | ||||
|                 return rteee("profile", false); | ||||
|                 return RTEE("profile"); | ||||
|             } | ||||
|             if (req.uri_path == "/registration") { | ||||
|                 return rteee("registration", false); | ||||
|                 return RTEE("registration"); | ||||
|             } | ||||
|             /* Trying to interpret request as asset lookup */ | ||||
|             ret = samI.get_asset(req.uri_path, sa); | ||||
| @ -112,7 +183,7 @@ namespace iu9cawebchat { | ||||
|                     std::string new_password = req.substr(nid + 1); | ||||
|                     if (!check_password(new_password)) | ||||
|                         een9_THROW("Bad password"); | ||||
|                     sqlite_single_statement(wgd.db->hand, | ||||
|                     sqlite_nooutput(wgd.db->hand, | ||||
|                         "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", | ||||
|                         {}, {{1, new_password}}); | ||||
|                     return "Successul update"; | ||||
|  | ||||
| @ -16,7 +16,7 @@ namespace iu9cawebchat { | ||||
|         if (sqlite3_close(hand) != 0) {abort();} | ||||
|     } | ||||
| 
 | ||||
|     void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement, | ||||
|     void sqlite_nooutput(sqlite3* db_hand, const std::string& req_statement, | ||||
|                                  const std::vector<std::pair<int, int64_t>>& int64_binds, | ||||
|                                  const std::vector<std::pair<int, std::string>>& text8_binds) { | ||||
|         sqlite3_stmt* stmt_obj = NULL; | ||||
| @ -26,27 +26,24 @@ namespace iu9cawebchat { | ||||
|             een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + | ||||
|                 ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); | ||||
|         } | ||||
|         een9_ASSERT(ret == 0, "Can't compile request expression"); | ||||
|         assert(sqlite3_errcode(db_hand) == SQLITE_OK); | ||||
|         struct Guard1{sqlite3_stmt*& r; ~Guard1(){if (sqlite3_finalize(r) != 0) {abort();}}} guard1{stmt_obj}; | ||||
|         for (const std::pair<int, int64_t>& bv: int64_binds) { | ||||
|             ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second); | ||||
|             een9_ASSERT(ret, "Can't bind to parameter #" + std::to_string(bv.first)); | ||||
|             een9_ASSERT(ret == 0, "sqlite3_bind_int64"); | ||||
|         } | ||||
|         for (const std::pair<int, std::string>& bv: text8_binds) { | ||||
|             een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter"); | ||||
|             een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge"); | ||||
|             ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC); | ||||
|             een9_ASSERT(ret == 0, "sqlite3_bind_text"); | ||||
|         } | ||||
|         while (true) { | ||||
|             ret = sqlite3_step(stmt_obj); | ||||
|             if (ret == SQLITE_DONE) | ||||
|                 break; | ||||
|             if (ret != SQLITE_ROW) { | ||||
|                 printf("sqlite_row error!!!\n"); | ||||
|                 printf("%s\n", sqlite3_errmsg(db_hand)); | ||||
|                 break; | ||||
|             } | ||||
|             if (ret != SQLITE_ROW) | ||||
|                 een9_THROW(std::string("sqlite_row ") + sqlite3_errstr(ret)); | ||||
|             int cc = sqlite3_column_count(stmt_obj); | ||||
|             std::vector<int> types(cc); | ||||
|             for (int i = 0; i < cc; i++) { | ||||
| @ -84,11 +81,79 @@ namespace iu9cawebchat { | ||||
|                     const unsigned char* text = sqlite3_column_text(stmt_obj, i); | ||||
|                     een9_ASSERT(text, "oom in sqlite3_column_text"); | ||||
|                     printf("%s | ", (const char*)text); | ||||
|                     // todo: THIS F. B.S. IS NOT SAFE
 | ||||
|                     // todo: print only if string is safe to print
 | ||||
|                 } | ||||
|             } | ||||
|             printf("\n"); | ||||
|         } | ||||
|         printf("Request steps are done\n"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     SqliteStatement::SqliteStatement(SqliteConnection &connection, const std::string &req_statement, | ||||
|         const std::vector<std::pair<int, int64_t>> &int64_binds, | ||||
|         const std::vector<std::pair<int, std::string>> &text8_binds): conn(connection) { | ||||
| 
 | ||||
|         int ret = sqlite3_prepare_v2(connection.hand, req_statement.c_str(), -1, &stmt_obj, NULL); | ||||
|         if (ret != 0) { | ||||
|             int err_pos = sqlite3_error_offset(connection.hand); | ||||
|             een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + | ||||
|                 ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); | ||||
|         } | ||||
|         try { | ||||
|             for (const std::pair<int, int64_t>& bv: int64_binds) { | ||||
|                 ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second); | ||||
|                 een9_ASSERT(ret == 0, "sqlite3_bind_int64"); | ||||
|             } | ||||
|             for (const std::pair<int, std::string>& bv: text8_binds) { | ||||
|                 een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter"); | ||||
|                 een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge"); | ||||
|                 ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC); | ||||
|                 een9_ASSERT(ret == 0, "sqlite3_bind_text"); | ||||
|             } | ||||
|         } catch (const std::exception& e) { | ||||
|             sqlite3_finalize(stmt_obj); | ||||
|             throw; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     SqliteStatement::~SqliteStatement() { | ||||
|         sqlite3_finalize(stmt_obj); | ||||
|     } | ||||
| 
 | ||||
|     int sqlite_stmt_step(SqliteStatement &stmt, | ||||
|         const std::vector<std::pair<int, fsql_integer_or_null *>> &ret_of_integer_or_null, | ||||
|         const std::vector<std::pair<int, fsql_text8_or_null *>> &ret_of_text8_or_null) { | ||||
|         int ret = sqlite3_step(stmt.stmt_obj); | ||||
|         if (ret == SQLITE_DONE) | ||||
|             return ret; | ||||
|         een9_ASSERT(ret == SQLITE_ROW, std::string("sqlite3_step ") + sqlite3_errstr(ret)); | ||||
|         int cc = sqlite3_column_count(stmt.stmt_obj); | ||||
|         for (auto& resp: ret_of_integer_or_null) { | ||||
|             if (resp.first >= cc) | ||||
|                 een9_THROW("Not enough"); | ||||
|             int type = sqlite3_column_type(stmt.stmt_obj, resp.first); | ||||
|             if (type == SQLITE_INTEGER) { | ||||
|                 *resp.second = fsql_integer_or_null{true, (int64_t)sqlite3_column_int64(stmt.stmt_obj, resp.first)}; | ||||
|             } else if (type == SQLITE_NULL) { | ||||
|                 *resp.second = fsql_integer_or_null{false, 0}; | ||||
|             } else | ||||
|                 een9_THROW("sqlite3_column_type. Incorrect type"); | ||||
|         } | ||||
|         for (auto& resp: ret_of_text8_or_null) { | ||||
|             if (resp.first >= cc) | ||||
|                 een9_THROW("Not enough"); | ||||
|             int type = sqlite3_column_type(stmt.stmt_obj, resp.first); | ||||
|             if (type == SQLITE_TEXT) { | ||||
|                 /* Hm, yeah, I am reinterpret_casting between char and unsigned char again */ | ||||
|                 const unsigned char* text = sqlite3_column_text(stmt.stmt_obj, resp.first); | ||||
|                 een9_ASSERT(text, "oom in sqlite3_column_text"); | ||||
|                 *resp.second = fsql_text8_or_null{true, reinterpret_cast<const char*>(text)}; | ||||
|             } else if (type == SQLITE_NULL) { | ||||
|                 *resp.second = fsql_text8_or_null{false, ""}; | ||||
|             } else | ||||
|                 een9_THROW("sqlite3_column_type. Incorrect type"); | ||||
|         } | ||||
|         return SQLITE_ROW; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -15,9 +15,35 @@ namespace iu9cawebchat { | ||||
|         ~SqliteConnection(); | ||||
|     }; | ||||
| 
 | ||||
|     void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement, | ||||
|     void sqlite_nooutput(sqlite3* db_hand, const std::string& req_statement, | ||||
|         const std::vector<std::pair<int, int64_t>>& int64_binds= {}, | ||||
|         const std::vector<std::pair<int, std::string>>& text8_binds = {}); | ||||
| 
 | ||||
|     struct fsql_integer_or_null { | ||||
|         bool exist; | ||||
|         int64_t value; | ||||
|     }; | ||||
| 
 | ||||
|     struct fsql_text8_or_null { | ||||
|         bool exist; | ||||
|         std::string value; | ||||
|     }; | ||||
| 
 | ||||
|     struct SqliteStatement { | ||||
|         SqliteConnection& conn; | ||||
|         sqlite3_stmt* stmt_obj = NULL; | ||||
|         SqliteStatement(SqliteConnection& connection, const std::string& req_statement, | ||||
|             const std::vector<std::pair<int, int64_t>>& int64_binds= {}, | ||||
|             const std::vector<std::pair<int, std::string>>& text8_binds = {}); | ||||
|         SqliteStatement(SqliteStatement&) = delete; | ||||
|         SqliteStatement& operator=(SqliteStatement&) = delete; | ||||
| 
 | ||||
|         ~SqliteStatement(); | ||||
|     }; | ||||
| 
 | ||||
|     int sqlite_stmt_step(SqliteStatement& stmt, | ||||
|         const std::vector<std::pair<int, fsql_integer_or_null*>>& ret_of_integer_or_null, | ||||
|         const std::vector<std::pair<int, fsql_text8_or_null*>>& ret_of_text8_or_null); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user