From 4f67cf11bd262886f6e288f8bd47964c317cd3e9 Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Fri, 22 Aug 2025 17:46:38 +0700 Subject: [PATCH] added database --- .clang-format | 6 +++ .gitignore | 2 + CMakeLists.txt | 6 ++- Makefile | 2 +- meson.build | 7 ++- src/add_user.c | 31 ++++------- src/change_password.c | 31 ++++------- src/change_user.c | 31 ++++------- src/database.c | 62 ++++++++++++++++++++++ src/database.h | 11 ++++ src/db_get_all_users.c | 99 +++++++++++++++++++++++++++++++++++ src/db_get_user_by_username.c | 44 ++++++++++++++++ src/db_get_users_from_file.c | 50 ++++++++++++++++++ src/delete_user.c | 31 ++++------- src/file.c | 34 ++++++++++++ src/file.h | 3 ++ src/get_all_users.c | 44 ++++++---------- src/get_token.c | 25 +++++++-- src/get_user.c | 61 ++++++++++----------- src/jwt.c | 56 +++++++++----------- src/jwt.h | 2 +- src/main.c | 32 ++++++----- src/register_user.c | 89 ++++++++++++++----------------- src/route.h | 9 ++-- src/sql.h | 19 +++++++ src/user.c | 34 +++++++----- src/user.h | 21 +++++--- src/utils.c | 8 +-- src/verify_token.c | 2 +- users.json | 16 ++++++ 30 files changed, 601 insertions(+), 267 deletions(-) create mode 100644 .clang-format create mode 100644 src/database.c create mode 100644 src/database.h create mode 100644 src/db_get_all_users.c create mode 100644 src/db_get_user_by_username.c create mode 100644 src/db_get_users_from_file.c create mode 100644 src/file.c create mode 100644 src/file.h create mode 100644 src/sql.h create mode 100644 users.json diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..24fab55 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: Google +AllowShortFunctionsOnASingleLine: false +SpaceAfterCStyleCast: true +PointerBindsToType: false +DerivePointerBinding: false +IncludeBlocks: Preserve diff --git a/.gitignore b/.gitignore index 543b94f..5328952 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ .cache .idea build +builddir fmongoose +users.db \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c72be2..e1914cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,15 @@ project(fmongoose LANGUAGES C) set(CMAKE_C_STANDARD 23) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -include_directories(./src/lib/) +include_directories(./src/lib/ ${SQLite3_INCLUDE_DIRS}) FILE(GLOB_RECURSE SOURCES src/*.c) +find_package(SQLite3 REQUIRED) + add_executable( fmongoose ${SOURCES} ) -target_link_libraries(fmongoose ssl crypto) +target_link_libraries(fmongoose ssl crypto sqlite3 pthread) diff --git a/Makefile b/Makefile index a0439b6..f267588 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC = gcc CFLAGS = -Wall -g -std=gnu2x LDFLAGS = -LIBS = -lpthread -lcrypto +LIBS = -lpthread -lcrypto -lsqlite3 INCLUDES = -I./src/lib -I./src SRCS = $(wildcard src/*.c src/lib/*.c) OBJS = $(SRCS:.c=.o) diff --git a/meson.build b/meson.build index 1b50727..79ed8d9 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('fmongoose', 'c', default_options: ['c_std=gnu2x']) include_dirs = include_directories('src', 'src/lib') -deps = dependency('openssl') +deps = [dependency('openssl'), dependency('sqlite3')] executable( 'fmongoose', 'src/main.c', @@ -18,6 +18,11 @@ executable( 'src/jwt.c', 'src/lib/mongoose.c', 'src/lib/cJSON.c', + 'src/database.c', + 'src/db_get_all_users.c', + 'src/db_get_user_by_username.c', + 'src/db_get_users_from_file.c', + 'src/file.c', include_directories: include_dirs, dependencies: deps, ) diff --git a/src/add_user.c b/src/add_user.c index 113a4d6..ed68752 100644 --- a/src/add_user.c +++ b/src/add_user.c @@ -1,26 +1,17 @@ -#include "user.h" #include "jwt.h" +#include "user.h" #include "utils.h" void add_user(struct mg_connection *c, struct mg_http_message *hm) { - request_with_token *requestdata = check_body(hm); - const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); - if (!valid) { - mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), - MG_ESC("invalid token")); - return; - } - char responsedata[2048] = {0}; - snprintf( - responsedata, - sizeof(responsedata), - "{\"success\": true}" - ); + request_with_token *requestdata = check_body(hm); + const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); + if (!valid) { + mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); + return; + } + char responsedata[2048] = {0}; + snprintf(responsedata, sizeof(responsedata), "{\"success\": true}"); - mg_http_reply( - c, - 200, - "Content-Type: application/json\r\n", - responsedata - ); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", responsedata); } diff --git a/src/change_password.c b/src/change_password.c index 894e9d6..9fbd0af 100644 --- a/src/change_password.c +++ b/src/change_password.c @@ -1,26 +1,17 @@ -#include "user.h" #include "jwt.h" +#include "user.h" #include "utils.h" void change_password(struct mg_connection *c, struct mg_http_message *hm) { - request_with_token *requestdata = check_body(hm); - const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); - if (!valid) { - mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), - MG_ESC("invalid token")); - return; - } - char responsedata[2048] = {0}; - snprintf( - responsedata, - sizeof(responsedata), - "{\"success\": true}" - ); + request_with_token *requestdata = check_body(hm); + const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); + if (!valid) { + mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); + return; + } + char responsedata[64] = {0}; + snprintf(responsedata, sizeof(responsedata), "{\"success\": true}"); - mg_http_reply( - c, - 200, - "Content-Type: application/json\r\n", - responsedata - ); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", responsedata); } diff --git a/src/change_user.c b/src/change_user.c index cbacc7c..df91ded 100644 --- a/src/change_user.c +++ b/src/change_user.c @@ -1,26 +1,17 @@ -#include "user.h" #include "jwt.h" +#include "user.h" #include "utils.h" void change_user(struct mg_connection *c, struct mg_http_message *hm) { - request_with_token *requestdata = check_body(hm); - const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); - if (!valid) { - mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), - MG_ESC("invalid token")); - return; - } - char responsedata[2048] = {0}; - snprintf( - responsedata, - sizeof(responsedata), - "{\"success\": true}" - ); + request_with_token *requestdata = check_body(hm); + const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); + if (!valid) { + mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); + return; + } + char responsedata[2048] = {0}; + snprintf(responsedata, sizeof(responsedata), "{\"success\": true}"); - mg_http_reply( - c, - 200, - "Content-Type: application/json\r\n", - responsedata - ); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", responsedata); } diff --git a/src/database.c b/src/database.c new file mode 100644 index 0000000..baaf430 --- /dev/null +++ b/src/database.c @@ -0,0 +1,62 @@ +#include "database.h" +#include +#include +#include "sql.h" +#include + +sqlite3* db = nullptr; +pthread_mutex_t db_mutex = PTHREAD_MUTEX_INITIALIZER; + +void init_db() { + if (sqlite3_open("users.db", &db) != SQLITE_OK) { + fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + exit(1); + } +} + +void migrate_db() { + char *err_msg = 0; + pthread_mutex_lock(&db_mutex); + if (sqlite3_exec(db, CREATE_TABLE_USERS, nullptr, nullptr, &err_msg) != + SQLITE_OK) { + fprintf(stderr, "SQL error: %s\n", err_msg); + sqlite3_free(err_msg); + sqlite3_close(db); + exit(1); + } + pthread_mutex_unlock(&db_mutex); +} + +// Вспомогательная функция для экранирования специальных символов в JSON +char* json_escape(const char* str) { + if (!str) return NULL; + + size_t len = strlen(str); + // Выделяем память с запасом (каждый символ может быть экранирован) + char* escaped = malloc(len * 6 + 1); + if (!escaped) return NULL; + + char* p = escaped; + for (size_t i = 0; i < len; i++) { + switch (str[i]) { + case '\"': strcpy(p, "\\\""); p += 2; break; + case '\\': strcpy(p, "\\\\"); p += 2; break; + case '\b': strcpy(p, "\\b"); p += 2; break; + case '\f': strcpy(p, "\\f"); p += 2; break; + case '\n': strcpy(p, "\\n"); p += 2; break; + case '\r': strcpy(p, "\\r"); p += 2; break; + case '\t': strcpy(p, "\\t"); p += 2; break; + default: + if (str[i] < 0x20) { + sprintf(p, "\\u%04x", (unsigned char)str[i]); + p += 6; + } else { + *p++ = str[i]; + } + } + } + *p = '\0'; + + return escaped; +} \ No newline at end of file diff --git a/src/database.h b/src/database.h new file mode 100644 index 0000000..d518349 --- /dev/null +++ b/src/database.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +extern sqlite3 *db; +extern pthread_mutex_t db_mutex; + +void init_db(); +void migrate_db(); +char* json_escape(const char* str); \ No newline at end of file diff --git a/src/db_get_all_users.c b/src/db_get_all_users.c new file mode 100644 index 0000000..f5198f1 --- /dev/null +++ b/src/db_get_all_users.c @@ -0,0 +1,99 @@ +#include "database.h" +#include "sql.h" +#include "user.h" +#include + +char *db_get_all_users() { + pthread_mutex_lock(&db_mutex); + if (!db) { + fprintf(stderr, "Database not initialized\n"); + pthread_mutex_unlock(&db_mutex); + return nullptr; + } + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_prepare_v2(db, SELECT_ALL_USERS_SQL, -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db)); + pthread_mutex_unlock(&db_mutex); + return nullptr; + } + + char *result = malloc(1024); + if (!result) { + sqlite3_finalize(stmt); + pthread_mutex_unlock(&db_mutex); + return nullptr; + } + + strcpy(result, "["); + size_t result_size = strlen(result); + size_t result_capacity = 1024; + + int first_record = 1; + + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + int id = sqlite3_column_int(stmt, 0); + const unsigned char *username = sqlite3_column_text(stmt, 1); + const unsigned char *email = sqlite3_column_text(stmt, 2); + const unsigned char *group = sqlite3_column_text(stmt, 4); + int is_superuser = sqlite3_column_int(stmt, 5); + + char *escaped_username = json_escape((const char *) username); + char *escaped_email = json_escape((const char *) email); + char *escaped_group = json_escape((const char *) group); + if (!escaped_username || !escaped_email || !escaped_group) { + free(escaped_username); + free(escaped_email); + free(escaped_group); + sqlite3_finalize(stmt); + return NULL; + } + + char record[512]; + if (sqlite3_column_type(stmt, 2) == SQLITE_NULL) { + snprintf(record, sizeof(record), + "%s{\"id\":%d,\"username\":\"%s\",\"email\":\"%s\",\"group\":" + "\"%s\",\"is_superuser\":%s}", + first_record ? "" : ",", id, escaped_username, escaped_email, + escaped_group, is_superuser ? "true" : "false"); + } else { + snprintf(record, sizeof(record), + "%s{\"id\":%d,\"username\":\"%s\",\"email\":\"%s\",\"group\":" + "\"%s\",\"is_superuser\":%s}", + first_record ? "" : ",", id, escaped_username, escaped_email, + escaped_group, is_superuser ? "true" : "false"); + } + + free(escaped_username); + free(escaped_email); + free(escaped_group); + + size_t record_len = strlen(record); + if (result_size + record_len + 10 > result_capacity) { + result_capacity *= 2; + char *new_result = realloc(result, result_capacity); + if (!new_result) { + free(result); + sqlite3_finalize(stmt); + return nullptr; + } + result = new_result; + } + strcat(result, record); + result_size += record_len; + first_record = 0; + } + + if (rc != SQLITE_DONE) { + fprintf(stderr, "Error reading data: %s\n", sqlite3_errmsg(db)); + free(result); + sqlite3_finalize(stmt); + return NULL; + } + strcat(result, "]"); + sqlite3_finalize(stmt); + pthread_mutex_unlock(&db_mutex); + return result; +} \ No newline at end of file diff --git a/src/db_get_user_by_username.c b/src/db_get_user_by_username.c new file mode 100644 index 0000000..5be5f4c --- /dev/null +++ b/src/db_get_user_by_username.c @@ -0,0 +1,44 @@ +#include "database.h" +#include "sql.h" +#include "user.h" + +User *db_get_user_by_username(const char *username) { + User *user = nullptr; + pthread_mutex_lock(&db_mutex); + if (!db) { + fprintf(stderr, "Database not initialized\n"); + pthread_mutex_unlock(&db_mutex); + return nullptr; + } + sqlite3_stmt *stmt; + int rc = + sqlite3_prepare_v2(db, SELECT_USER_BY_USERNAME_SQL, -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + exit(1); + } + + sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); + + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + int id = sqlite3_column_int(stmt, 0); + const unsigned char *db_username = sqlite3_column_text(stmt, 1); + const unsigned char *db_email = sqlite3_column_text(stmt, 2); + const unsigned char *db_password = sqlite3_column_text(stmt, 3); + const unsigned char *db_group = sqlite3_column_text(stmt, 4); + int db_is_superuser = sqlite3_column_int(stmt, 5); + user = new_user(id, (char *) db_username, (char *) db_password, + (char *) db_email, (char *) db_group, db_is_superuser); + } + + if (rc != SQLITE_DONE) { + fprintf(stderr, "Error reading data: %s\n", sqlite3_errmsg(db)); + free_user(user); + sqlite3_finalize(stmt); + return nullptr; + } + + pthread_mutex_unlock(&db_mutex); + return user; +} \ No newline at end of file diff --git a/src/db_get_users_from_file.c b/src/db_get_users_from_file.c new file mode 100644 index 0000000..a435976 --- /dev/null +++ b/src/db_get_users_from_file.c @@ -0,0 +1,50 @@ +#include +#include +#include "sql.h" +#include "user.h" +#include "file.h" +#include "database.h" + +void db_get_users_from_file(const char *path) { + char *users_string = read_file(path); + cJSON *json_array = cJSON_Parse(users_string); + pthread_mutex_lock(&db_mutex); + for (int i = 0; i < cJSON_GetArraySize(json_array); i++) { + cJSON *json_object = cJSON_GetArrayItem(json_array, i); + const char *username = + cJSON_GetObjectItem(json_object, "username")->valuestring; + const char *email = cJSON_GetObjectItem(json_object, "email")->valuestring; + const char *password = + cJSON_GetObjectItem(json_object, "password")->valuestring; + const char *group = cJSON_GetObjectItem(json_object, "group")->valuestring; + const bool is_superuser = + cJSON_GetObjectItem(json_object, "is_superuser")->valueint; + + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, INSERT_USER_SQL, -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + exit(1); + } + + sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, email, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, password, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, group, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 5, is_superuser); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + fprintf(stderr, "Cannot insert data: %s\n", sqlite3_errmsg(db)); + } else { + printf("Record inserted successfully. Row ID: %lld\n", + sqlite3_last_insert_rowid(db)); + } + sqlite3_finalize(stmt); + } + + cJSON_Delete(json_array); + free(users_string); + pthread_mutex_unlock(&db_mutex); +} diff --git a/src/delete_user.c b/src/delete_user.c index a291d25..b35ee49 100644 --- a/src/delete_user.c +++ b/src/delete_user.c @@ -1,26 +1,17 @@ -#include "user.h" #include "jwt.h" +#include "user.h" #include "utils.h" void delete_user(struct mg_connection *c, struct mg_http_message *hm) { - request_with_token *requestdata = check_body(hm); - const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); - if (!valid) { - mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), - MG_ESC("invalid token")); - return; - } - char responsedata[2048] = {0}; - snprintf( - responsedata, - sizeof(responsedata), - "{\"success\": true}" - ); + request_with_token *requestdata = check_body(hm); + const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); + if (!valid) { + mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); + return; + } + char responsedata[2048] = {0}; + snprintf(responsedata, sizeof(responsedata), "{\"success\": true}"); - mg_http_reply( - c, - 200, - "Content-Type: application/json\r\n", - responsedata - ); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", responsedata); } diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..6df353d --- /dev/null +++ b/src/file.c @@ -0,0 +1,34 @@ +#include "file.h" +#include +#include + +char *read_file(const char *path) { + FILE *fp = fopen(path, "rb"); + if (fp == NULL) { + perror("Error opening file"); + return nullptr; + } + + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); + rewind(fp); + + char *buffer = (char *) malloc(file_size + 1); + if (!buffer) { + perror("Error allocating memory"); + fclose(fp); + return nullptr; + } + + size_t bytes_read = fread(buffer, 1, file_size, fp); + if (bytes_read != file_size) { + perror("Error reading file"); + free(buffer); + fclose(fp); + return nullptr; + } + + buffer[file_size] = '\0'; + fclose(fp); + return buffer; +} \ No newline at end of file diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..7503f09 --- /dev/null +++ b/src/file.h @@ -0,0 +1,3 @@ +#pragma once + +char *read_file(const char *path); \ No newline at end of file diff --git a/src/get_all_users.c b/src/get_all_users.c index 4dbe711..e8e1308 100644 --- a/src/get_all_users.c +++ b/src/get_all_users.c @@ -1,3 +1,4 @@ +#include "jwt.h" #include "user.h" #include "utils.h" @@ -7,33 +8,20 @@ void get_all_users(struct mg_connection *c, struct mg_http_message *hm) { mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), MG_ESC(data->error)); return; } - free_request_with_token(data); - - User users[2] = {{.id = 1, - .username = "tola", - .email = "mail@example.com", - .password = "passsword", - .group = "root", - .is_superuser = true}, - {.id = 2, - .username = "lisa", - .email = "mail@example.com", - .password = "asdf", - .group = "users", - .is_superuser = false}}; - char msg[2048] = {0}; - strncpy(msg, "[", sizeof(msg)); - for (int i = 0; i < sizeof(users) / sizeof(User); i++) { - snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), - "{\"%s\":%d,\"%s\":\"%s\",\"%s\":\"%s\",\"%s\":\"%s\",\"%s\":\"%" - "s\",\"%s\":%d}", - "id", users[i].id, "username", users[i].username, "email", - users[i].email, "password", users[i].password, "group", - users[i].group, "is_superuser", users[i].is_superuser); - if (i < sizeof(users) / sizeof(User) - 1) { - strncat(msg, ",", strlen(msg)); - } + if (!jwt_verifyJWT(data->token, JWT_DEFAULT_SECRET)) { + mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); + return; } - strncat(msg, "]\n", strlen(msg)); - mg_http_reply(c, 200, "Content-type: application/json\r\n", msg); + cJSON *payload = jwt_get_payload(data->token); + bool is_superuser = cJSON_GetObjectItem(payload, "is_superuser")->valueint; + if (!is_superuser) { + mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("user is not superuser")); + return; + } + char *all_users = db_get_all_users(); + mg_http_reply(c, 200, "Content-type: application/json\r\n", all_users); + free(all_users); + free_request_with_token(data); } diff --git a/src/get_token.c b/src/get_token.c index d232241..5522a10 100644 --- a/src/get_token.c +++ b/src/get_token.c @@ -1,5 +1,5 @@ -#include "user.h" #include "jwt.h" +#include "user.h" void get_token(struct mg_connection *c, struct mg_http_message *hm) { if (hm->body.len == 0) { @@ -20,9 +20,28 @@ void get_token(struct mg_connection *c, struct mg_http_message *hm) { MG_ESC("must have username and password")); return; } + User *db_user = db_get_user_by_username(username->valuestring); + if (!db_user) { + mg_http_reply(c, 400, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("couldn't find user")); + return; + } + if (strcmp(db_user->password, password->valuestring)) { + mg_http_reply(c, 400, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("wrong password")); + return; + } const char *header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; - const char *payload = "{\"sub\": \"1234567890\",\"username\": " - "\"tola\",\"is_superuser\": true,\"iat\": 1516239022}"; + char payload[1024]; + snprintf(payload, sizeof(payload), + "{\"id\": %d,\"username\": " + "\"%s\",\"email\": " + "\"%s\",\"group\": " + "\"%s\",\"is_superuser\": %s}", + db_user->id, db_user->username, db_user->email, db_user->group, + db_user->is_superuser ? "true" : "false"); char *token = jwt_createJWT(header, payload, JWT_DEFAULT_SECRET); mg_http_reply(c, 200, "", "{%m:%m}", MG_ESC("token"), MG_ESC(token)); + free_user(db_user); + free(token); } diff --git a/src/get_user.c b/src/get_user.c index 248355b..0a26199 100644 --- a/src/get_user.c +++ b/src/get_user.c @@ -1,36 +1,37 @@ -#include "user.h" #include "jwt.h" +#include "user.h" #include "utils.h" void get_user(struct mg_connection *c, struct mg_http_message *hm) { - request_with_token *requestdata = check_body(hm); - const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); - if (!valid) { - mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), - MG_ESC("invalid token")); - return; - } - const User *user = new_user("username", "", "mail@example.com", "root", true); - if (user == NULL) { - mg_http_reply(c, 404, "", "{%m:%m}", MG_ESC("error"), MG_ESC("user not found")); - return; - } - char responsedata[2048] = {0}; - snprintf( - responsedata, - sizeof(responsedata), - "{\"id\": %d, \"username\": \"%s\", \"email\": \"%s\", \"group\": \"%s\", \"is_superuser\": %s}", - user->id, - user->username, - user->email, - user->group, - user->is_superuser ? "true" : "false" - ); + request_with_token *requestdata = check_body(hm); + if (!jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET)) { + free_request_with_token(requestdata); + mg_http_reply(c, 403, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); + return; + } + cJSON *payload = jwt_get_payload(requestdata->token); + char *username = + strdup(cJSON_GetObjectItem(payload, "username")->valuestring); + User *user = db_get_user_by_username(username); + if (!user) { + free_request_with_token(requestdata); + cJSON_Delete(payload); + free(username); + mg_http_reply(c, 404, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("user not found")); + return; + } + char responsedata[2048] = {0}; + snprintf(responsedata, sizeof(responsedata), + "{\"id\": %d, \"username\": \"%s\", \"email\": \"%s\", \"group\": " + "\"%s\", \"is_superuser\": %s}", + user->id, user->username, user->email, user->group, + user->is_superuser ? "true" : "false"); - mg_http_reply( - c, - 200, - "Content-Type: application/json\r\n", - responsedata - ); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", responsedata); + free_request_with_token(requestdata); + free(username); + free_user(user); + cJSON_Delete(payload); } diff --git a/src/jwt.c b/src/jwt.c index f6b2cd7..f5c8c3b 100644 --- a/src/jwt.c +++ b/src/jwt.c @@ -21,7 +21,7 @@ static void base64_to_base64url(char *str) { else if (*p == '/') *p = '_'; } - remove_padding(str); // Удаляем паддинг + remove_padding(str); // Удаляем паддинг } // Преобразует Base64URL в Base64 @@ -57,28 +57,27 @@ char *jwt_base64url_encode(const unsigned char *input, const int length) { BUF_MEM *bufferPtr; BIO_get_mem_ptr(bio, &bufferPtr); - const auto result = (char *)malloc(bufferPtr->length + 1); + const auto result = (char *) malloc(bufferPtr->length + 1); memcpy(result, bufferPtr->data, bufferPtr->length); result[bufferPtr->length] = '\0'; BIO_free_all(bio); - base64_to_base64url(result); // Преобразование в Base64URL + base64_to_base64url(result); // Преобразование в Base64URL return result; } // Base64URL декодирование unsigned char *jwt_base64url_decode(const char *input, int input_len, int *output_len) { - if (input_len == 0) - input_len = strlen(input); + if (input_len == 0) input_len = strlen(input); // Создаем копию для преобразования - const auto input_copy = (char *)malloc(input_len + 1); + const auto input_copy = (char *) malloc(input_len + 1); strncpy(input_copy, input, input_len); input_copy[input_len] = '\0'; - base64url_to_base64(input_copy); // Преобразование в Base64 + base64url_to_base64(input_copy); // Преобразование в Base64 BIO *b64 = BIO_new(BIO_f_base64()); BIO *bio = BIO_new_mem_buf(input_copy, -1); @@ -86,7 +85,7 @@ unsigned char *jwt_base64url_decode(const char *input, int input_len, BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); - const auto buffer = (unsigned char *)malloc(input_len); + const auto buffer = (unsigned char *) malloc(input_len); const int len = BIO_read(bio, buffer, input_len); BIO_free_all(bio); @@ -105,13 +104,13 @@ unsigned char *jwt_base64url_decode(const char *input, int input_len, // Создание JWT токена char *jwt_createJWT(const char *header_json, const char *payload_json, const char *secret) { - char *encodedHeader = jwt_base64url_encode((const unsigned char *)header_json, - strlen(header_json)); + char *encodedHeader = jwt_base64url_encode( + (const unsigned char *) header_json, strlen(header_json)); char *encodedPayload = jwt_base64url_encode( - (const unsigned char *)payload_json, strlen(payload_json)); + (const unsigned char *) payload_json, strlen(payload_json)); char *unsignedToken = - (char *)malloc(strlen(encodedHeader) + strlen(encodedPayload) + 2); + (char *) malloc(strlen(encodedHeader) + strlen(encodedPayload) + 2); sprintf(unsignedToken, "%s.%s", encodedHeader, encodedPayload); unsigned int digest_len; @@ -120,7 +119,7 @@ char *jwt_createJWT(const char *header_json, const char *payload_json, char *encodedSignature = jwt_base64url_encode(digest, digest_len); char *jwt = - (char *)malloc(strlen(unsignedToken) + strlen(encodedSignature) + 2); + (char *) malloc(strlen(unsignedToken) + strlen(encodedSignature) + 2); sprintf(jwt, "%s.%s", unsignedToken, encodedSignature); free(encodedHeader); @@ -135,8 +134,8 @@ char *jwt_createJWT(const char *header_json, const char *payload_json, // Генерация HMAC-SHA256 unsigned char *jwt_hmac_sha256(const char *key, int key_len, const char *data, int data_len, unsigned int *digest_len) { - unsigned char *digest = (unsigned char *)malloc(EVP_MAX_MD_SIZE); - HMAC(EVP_sha256(), key, key_len, (const unsigned char *)data, data_len, + unsigned char *digest = (unsigned char *) malloc(EVP_MAX_MD_SIZE); + HMAC(EVP_sha256(), key, key_len, (const unsigned char *) data, data_len, digest, digest_len); return digest; } @@ -145,24 +144,23 @@ unsigned char *jwt_hmac_sha256(const char *key, int key_len, const char *data, bool jwt_verifyJWT(const char *token, const char *secret) { const char *dot1 = strchr(token, '.'); const char *dot2 = dot1 ? strchr(dot1 + 1, '.') : nullptr; - if (!dot1 || !dot2) - return false; + if (!dot1 || !dot2) return false; size_t header_len = dot1 - token; size_t payload_len = dot2 - dot1 - 1; size_t sig_len = strlen(dot2 + 1); - char *header = (char *)malloc(header_len + 1); + char *header = (char *) malloc(header_len + 1); strncpy(header, token, header_len); header[header_len] = '\0'; - char *payload = (char *)malloc(payload_len + 1); + char *payload = (char *) malloc(payload_len + 1); strncpy(payload, dot1 + 1, payload_len); payload[payload_len] = '\0'; char *signature = strndup(dot2 + 1, sig_len); - char *signing_input = (char *)malloc(header_len + payload_len + 2); + char *signing_input = (char *) malloc(header_len + payload_len + 2); sprintf(signing_input, "%s.%s", header, payload); int expected_sig_len; @@ -183,7 +181,7 @@ bool jwt_verifyJWT(const char *token, const char *secret) { int payload_bin_len; unsigned char *payload_bin = jwt_base64url_decode(payload, payload_len, &payload_bin_len); - char *payload_str = (char *)malloc(payload_bin_len + 1); + char *payload_str = (char *) malloc(payload_bin_len + 1); memcpy(payload_str, payload_bin, payload_bin_len); payload_str[payload_bin_len] = '\0'; @@ -192,7 +190,7 @@ bool jwt_verifyJWT(const char *token, const char *secret) { cJSON *exp = cJSON_GetObjectItemCaseSensitive(json_payload, "exp"); if (cJSON_IsNumber(exp)) { time_t now = time(nullptr); - if (now >= (time_t)exp->valuedouble) { + if (now >= (time_t) exp->valuedouble) { exp_valid = false; } } @@ -216,18 +214,17 @@ bool jwt_verifyJWT(const char *token, const char *secret) { cJSON *jwt_get_payload(const char *token) { const char *dot1 = strchr(token, '.'); const char *dot2 = dot1 ? strchr(dot1 + 1, '.') : nullptr; - if (!dot1 || !dot2) - return nullptr; + if (!dot1 || !dot2) return nullptr; size_t payload_len = dot2 - dot1 - 1; - char *payload = (char *)malloc(payload_len + 1); + char *payload = (char *) malloc(payload_len + 1); strncpy(payload, dot1 + 1, payload_len); payload[payload_len] = '\0'; int payload_bin_len; unsigned char *payload_bin = jwt_base64url_decode(payload, payload_len, &payload_bin_len); - char *payload_str = (char *)malloc(payload_bin_len + 1); + char *payload_str = (char *) malloc(payload_bin_len + 1); memcpy(payload_str, payload_bin, payload_bin_len); payload_str[payload_bin_len] = '\0'; @@ -243,18 +240,17 @@ cJSON *jwt_get_payload(const char *token) { // Получение header из токена cJSON *jwt_get_header(const char *token) { const char *dot1 = strchr(token, '.'); - if (!dot1) - return nullptr; + if (!dot1) return nullptr; size_t header_len = dot1 - token; - char *header = (char *)malloc(header_len + 1); + char *header = (char *) malloc(header_len + 1); strncpy(header, token, header_len); header[header_len] = '\0'; int header_bin_len; unsigned char *header_bin = jwt_base64url_decode(header, header_len, &header_bin_len); - char *header_str = (char *)malloc(header_bin_len + 1); + char *header_str = (char *) malloc(header_bin_len + 1); memcpy(header_str, header_bin, header_bin_len); header_str[header_bin_len] = '\0'; diff --git a/src/jwt.h b/src/jwt.h index ca0a7a0..7e03307 100644 --- a/src/jwt.h +++ b/src/jwt.h @@ -21,4 +21,4 @@ bool jwt_verifyJWT(const char *token, const char *secret); cJSON *jwt_get_payload(const char *token); cJSON *jwt_get_header(const char *token); -#endif // JWT_H +#endif // JWT_H diff --git a/src/main.c b/src/main.c index bf30e03..0ed524b 100644 --- a/src/main.c +++ b/src/main.c @@ -1,10 +1,13 @@ -#include "user.h" #include +#include +#include "database.h" #include "route.h" +#include "sql.h" +#include "user.h" static void root_handler(struct mg_connection *c, struct mg_http_message *hm) { - struct mg_http_serve_opts opts = {.root_dir = "./web/"}; - mg_http_serve_dir(c, hm, &opts); + struct mg_http_serve_opts opts = {.root_dir = "./web/"}; + mg_http_serve_dir(c, hm, &opts); } static const struct route routes[] = { @@ -18,28 +21,33 @@ static const struct route routes[] = { {"POST", "/api/users/change", change_password}, {"POST", "/api/users/add", add_user}, {"POST", "/api/register", register_user}, - {nullptr, nullptr, nullptr} -}; + {nullptr, nullptr, nullptr}}; void ev_handler(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { - const auto hm = (struct mg_http_message *)ev_data; + const auto hm = (struct mg_http_message *) ev_data; bool route_found = false; for (int i = 0; routes[i].path != nullptr; i++) { - if (mg_match(hm->uri, mg_str(routes[i].path), nullptr) && mg_match(hm->method, mg_str(routes[i].method), nullptr)) { - routes[i].handler(c, hm); - route_found = true; - break; - } + if (mg_match(hm->uri, mg_str(routes[i].path), nullptr) && + mg_match(hm->method, mg_str(routes[i].method), nullptr)) { + routes[i].handler(c, hm); + route_found = true; + break; + } } if (!route_found) { - mg_http_reply(c, 404, "", "{%m:%m}", MG_ESC("error"), MG_ESC("URI not found")); + mg_http_reply(c, 404, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("URI not found")); } } } int main(void) { + init_db(); + migrate_db(); + db_get_users_from_file("users.json"); + struct mg_mgr mgr; mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://0.0.0.0:8222", ev_handler, nullptr); diff --git a/src/register_user.c b/src/register_user.c index 52054b3..6eadf46 100644 --- a/src/register_user.c +++ b/src/register_user.c @@ -1,55 +1,44 @@ #include "user.h" void register_user(struct mg_connection *c, struct mg_http_message *hm) { - if (hm->body.len == 0) { - mg_http_reply(c, 400, "", "{%m:%m}", MG_ESC("error"), - MG_ESC("must have , , , , ")); - return; - } - cJSON *jsonbody = cJSON_Parse(hm->body.buf); - cJSON *username = cJSON_GetObjectItem(jsonbody, "username"); - cJSON *password = cJSON_GetObjectItem(jsonbody, "password"); - cJSON *email = cJSON_GetObjectItem(jsonbody, "email"); - cJSON *group = cJSON_GetObjectItem(jsonbody, "group"); - cJSON *is_superuser = cJSON_GetObjectItem(jsonbody, "is_superuser"); - if (username == nullptr || password == nullptr || email == nullptr) { - mg_http_reply(c, 400, "", "{%m:%m}", MG_ESC("error"), - MG_ESC("must have , , , , ")); - return; - } - if (group == nullptr) { - group = cJSON_CreateString("users"); - } - if (is_superuser == nullptr) { - is_superuser = cJSON_CreateBool(false); - } - const User *new_user_data = new_user( - username->valuestring, - password->valuestring, - email->valuestring, - group->valuestring, - is_superuser->valueint - ); - if (new_user_data == nullptr) { - mg_http_reply(c, 500, "", "{%m:%m}", MG_ESC("error"), MG_ESC("failed to create user")); - return; - } - char responsedata[2048] = {0}; - snprintf( - responsedata, - sizeof(responsedata), - "{\"id\": %d, \"username\": \"%s\", \"email\": \"%s\", \"group\": \"%s\", \"is_superuser\": %d}", - new_user_data->id, - new_user_data->username, - new_user_data->email, - new_user_data->group, - new_user_data->is_superuser - ); + if (hm->body.len == 0) { + mg_http_reply(c, 400, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("must have , , , , ")); + return; + } + cJSON *jsonbody = cJSON_Parse(hm->body.buf); + cJSON *username = cJSON_GetObjectItem(jsonbody, "username"); + cJSON *password = cJSON_GetObjectItem(jsonbody, "password"); + cJSON *email = cJSON_GetObjectItem(jsonbody, "email"); + cJSON *group = cJSON_GetObjectItem(jsonbody, "group"); + cJSON *is_superuser = cJSON_GetObjectItem(jsonbody, "is_superuser"); + if (username == nullptr || password == nullptr || email == nullptr) { + mg_http_reply(c, 400, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("must have , , , , ")); + return; + } + if (group == nullptr) { + group = cJSON_CreateString("users"); + } + if (is_superuser == nullptr) { + is_superuser = cJSON_CreateBool(false); + } + const User *new_user_data = + new_user(0, username->valuestring, password->valuestring, email->valuestring, + group->valuestring, is_superuser->valueint); + if (new_user_data == nullptr) { + mg_http_reply(c, 500, "", "{%m:%m}", MG_ESC("error"), + MG_ESC("failed to create user")); + return; + } + char responsedata[2048] = {0}; + snprintf(responsedata, sizeof(responsedata), + "{\"id\": %d, \"username\": \"%s\", \"email\": \"%s\", \"group\": " + "\"%s\", \"is_superuser\": %d}", + new_user_data->id, new_user_data->username, new_user_data->email, + new_user_data->group, new_user_data->is_superuser); - mg_http_reply( - c, - 200, - "Content-Type: application/json\r\n", - responsedata - ); + mg_http_reply(c, 200, "Content-Type: application/json\r\n", responsedata); } diff --git a/src/route.h b/src/route.h index e97108b..dd4b130 100644 --- a/src/route.h +++ b/src/route.h @@ -1,9 +1,10 @@ #pragma once -typedef void (*route_handler_t)(struct mg_connection *c, struct mg_http_message *hm); +typedef void (*route_handler_t)(struct mg_connection *c, + struct mg_http_message *hm); struct route { - char *method; - char *path; - route_handler_t handler; + char *method; + char *path; + route_handler_t handler; }; diff --git a/src/sql.h b/src/sql.h new file mode 100644 index 0000000..4c364e6 --- /dev/null +++ b/src/sql.h @@ -0,0 +1,19 @@ +#pragma once + +#define CREATE_TABLE_USERS \ + "create table if not exists users(" \ + "id integer primary key autoincrement," \ + "username text unique," \ + "email char(50) unique," \ + "password char(150) not null," \ + "user_group char(50) default 'users'," \ + "is_superuser boolean default false" \ + ");" + +#define INSERT_USER_SQL \ + "INSERT INTO users (username, email, password, user_group, is_superuser) " \ + "VALUES (?, ?, ?, ?, ?)" + +#define SELECT_ALL_USERS_SQL "select * from users" +#define SELECT_USER_BY_USERNAME_SQL "select * from users where username = ?" + diff --git a/src/user.c b/src/user.c index f1ee3bc..a6bd83e 100644 --- a/src/user.c +++ b/src/user.c @@ -1,6 +1,4 @@ #include "user.h" -#include "jwt.h" -#include "utils.h" #include #include @@ -26,15 +24,25 @@ void free_request_with_token(request_with_token *data) { free(data); } -User *new_user(const char *username, const char *password, const char *email, const char *group, bool is_superuser) { - User *user = malloc(sizeof(User)); - if (user == NULL) { - return NULL; - } - user->username = strdup(username); - user->password = strdup(password); - user->email = strdup(email); - user->group = strdup(group); - user->is_superuser = is_superuser; - return user; +User *new_user(int id, const char *username, const char *password, const char *email, + const char *group, bool is_superuser) { + User *user = malloc(sizeof(User)); + if (user == NULL) { + return NULL; + } + user->id = id; + user->username = strdup(username); + user->password = strdup(password); + user->email = strdup(email); + user->group = strdup(group); + user->is_superuser = is_superuser; + return user; +} + +void free_user(User *user) { + free(user->username); + free(user->password); + free(user->email); + free(user->group); + free(user); } diff --git a/src/user.h b/src/user.h index 6b37af7..2e94960 100644 --- a/src/user.h +++ b/src/user.h @@ -2,6 +2,7 @@ #include #include +#include typedef struct User { int id; @@ -12,15 +13,18 @@ typedef struct User { bool is_superuser; } User; -User *new_user(const char *username, const char *password, const char *email, const char *group, bool is_superuser); +User *new_user(int id, const char *username, const char *password, const char *email, + const char *group, bool is_superuser); + +void free_user(User *user); typedef struct register_request { - char *username; - char *email; - char *password; - char *group; - bool is_superuser; - char *error; + char *username; + char *email; + char *password; + char *group; + bool is_superuser; + char *error; } register_request; typedef struct request_with_token { @@ -48,3 +52,6 @@ void change_password(struct mg_connection *c, struct mg_http_message *hm); void register_user(struct mg_connection *c, struct mg_http_message *hm); void get_token(struct mg_connection *c, struct mg_http_message *hm); void verify_token(struct mg_connection *c, struct mg_http_message *hm); +void db_get_users_from_file(const char *filename); +char *db_get_all_users(); +User *db_get_user_by_username(const char *username); diff --git a/src/utils.c b/src/utils.c index 3ef66ce..f35ca32 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,11 +1,11 @@ #include "utils.h" -#include "user.h" #include #include +#include "user.h" request_with_token *check_body(const struct mg_http_message *hm) { if (hm->body.len == 0) { - return new_request_with_token(nullptr, nullptr, (char *)"must have token"); + return new_request_with_token(nullptr, nullptr, (char *) "must have token"); } cJSON *json_body = cJSON_Parse(hm->body.buf); @@ -13,9 +13,9 @@ request_with_token *check_body(const struct mg_http_message *hm) { if (token == NULL) { const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { - return new_request_with_token(nullptr, nullptr, (char *)error_ptr); + return new_request_with_token(nullptr, nullptr, (char *) error_ptr); } - return new_request_with_token(nullptr, nullptr, (char *)"must have token"); + return new_request_with_token(nullptr, nullptr, (char *) "must have token"); } return new_request_with_token(strdup(token->valuestring), json_body, nullptr); } diff --git a/src/verify_token.c b/src/verify_token.c index 9993b12..4c605e6 100644 --- a/src/verify_token.c +++ b/src/verify_token.c @@ -1,5 +1,5 @@ -#include "user.h" #include "jwt.h" +#include "user.h" #include "utils.h" void verify_token(struct mg_connection *c, struct mg_http_message *hm) { diff --git a/users.json b/users.json new file mode 100644 index 0000000..6ad4817 --- /dev/null +++ b/users.json @@ -0,0 +1,16 @@ +[ + { + "username": "tola", + "email": "mail@example.com", + "password": "2808", + "group": "root", + "is_superuser": true + }, + { + "username": "lisa", + "email": "lisa@example.com", + "password": "asdf", + "group": "users", + "is_superuser": false + } +]