From b0e992e99bf0f6bcb3602f6f4fd4874cc510cfaa Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Thu, 19 Feb 2026 17:39:44 +0100 Subject: Add some simple login system --- src/web.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 187 insertions(+), 4 deletions(-) (limited to 'src/web.c') diff --git a/src/web.c b/src/web.c index e0d6d55..c55278a 100644 --- a/src/web.c +++ b/src/web.c @@ -14,6 +14,7 @@ #include #include #include +#include #define PAGE "libmicrohttpd demo"\ "libmicrohttpd demo" @@ -39,6 +40,26 @@ static int generate_secret(char *out) { return 0; } +static char *generate_session_token(struct App *app, const char *username, const char *password) { + char combined[128]; + snprintf(combined, sizeof(combined), "%s:%s", username, password); + return crypt(combined, app->session_secret); +} + +static int validate_credentials(struct App *app, const char *username, const char *password) { + return strcmp(username, app->admin_user) == 0 && strcmp(password, app->admin_pass) == 0; +} + +static int is_authenticated(struct App *app, struct MHD_Connection *connection) { + const char *cookie = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "session"); + if (cookie == NULL) return 0; + + char *expected = generate_session_token(app, app->admin_user, app->admin_pass); + if (expected == NULL) return 0; + + return strcmp(cookie, expected) == 0; +} + void prepare_database(struct App *app) { // @CLEANUP: Move this to main? doesn't belong here at least sqlite3_config(SQLITE_CONFIG_LOG, sqliteError, NULL); @@ -614,6 +635,9 @@ static char *render_clients_page(struct Request *request, size_t *out_len) { "\n" "\n" "

Clients

\n" + "
\n" + " \n" + "
\n" " \n" " \n"); @@ -745,6 +769,39 @@ static char *render_client_detail_page(struct Request *request, int64_t client_i return buf; } +static char *render_login_page(const char *error_msg, size_t *out_len) { + char *buf = NULL; + size_t buf_len = 0; + FILE *f = open_memstream(&buf, &buf_len); + if (!f) return NULL; + + fprintf(f, + "\n" + "\n" + "\n" + " Login\n" + "\n" + "\n" + "

Login

\n"); + + if (error_msg) { + fprintf(f, "

%s

\n", error_msg); + } + + fprintf(f, + " \n" + "

\n" + "

\n" + "

\n" + " \n" + "\n" + "\n"); + + fclose(f); + *out_len = buf_len; + return buf; +} + static enum MHD_Result create_post_collector(struct PostCollector *c) { c->data = malloc(512); c->size = 0; @@ -799,6 +856,14 @@ form_iterator(void *cls, memcpy(buf, data, copy_size); request->form.client_id = strtoll(buf, NULL, 10); request->form.client_id_set = 1; + } else if (strcmp(key, "username") == 0 && off + size < 32) { + memcpy(request->form.username + off, data, size); + request->form.username[off + size] = '\0'; + request->form.username_set = 1; + } else if (strcmp(key, "password") == 0 && off + size < 64) { + memcpy(request->form.password + off, data, size); + request->form.password[off + size] = '\0'; + request->form.password_set = 1; } return MHD_YES; @@ -831,7 +896,7 @@ static enum MHD_Result handler( request->host = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Host"); if(strcmp(method, "POST") == 0) { - if(strcmp(url, "/clients") == 0 || strcmp(url, "/clients/delete") == 0) { + if(strcmp(url, "/clients") == 0 || strcmp(url, "/clients/delete") == 0 || strcmp(url, "/login") == 0) { request->post_proc = MHD_create_post_processor( connection, 1024, &form_iterator, request); if (request->post_proc == NULL) { @@ -861,10 +926,71 @@ static enum MHD_Result handler( } } - if(strcmp(url, "/clients") == 0) { + if(strcmp(url, "/logout") == 0) { + struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Set-Cookie", "session=; Path=/; HttpOnly; Max-Age=0"); + MHD_add_response_header(response, "Location", "/login"); + ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); + MHD_destroy_response(response); + return ret; + } else if(strcmp(url, "/login") == 0) { MHD_destroy_post_processor(request->post_proc); request->post_proc = NULL; + if (!request->form.username_set || !request->form.password_set) { + size_t html_len; + char *html = render_login_page("Invalid credentials", &html_len); + if (!html) return MHD_NO; + struct MHD_Response *response = MHD_create_response_from_buffer( + html_len, html, MHD_RESPMEM_MUST_FREE); + ret = MHD_add_response_header(response, "Content-Type", "text/html"); + if(ret != MHD_YES) return ret; + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } + + if (!validate_credentials(server->app, request->form.username, request->form.password)) { + size_t html_len; + char *html = render_login_page("Invalid credentials", &html_len); + if (!html) return MHD_NO; + struct MHD_Response *response = MHD_create_response_from_buffer( + html_len, html, MHD_RESPMEM_MUST_FREE); + ret = MHD_add_response_header(response, "Content-Type", "text/html"); + if(ret != MHD_YES) return ret; + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } + + // Valid credentials - set cookie and redirect + char *token = generate_session_token(server->app, request->form.username, request->form.password); + if (token == NULL) return MHD_NO; + + char cookie_header[128]; + snprintf(cookie_header, sizeof(cookie_header), "session=%s; Path=/; HttpOnly", token); + + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Set-Cookie", cookie_header); + MHD_add_response_header(response, "Location", "/clients"); + ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); + MHD_destroy_response(response); + return ret; + } else if(strcmp(url, "/clients") == 0) { + MHD_destroy_post_processor(request->post_proc); + request->post_proc = NULL; + + // Auth check + if (!is_authenticated(server->app, connection)) { + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Location", "/login"); + ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); + MHD_destroy_response(response); + return ret; + } + if (!request->form.name_set || request->form.name[0] == '\0') { return MHD_NO; } @@ -885,6 +1011,16 @@ static enum MHD_Result handler( MHD_destroy_post_processor(request->post_proc); request->post_proc = NULL; + // Auth check + if (!is_authenticated(server->app, connection)) { + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Location", "/login"); + ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); + MHD_destroy_response(response); + return ret; + } + if (!request->form.client_id_set || request->form.client_id <= 0) { return MHD_NO; } @@ -1031,7 +1167,28 @@ static enum MHD_Result handler( } else if (strcmp(method, "GET") == 0) { if (*upload_data_size != 0) return MHD_NO; - if(strcmp(url, "/clients") == 0) { + if(strcmp(url, "/login") == 0) { + size_t html_len; + char *html = render_login_page(NULL, &html_len); + if (!html) return MHD_NO; + struct MHD_Response *response = MHD_create_response_from_buffer( + html_len, html, MHD_RESPMEM_MUST_FREE); + ret = MHD_add_response_header(response, "Content-Type", "text/html"); + if(ret != MHD_YES) return ret; + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } else if(strcmp(url, "/clients") == 0) { + // Auth check + if (!is_authenticated(server->app, connection)) { + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Location", "/login"); + ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); + MHD_destroy_response(response); + return ret; + } + size_t html_len; char *html = render_clients_page(request, &html_len); @@ -1054,6 +1211,16 @@ static enum MHD_Result handler( MHD_destroy_response(response); return ret; } else if (strncmp(url, "/client/", 8) == 0) { + // Auth check + if (!is_authenticated(server->app, connection)) { + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Location", "/login"); + ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); + MHD_destroy_response(response); + return ret; + } + int64_t client_id; if (sscanf(url + 8, "%ld", &client_id) != 1 || client_id <= 0) { // Bad Request @@ -1091,6 +1258,16 @@ static enum MHD_Result handler( MHD_destroy_response(response); return ret; } else { + // Auth check for default page + if (!is_authenticated(server->app, connection)) { + struct MHD_Response *response = MHD_create_response_from_buffer( + 0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Location", "/login"); + ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); + MHD_destroy_response(response); + return ret; + } + struct MHD_Response *response = MHD_create_response_from_buffer( strlen(server->page), (void *)server->page, @@ -1127,10 +1304,16 @@ static void request_completed_callback ( static struct MHD_Daemon *d = NULL; -int web_begin(struct Server *server, int port) { +int web_begin(struct Server *server, int port, const char *admin_user, const char *admin_pass) { assert(d == NULL); server->page = PAGE; + server->app->admin_user = admin_user; + server->app->admin_pass = admin_pass; + + if (generate_secret(server->app->session_secret) != 0) { + return 1; + } d = MHD_start_daemon( MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DUAL_STACK, -- cgit v1.2.3
NameSecretActions