diff --git a/configs/config.json b/configs/config.json new file mode 100644 index 0000000..20ef325 --- /dev/null +++ b/configs/config.json @@ -0,0 +1,3 @@ +{ + "server_port": 8081 +} \ No newline at end of file diff --git a/include/db/auth.h b/include/db/auth.h new file mode 100644 index 0000000..bdc9a5b --- /dev/null +++ b/include/db/auth.h @@ -0,0 +1,25 @@ +#ifndef DB_AUTH_H +#define DB_AUTH_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + int open_user_db(); + + void close_user_db(); + + int check_user_exists(const char* user_id, int* result); + + int set_user_password(const char* user_id, const char* password); + + int login(const char* user_id, const char* password, int* result); + + int delete_user(const char* user_id); + +#ifdef __cplusplus +} +#endif + +#endif // DB_AUTH_H \ No newline at end of file diff --git a/include/server/auth.h b/include/server/auth.h index 675cee5..6aa801c 100644 --- a/include/server/auth.h +++ b/include/server/auth.h @@ -1,6 +1,12 @@ #ifndef SERVER_AUTH_H #define SERVER_AUTH_H -// Your code here +#include "server/types.h" + +int login_handler(mg_connection* conn, void* cbdata); +int register_handler(mg_connection* conn, void* cbdata); +int delete_handler(mg_connection* conn, void* cbdata); + +extern char* secret; #endif // SERVER_AUTH_H \ No newline at end of file diff --git a/include/server/config.h b/include/server/config.h new file mode 100644 index 0000000..94d2151 --- /dev/null +++ b/include/server/config.h @@ -0,0 +1,19 @@ +#ifndef SERVER_CONFIG_H +#define SERVER_CONFIG_H + +#include + +typedef struct +{ + int server_port; + char* secret; +} config_t; + +void config_ctor(config_t* this); +void config_dtor(config_t* this); + +bool config_read(config_t* this, const char* filename); + +int print_config(); + +#endif // SERVER_CONFIG_H diff --git a/include/server/defs.h b/include/server/defs.h deleted file mode 100644 index 8543214..0000000 --- a/include/server/defs.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef KQM_DEFS_H -#define KQM_DEFS_H - -#define CR "\r" -#define LF "\n" -#define CRLF "\r\n" - -#endif // KQM_DEFS_H diff --git a/include/server/types.h b/include/server/types.h index 5a48fb3..db3d8d6 100644 --- a/include/server/types.h +++ b/include/server/types.h @@ -1,11 +1,10 @@ #ifndef SERVER_TYPES_H #define SERVER_TYPES_H -#include - typedef struct mg_callbacks mg_callbacks; typedef struct mg_context mg_context; typedef struct mg_connection mg_connection; typedef struct mg_request_info mg_request_info; +typedef struct mg_form_data_handler mg_form_data_handler; #endif // SERVER_TYPES_H diff --git a/include/server/util.h b/include/server/util.h new file mode 100644 index 0000000..ebed2bc --- /dev/null +++ b/include/server/util.h @@ -0,0 +1,6 @@ +#ifndef SERVER_UTIL_H +#define SERVER_UTIL_H + +char* kqm_strndup(const char* str, size_t n); + +#endif // SERVER_UTIL_H \ No newline at end of file diff --git a/src/db/auth.cpp b/src/db/auth.cpp index 5d678c5..631aeec 100644 --- a/src/db/auth.cpp +++ b/src/db/auth.cpp @@ -70,10 +70,10 @@ extern "C" return 1; } - auto logout(const char* user_id) -> int + auto delete_user(const char* user_id) -> int { - leveldb::WriteOptions write_options; - leveldb::Status status = user_db->Delete(write_options, user_id); + auto write_options = leveldb::WriteOptions{}; + auto status = user_db->Delete(write_options, user_id); return status.ok(); } } \ No newline at end of file diff --git a/src/jwt/jwt.cpp b/src/jwt/jwt.cpp index d83e6f9..33e7701 100644 --- a/src/jwt/jwt.cpp +++ b/src/jwt/jwt.cpp @@ -15,7 +15,7 @@ extern "C" .set_issuer("KeqingMoe") .set_subject("user_id") .set_audience("web_client") - .set_expires_at(std::chrono::system_clock::now() + 3s) + .set_expires_at(std::chrono::system_clock::now() + 1h) .set_payload_claim("user_id", jwt::claim(std::string{user_id})) .sign(jwt::algorithm::hs256{secret}); return strdup(token.c_str()); diff --git a/src/server/auth/auth.c b/src/server/auth/auth.c new file mode 100644 index 0000000..74a21fe --- /dev/null +++ b/src/server/auth/auth.c @@ -0,0 +1,5 @@ +#include + +#include + +char* secret = NULL; diff --git a/src/server/auth/delete.c b/src/server/auth/delete.c new file mode 100644 index 0000000..6cabe2c --- /dev/null +++ b/src/server/auth/delete.c @@ -0,0 +1,134 @@ +#include "server/auth.h" +#include "server/util.h" + +#include "db/auth.h" + +#include "jwt/jwt.h" + +#include + +#include + +#include +#include +#include + +typedef struct +{ + char* token; +} delete_form_t; + +static void delete_form_dtor(delete_form_t* form) +{ + if (form->token) free(form->token); +} + +static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data) +{ + return MG_FORM_FIELD_HANDLE_GET; +} + +static int field_get(const char* key, const char* value, size_t valuelen, void* user_data) +{ + delete_form_t* form = (delete_form_t*)user_data; + if (strcmp(key, "token") == 0) { + form->token = kqm_strndup(value, valuelen); + return MG_FORM_FIELD_HANDLE_ABORT; + } + return MG_FORM_FIELD_HANDLE_GET; +} + + +int delete_handler(mg_connection* conn, void* cbdata) +{ + const mg_request_info* post_body = mg_get_request_info(conn); + + if (post_body == NULL) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n"); + mg_printf(conn, "{\"error\":\"null request\"}"); + return 1; + } + + if (strcmp(post_body->request_method, "POST")) { + mg_printf(conn, + "HTTP/1.1 405 Method Not Allowed\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n"); + mg_printf(conn, "{\"error\":\"must use post request\"}"); + return 1; + } + + delete_form_t form = {NULL}; + + mg_form_data_handler delete_callback = { + .field_found = field_found, + .field_get = field_get, + .field_store = NULL, + .user_data = &form, + }; + + mg_handle_form_request(conn, &delete_callback); + + if (!form.token) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"need token\"}"); + delete_form_dtor(&form); + return 1; + } + + int result = verify_token(form.token, secret); + if (!result) { + mg_printf(conn, + "HTTP/1.1 401 Unauthorized\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"auth failed\"}"); + delete_form_dtor(&form); + return 1; + } + + char* user_id = get_payload(form.token); + + int flag = check_user_exists(user_id, &result); + if (!flag) { + mg_printf(conn, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"failed to check user existence\"}"); + delete_form_dtor(&form); + return 1; + } else if (!result) { + mg_printf(conn, + "HTTP/1.1 404 Not Found\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"user does not exist\"}"); + delete_form_dtor(&form); + return 1; + } + + flag = delete_user(user_id); + if (!flag) { + mg_printf(conn, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"failed to delete account\"}"); + } else { + mg_printf(conn, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"success\":\"delete account success\"}"); + } + free(user_id); + delete_form_dtor(&form); + return 1; +} diff --git a/src/server/auth/login.c b/src/server/auth/login.c new file mode 100644 index 0000000..f255bc0 --- /dev/null +++ b/src/server/auth/login.c @@ -0,0 +1,145 @@ +#include "server/auth.h" +#include "server/util.h" + +#include "db/auth.h" + +#include "jwt/jwt.h" + +#include + +#include + +#include +#include +#include + +typedef struct +{ + char* user_id; + char* password; +} login_form_t; + +static void login_form_dtor(login_form_t* form) +{ + if (form->user_id) free(form->user_id); + if (form->password) free(form->password); +} + +static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data) +{ + return MG_FORM_FIELD_HANDLE_GET; +} + +static int field_get(const char* key, const char* value, size_t valuelen, void* user_data) +{ + login_form_t* form = (login_form_t*)user_data; + if (strcmp(key, "user_id") == 0) { + form->user_id = kqm_strndup(value, valuelen); + } else if (strcmp(key, "password") == 0) { + form->password = kqm_strndup(value, valuelen); + } + if (form->user_id && form->password) { + return MG_FORM_FIELD_HANDLE_ABORT; + } + return MG_FORM_FIELD_HANDLE_GET; +} + + +int login_handler(mg_connection* conn, void* cbdata) +{ + const mg_request_info* post_body = mg_get_request_info(conn); + + if (post_body == NULL) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n"); + mg_printf(conn, "{\"error\":\"null request\"}"); + return 1; + } + + if (strcmp(post_body->request_method, "POST")) { + mg_printf(conn, + "HTTP/1.1 405 Method Not Allowed\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n"); + mg_printf(conn, "{\"error\":\"must use post request\"}"); + return 1; + } + + login_form_t form = {NULL, NULL}; + + mg_form_data_handler login_callback = { + .field_found = field_found, + .field_get = field_get, + .field_store = NULL, + .user_data = &form, + }; + + mg_handle_form_request(conn, &login_callback); + + if (!form.user_id) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"need user_id\"}"); + login_form_dtor(&form); + return 1; + } + if (!form.password) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"need password\"}"); + login_form_dtor(&form); + return 1; + } + + int result; + int flag = check_user_exists(form.user_id, &result); + if (!flag) { + mg_printf(conn, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"failed to check user existence\"}"); + login_form_dtor(&form); + return 1; + } else if (!result) { + mg_printf(conn, + "HTTP/1.1 404 Not Found\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"user does not exist\"}"); + login_form_dtor(&form); + return 1; + } + + flag = login(form.user_id, form.password, &result); + if (!flag) { + mg_printf(conn, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"failed to login\"}"); + } else if (!result) { + mg_printf(conn, + "HTTP/1.1 401 Unauthorized\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"incorrect password or user id\"}"); + } else { + char* token = create_token(form.user_id, secret); + mg_printf(conn, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"token\":\"%s\"}", + token); + free(token); + } + login_form_dtor(&form); + return 1; +} diff --git a/src/server/auth/register.c b/src/server/auth/register.c new file mode 100644 index 0000000..1f32f7f --- /dev/null +++ b/src/server/auth/register.c @@ -0,0 +1,134 @@ +#include "server/auth.h" +#include "server/util.h" + +#include "db/auth.h" + +#include + +#include + +#include +#include +#include + +typedef struct +{ + char* user_id; + char* password; +} register_form_t; + +static void register_form_dtor(register_form_t* form) +{ + if (form->user_id) free(form->user_id); + if (form->password) free(form->password); +} + +static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data) +{ + return MG_FORM_FIELD_HANDLE_GET; +} + +static int field_get(const char* key, const char* value, size_t valuelen, void* user_data) +{ + register_form_t* form = (register_form_t*)user_data; + if (strcmp(key, "user_id") == 0) { + form->user_id = kqm_strndup(value, valuelen); + } else if (strcmp(key, "password") == 0) { + form->password = kqm_strndup(value, valuelen); + } + if (form->user_id && form->password) { + return MG_FORM_FIELD_HANDLE_ABORT; + } + return MG_FORM_FIELD_HANDLE_GET; +} + + +int register_handler(mg_connection* conn, void* cbdata) +{ + const mg_request_info* post_body = mg_get_request_info(conn); + + if (post_body == NULL) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n"); + mg_printf(conn, "{\"error\":\"null request\"}"); + return 1; + } + + if (strcmp(post_body->request_method, "POST")) { + mg_printf(conn, + "HTTP/1.1 405 Method Not Allowed\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n"); + mg_printf(conn, "{\"error\":\"must use post request\"}"); + return 1; + } + + register_form_t form = {NULL, NULL}; + + mg_form_data_handler register_callback = { + .field_found = field_found, + .field_get = field_get, + .field_store = NULL, + .user_data = &form, + }; + + mg_handle_form_request(conn, ®ister_callback); + + if (!form.user_id) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"need user_id\"}"); + register_form_dtor(&form); + return 1; + } + if (!form.password) { + mg_printf(conn, + "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"need password\"}"); + register_form_dtor(&form); + return 1; + } + + int result; + int flag = check_user_exists(form.user_id, &result); + if (!flag) { + mg_printf(conn, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"failed to check user existence\"}"); + register_form_dtor(&form); + return 1; + } else if (result) { + mg_printf(conn, + "HTTP/1.1 409 Conflict\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"user already exists\"}"); + register_form_dtor(&form); + return 1; + } + + flag = set_user_password(form.user_id, form.password); + if (!flag) { + mg_printf(conn, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"error\":\"failed to register\"}"); + } else { + mg_printf(conn, + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n\r\n" + "{\"success\":\"registration success\"}"); + } + register_form_dtor(&form); + return 1; +} diff --git a/src/server/config.c b/src/server/config.c new file mode 100644 index 0000000..e806af4 --- /dev/null +++ b/src/server/config.c @@ -0,0 +1,77 @@ +#include "server/config.h" + +#include + +#include +#include +#include + +char* read_file(const char* filename) +{ + FILE* file = fopen(filename, "rb"); + if (file == NULL) { + printf("Error opening file\n"); + return NULL; + } + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char* buffer = (char*)malloc(file_size + 1); + if (buffer == NULL) { + printf("Memory allocation failed\n"); + fclose(file); + return NULL; + } + + fread(buffer, 1, file_size, file); + buffer[file_size] = '\0'; + + fclose(file); + return buffer; +} + +void config_ctor(config_t* this) +{ + this->server_port = 0; + this->secret = NULL; +} + +void config_dtor(config_t* this) +{ + if (this->secret) free(this->secret); +} + +bool config_read(config_t* this, const char* filename) +{ + char* json_data = read_file(filename); + if (json_data == NULL) { + return false; + } + + cJSON* root = cJSON_Parse(json_data); + if (root == NULL) { + free(json_data); + return false; + } + + cJSON* server_port = cJSON_GetObjectItem(root, "server_port"); + if (cJSON_IsNumber(server_port)) { + this->server_port = server_port->valueint; + } else { + this->server_port = 80; + } + + cJSON* secret = cJSON_GetObjectItem(root, "secret"); + if (cJSON_IsString(secret) && secret->valuestring != NULL) { + this->secret = strdup(secret->valuestring); + } else { + this->secret = strdup("me@keqing.moe"); + } + + cJSON_Delete(root); + free(json_data); + + return true; +} diff --git a/src/server/util.c b/src/server/util.c new file mode 100644 index 0000000..a1e3aa9 --- /dev/null +++ b/src/server/util.c @@ -0,0 +1,13 @@ +#include "server/util.h" + +#include +#include + +char* kqm_strndup(const char* str, size_t n) +{ + char* s = (char*)malloc(n + 1); + memcpy(s, str, n); + s[n] = '\0'; + return s; +} +