From 56d670c9b6e8ee48dfec4aa08b983b7c28f658bb Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Sat, 30 Aug 2025 12:08:57 +0700 Subject: [PATCH] added field change and get and updated tests --- meson.build | 2 + src/change_password.c | 79 ++++++++++++++++------------------- src/change_user.c | 36 ++++++++++------ src/db_get_user_by_id.c | 14 +++---- src/db_get_user_by_username.c | 6 +-- src/db_get_user_field_by_id.c | 45 ++++++++++++++++++++ src/db_update_user_field.c | 35 ++++++++++++++++ src/sql.h | 3 +- src/user.h | 6 ++- tests.sh | 68 ++++++++++++++++++++++++++++++ 10 files changed, 225 insertions(+), 69 deletions(-) create mode 100644 src/db_get_user_field_by_id.c create mode 100644 src/db_update_user_field.c diff --git a/meson.build b/meson.build index ce23cdb..4c3f6eb 100644 --- a/meson.build +++ b/meson.build @@ -31,6 +31,8 @@ executable( 'src/config.c', 'src/static.c', 'src/cors.c', + 'src/db_get_user_field_by_id.c', + 'src/db_update_user_field.c', include_directories: include_dirs, dependencies: deps, ) diff --git a/src/change_password.c b/src/change_password.c index 773fc4b..a73d117 100644 --- a/src/change_password.c +++ b/src/change_password.c @@ -1,13 +1,24 @@ +#include "cJSON.h" #include "jwt.h" #include "user.h" #include "utils.h" void change_password(struct mg_connection *c, struct mg_http_message *hm) { + if (hm->body.len == 0) { + mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("must have token, old_password, new_password")); + return; + } request_with_token *requestdata = check_body(hm); + if (requestdata->error != NULL) { + mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC(requestdata->error)); + return; + } const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); if (!valid) { - mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("invalid token")); + mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); return; } cJSON *jsonbody = cJSON_Parse(hm->body.buf); @@ -16,37 +27,31 @@ void change_password(struct mg_connection *c, struct mg_http_message *hm) { cJSON *userid = cJSON_GetObjectItem(jsonbody, "userid"); if ((old_password == nullptr || new_password == nullptr) && userid == nullptr) { - mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), + mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), MG_ESC("must have , ")); return; } cJSON *payload = jwt_get_payload(requestdata->token); if (!userid) { - char *username = cJSON_GetObjectItem(payload, "username")->valuestring; - User *old_user_data = db_get_user_by_username(username); - if (!old_user_data) { - mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), + cJSON *id = cJSON_GetObjectItem(payload, "id"); + char *password = db_get_user_field_by_id(id->valueint, "password"); + if (!password) { + mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), MG_ESC("failed to update user can't get old_user_data")); cJSON_Delete(payload); - } - char *password = old_user_data->password; - - if (strcmp(password, old_password->valuestring)) { - mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("invalid old password")); return; } - const bool old_is_superuser = old_user_data->is_superuser; - User *new_user_data = db_update_user( - old_user_data->id, old_user_data->username, old_user_data->email, - new_password->valuestring, old_user_data->group, &old_is_superuser); - if (!new_user_data) { - mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("failed to update user can't update database")); - free_user(old_user_data); + + if (strcmp(password, old_password->valuestring)) { + mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid old password")); + return; + } + if (!db_update_user_field(id->valueint, "password", + new_password->valuestring)) { + mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("failed to update user can't update database")); cJSON_Delete(payload); return; } @@ -55,33 +60,21 @@ void change_password(struct mg_connection *c, struct mg_http_message *hm) { snprintf(responsedata, sizeof(responsedata), "{\"success\": true}"); mg_http_reply(c, 200, HTTP_HEADERS, responsedata); - free_user(old_user_data); cJSON_Delete(payload); return; } bool is_superuser = cJSON_GetObjectItem(payload, "is_superuser")->valueint; if (!is_superuser) { - mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("user is not superuser")); + mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("user is not superuser")); cJSON_Delete(payload); return; } - User *old_user_data = db_get_user_by_id(userid->valueint); - if (!old_user_data) { - mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), - MG_ESC("failed to update user can't get old_user_data")); - cJSON_Delete(payload); - return; - } - const bool old_is_superuser = old_user_data->is_superuser; - User *new_user_data = db_update_user( - old_user_data->id, old_user_data->username, old_user_data->email, - new_password->valuestring, old_user_data->group, &old_is_superuser); - if (!new_user_data) { - mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("failed to update user can't create new_user_data from root")); - free_user(old_user_data); + if (!db_update_user_field(userid->valueint, "password", + new_password->valuestring)) { + mg_http_reply( + c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("failed to update user can't create new_user_data from root")); cJSON_Delete(payload); return; } @@ -89,7 +82,5 @@ void change_password(struct mg_connection *c, struct mg_http_message *hm) { snprintf(responsedata, sizeof(responsedata), "{\"success\": true}"); mg_http_reply(c, 200, HTTP_HEADERS, responsedata); - free_user(old_user_data); - free_user(new_user_data); cJSON_Delete(payload); } diff --git a/src/change_user.c b/src/change_user.c index 7705407..7d260be 100644 --- a/src/change_user.c +++ b/src/change_user.c @@ -3,11 +3,21 @@ #include "utils.h" void change_user(struct mg_connection *c, struct mg_http_message *hm) { + if (hm->body.len == 0) { + mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("must have token, new_user_data")); + return; + } request_with_token *requestdata = check_body(hm); + if (requestdata->error != NULL) { + mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC(requestdata->error)); + return; + } const bool valid = jwt_verifyJWT(requestdata->token, JWT_DEFAULT_SECRET); if (!valid) { - mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("invalid token")); + mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("invalid token")); return; } cJSON *jsonbody = cJSON_Parse(hm->body.buf); @@ -23,20 +33,19 @@ void change_user(struct mg_connection *c, struct mg_http_message *hm) { old_user_data = db_get_user_by_id(userid->valueint); } if (!old_user_data) { - mg_http_reply(c, 404, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("user not found")); + mg_http_reply(c, 404, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("user not found")); return; } char *password = old_user_data->password; if (!password) { - mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("password is required")); + mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("password is required")); return; } if (username == nullptr || email == nullptr || is_superuser == nullptr || group == nullptr) { - mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), + mg_http_reply(c, 400, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), MG_ESC("must have , , , ")); free_user(old_user_data); @@ -52,8 +61,8 @@ void change_user(struct mg_connection *c, struct mg_http_message *hm) { group->valuestring, &user_is_superuser); if (!updated_user_data) { - mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("failed to update user change user request")); + mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("failed to update user change user request")); cJSON_Delete(payload); free_user(old_user_data); return; @@ -75,8 +84,8 @@ void change_user(struct mg_connection *c, struct mg_http_message *hm) { bool req_is_superuser = cJSON_GetObjectItem(payload, "is_superuser")->valueint; if (!req_is_superuser) { - mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", - MG_ESC("error"), MG_ESC("user is not superuser")); + mg_http_reply(c, 403, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"), + MG_ESC("user is not superuser")); cJSON_Delete(payload); free_user(old_user_data); return; @@ -86,7 +95,8 @@ void change_user(struct mg_connection *c, struct mg_http_message *hm) { group->valuestring, &user_is_superuser); if (!updated_user_data) { mg_http_reply(c, 500, "Content-Type: application/json\r\n", "{%m:%m}", - MG_ESC("error"), MG_ESC("failed to update user can't change userdata")); + MG_ESC("error"), + MG_ESC("failed to update user can't change userdata")); cJSON_Delete(payload); free_user(old_user_data); return; diff --git a/src/db_get_user_by_id.c b/src/db_get_user_by_id.c index dc8d86e..acd759e 100644 --- a/src/db_get_user_by_id.c +++ b/src/db_get_user_by_id.c @@ -1,9 +1,9 @@ -#include "user.h" #include "database.h" #include "sql.h" +#include "user.h" User *db_get_user_by_id(const int id) { - User *user = nullptr; + User *user = nullptr; pthread_mutex_lock(&db_mutex); if (!db) { fprintf(stderr, "Database not initialized\n"); @@ -11,12 +11,11 @@ User *db_get_user_by_id(const int id) { return nullptr; } sqlite3_stmt *stmt; - int rc = - sqlite3_prepare_v2(db, SELECT_USER_BY_ID_SQL, -1, &stmt, nullptr); + int rc = sqlite3_prepare_v2(db, SELECT_USER_BY_ID_SQL, -1, &stmt, nullptr); if (rc != SQLITE_OK) { fprintf(stderr, "Cannot prepare statement: %s\n", sqlite3_errmsg(db)); - sqlite3_close(db); - exit(1); + pthread_mutex_unlock(&db_mutex); + return nullptr; } sqlite3_bind_int(stmt, 1, id); @@ -36,9 +35,10 @@ User *db_get_user_by_id(const int id) { fprintf(stderr, "Error reading data: %s\n", sqlite3_errmsg(db)); free_user(user); sqlite3_finalize(stmt); + pthread_mutex_unlock(&db_mutex); return nullptr; } pthread_mutex_unlock(&db_mutex); return user; -} \ No newline at end of file +} diff --git a/src/db_get_user_by_username.c b/src/db_get_user_by_username.c index 5be5f4c..90a5544 100644 --- a/src/db_get_user_by_username.c +++ b/src/db_get_user_by_username.c @@ -15,8 +15,8 @@ User *db_get_user_by_username(const char *username) { 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); + pthread_mutex_unlock(&db_mutex); + return nullptr; } sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); @@ -41,4 +41,4 @@ User *db_get_user_by_username(const char *username) { pthread_mutex_unlock(&db_mutex); return user; -} \ No newline at end of file +} diff --git a/src/db_get_user_field_by_id.c b/src/db_get_user_field_by_id.c new file mode 100644 index 0000000..7c70125 --- /dev/null +++ b/src/db_get_user_field_by_id.c @@ -0,0 +1,45 @@ +#include +#include "database.h" +#include "sql.h" +#include "user.h" + +char *db_get_user_field_by_id(const int id, const char *field) { + char *password = nullptr; + pthread_mutex_lock(&db_mutex); + if (!db) { + fprintf(stderr, "Database not initialized\n"); + pthread_mutex_unlock(&db_mutex); + return nullptr; + } + char sql[256]; + snprintf(sql, sizeof(sql), SELECT_USER_FIELD_SQL, field); + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, 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; + } + sqlite3_bind_int(stmt, 1, id); + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const unsigned char *db_password = sqlite3_column_text(stmt, 0); + password = malloc(strlen((const char *) db_password) + 1); + if (password) { + strcpy(password, (const char *) db_password); + } + sqlite3_finalize(stmt); + } else if (rc == SQLITE_DONE) { + fprintf(stderr, "Can't find user"); + sqlite3_finalize(stmt); + pthread_mutex_unlock(&db_mutex); + return nullptr; + } else { + fprintf(stderr, "Error executing query: %s\n", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + pthread_mutex_unlock(&db_mutex); + return nullptr; + } + pthread_mutex_unlock(&db_mutex); + return password; +} diff --git a/src/db_update_user_field.c b/src/db_update_user_field.c new file mode 100644 index 0000000..15379b5 --- /dev/null +++ b/src/db_update_user_field.c @@ -0,0 +1,35 @@ +#include "database.h" +#include "sql.h" +#include "user.h" + +bool db_update_user_field(const int id, const char *field, const char *value) { + pthread_mutex_lock(&db_mutex); + if (!db || !field || !value) { + fprintf(stderr, "invalid parameters\n"); + pthread_mutex_unlock(&db_mutex); + return false; + } + char sql[256]; + snprintf(sql, sizeof(sql), UPDATE_USER_FIELD_SQL, field); + + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) { + fprintf(stderr, "failed to prepare statement: %s\n", sqlite3_errmsg(db)); + pthread_mutex_unlock(&db_mutex); + return false; + } + sqlite3_bind_text(stmt, 1, value, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 2, id); + + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + + if (rc != SQLITE_DONE) { + fprintf(stderr, "execution failed: %s\n", sqlite3_errmsg(db)); + pthread_mutex_unlock(&db_mutex); + return false; + } + pthread_mutex_unlock(&db_mutex); + return sqlite3_changes(db) > 0; +} diff --git a/src/sql.h b/src/sql.h index f183b81..860d87a 100644 --- a/src/sql.h +++ b/src/sql.h @@ -29,4 +29,5 @@ "update users set username = ?, email = ?, password = ?, user_group = ?, " \ "is_superuser = ? where id = ?" #define DELETE_USER_SQL "delete from users where id = ?" -#define UPDATE_USER_PASSWORD_SQL "update users set password = ? where id = ?" +#define UPDATE_USER_FIELD_SQL "update users set %s = ? where id = ?" +#define SELECT_USER_FIELD_SQL "select %s from users where id = ?" diff --git a/src/user.h b/src/user.h index 6785fe3..85fb364 100644 --- a/src/user.h +++ b/src/user.h @@ -4,7 +4,8 @@ #include #include -#define HTTP_HEADERS "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n" +#define HTTP_HEADERS \ + "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n" typedef struct User { int id; @@ -54,6 +55,7 @@ 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); @@ -65,3 +67,5 @@ User *db_update_user(const int userid, const char *username, const char *email, const bool *is_superuser); bool db_delete_user(const int id); User *db_get_user_by_id(const int id); +char *db_get_user_field_by_id(const int id, const char *field); +bool db_update_user_field(const int id, const char *field, const char *value); diff --git a/tests.sh b/tests.sh index e7a2317..bc8bcd7 100755 --- a/tests.sh +++ b/tests.sh @@ -436,6 +436,40 @@ else errors=$(($errors+1)) fi +# test POST /api/users/password without post data +printf "==> ${BLUE}Testing${CYAN} POST${BWHITE} /api/users/password without post data${RESET} " +curl \ + --silent \ + -X POST \ + -d "" \ + localhost:8222/api/users/password &> api_users_password_no_data_test.log +sleep 0.1 +res=$(cat api_users_password_no_data_test.log) +if [[ "$res" == *'{"error":"must have token, old_password, new_password"}'* ]]; then + printf "[$GREEN OK $RESET]\n" + success=$(($success+1)) +else + printf "[$RED ERR $RESET]\n==> ${BLUE}Response: $RESET$(echo $res)\n" + errors=$(($errors+1)) +fi + +# test POST /api/users/password with wrong json +printf "==> ${BLUE}Testing${CYAN} POST${BWHITE} /api/users/password with wrong json${RESET} " +curl \ + --silent \ + -X POST \ + -d "ijansd fu9q303ghq9" \ + localhost:8222/api/users/password &> api_users_password_wrong_json_test.log +sleep 0.1 +res=$(cat api_users_password_wrong_json_test.log) +if [[ "$res" == *'error'* ]]; then + printf "[$GREEN OK $RESET]\n" + success=$(($success+1)) +else + printf "[$RED ERR $RESET]\n==> ${BLUE}Response: $RESET$(echo $res)\n" + errors=$(($errors+1)) +fi + # test PUT /api/users printf "==> ${BLUE}Testing${YELLOW} PUT${BWHITE} /api/users${RESET} " reqtoken=$(cat api_token_test.log | sed -e "s/{//g" | sed -e "s/}//g") @@ -459,6 +493,40 @@ else errors=$(($errors+1)) fi +# test PUT /api/users without post data +printf "==> ${BLUE}Testing${YELLOW} PUT${BWHITE} /api/users without post data${RESET} " +curl \ + --silent \ + -X PUT \ + -d "" \ + localhost:8222/api/users &> put_api_users_no_data_test.log +sleep 0.1 +res=$(cat put_api_users_no_data_test.log) +if [[ "$res" == *'{"error":"must have token, new_user_data"}'* ]]; then + printf "[$GREEN OK $RESET]\n" + success=$(($success+1)) +else + printf "[$RED ERR $RESET]\n==> ${BLUE}Response: $RESET$(echo $res)\n" + errors=$(($errors+1)) +fi + +# test PUT /api/users with wrong json +printf "==> ${BLUE}Testing${YELLOW} PUT${BWHITE} /api/users with wrong json${RESET} " +curl \ + --silent \ + -X PUT \ + -d "hiusdf 7qyt0 y qy40" \ + localhost:8222/api/users &> put_api_users_wrong_json_test.log +sleep 0.1 +res=$(cat put_api_users_wrong_json_test.log) +if [[ "$res" == *'error'* ]]; then + printf "[$GREEN OK $RESET]\n" + success=$(($success+1)) +else + printf "[$RED ERR $RESET]\n==> ${BLUE}Response: $RESET$(echo $res)\n" + errors=$(($errors+1)) +fi + # test POST /api/register printf "==> ${BLUE}Testing${CYAN} POST${BWHITE} /api/register${RESET} " curl \