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">
|
||||
@ -34,4 +35,5 @@
|
||||
<script src="/assets/js/list-rooms.js"> </script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
{% ENDELDEF%}
|
@ -1,3 +1,4 @@
|
||||
{% ELDEF main JSON pres JSON userinfo %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
@ -22,4 +23,5 @@
|
||||
|
||||
<script src="assets/js/registration.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</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…
Reference in New Issue
Block a user