auth: add authentication
All checks were successful
Build ESP32 BMC Firmware / build (push) Successful in 1m7s
All checks were successful
Build ESP32 BMC Firmware / build (push) Successful in 1m7s
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include "log_handler.h"
|
||||
#include "esp_log.h"
|
||||
#include "cJSON.h"
|
||||
#include "mbedtls/base64.h"
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
@@ -66,6 +67,122 @@ static esp_err_t send_json_success(httpd_req_t *req, cJSON *data) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HTTP Basic Authentication
|
||||
// ============================================================================
|
||||
|
||||
#if BMC_HTTP_AUTH_ENABLED
|
||||
|
||||
/**
|
||||
* @brief Check if request has valid authentication
|
||||
*
|
||||
* @param req HTTP request
|
||||
* @return true if authenticated, false otherwise
|
||||
*/
|
||||
static bool check_auth(httpd_req_t *req) {
|
||||
char auth_header[128];
|
||||
bool authenticated = false;
|
||||
|
||||
// Method 1: Check Authorization header
|
||||
esp_err_t ret = httpd_req_get_hdr_value_str(req, "Authorization", auth_header, sizeof(auth_header));
|
||||
if (ret == ESP_OK && strncmp(auth_header, "Basic ", 6) == 0) {
|
||||
// Decode base64 credentials
|
||||
const char *b64_credentials = auth_header + 6;
|
||||
size_t b64_len = strlen(b64_credentials);
|
||||
|
||||
unsigned char decoded[64];
|
||||
size_t decoded_len = 0;
|
||||
|
||||
int dec_ret = mbedtls_base64_decode(decoded, sizeof(decoded) - 1, &decoded_len,
|
||||
(const unsigned char *)b64_credentials, b64_len);
|
||||
if (dec_ret == 0) {
|
||||
decoded[decoded_len] = '\0';
|
||||
|
||||
// Build expected credentials string "username:password"
|
||||
char expected_creds[64];
|
||||
snprintf(expected_creds, sizeof(expected_creds), "%s:%s", BMC_HTTP_AUTH_USERNAME, BMC_HTTP_AUTH_PASSWORD);
|
||||
|
||||
authenticated = (strcmp((char *)decoded, expected_creds) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Check query parameter "auth" for WebSocket support
|
||||
// Format: ?auth=base64(username:password)
|
||||
if (!authenticated) {
|
||||
char query[256];
|
||||
ret = httpd_req_get_url_query_str(req, query, sizeof(query));
|
||||
if (ret == ESP_OK) {
|
||||
char auth_param[128];
|
||||
ret = httpd_query_key_value(query, "auth", auth_param, sizeof(auth_param));
|
||||
if (ret == ESP_OK) {
|
||||
// Decode base64 credentials from query parameter
|
||||
size_t b64_len = strlen(auth_param);
|
||||
unsigned char decoded[64];
|
||||
size_t decoded_len = 0;
|
||||
|
||||
int dec_ret = mbedtls_base64_decode(decoded, sizeof(decoded) - 1, &decoded_len,
|
||||
(const unsigned char *)auth_param, b64_len);
|
||||
if (dec_ret == 0) {
|
||||
decoded[decoded_len] = '\0';
|
||||
|
||||
// Build expected credentials string "username:password"
|
||||
char expected_creds[64];
|
||||
snprintf(expected_creds, sizeof(expected_creds), "%s:%s", BMC_HTTP_AUTH_USERNAME,
|
||||
BMC_HTTP_AUTH_PASSWORD);
|
||||
|
||||
authenticated = (strcmp((char *)decoded, expected_creds) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send 401 Unauthorized response
|
||||
*
|
||||
* @param req HTTP request
|
||||
* @return ESP_OK
|
||||
*/
|
||||
static esp_err_t send_auth_required(httpd_req_t *req) {
|
||||
char www_auth[64];
|
||||
snprintf(www_auth, sizeof(www_auth), "Basic realm=\"%s\"", BMC_HTTP_AUTH_REALM);
|
||||
httpd_resp_set_hdr(req, "WWW-Authenticate", www_auth);
|
||||
httpd_resp_set_status(req, "401 Unauthorized");
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Authentication required\"}");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Authentication middleware wrapper for handlers
|
||||
*
|
||||
* @param req HTTP request
|
||||
* @param handler The actual handler to call if authenticated
|
||||
* @return ESP_OK on success, error otherwise
|
||||
*/
|
||||
static esp_err_t auth_wrapper(httpd_req_t *req, esp_err_t (*handler)(httpd_req_t *)) {
|
||||
if (!check_auth(req)) {
|
||||
return send_auth_required(req);
|
||||
}
|
||||
return handler(req);
|
||||
}
|
||||
|
||||
// Macro to create authenticated handler wrapper
|
||||
#define AUTH_HANDLER(name, handler_func) \
|
||||
static esp_err_t name(httpd_req_t *req) { \
|
||||
return auth_wrapper(req, handler_func); \
|
||||
}
|
||||
|
||||
#else
|
||||
// No authentication - pass through directly
|
||||
#define AUTH_HANDLER(name, handler_func) \
|
||||
static esp_err_t name(httpd_req_t *req) { \
|
||||
return handler_func(req); \
|
||||
}
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Static File Handlers
|
||||
// ============================================================================
|
||||
@@ -387,6 +504,10 @@ static void uart_to_ws_callback(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
static esp_err_t ws_serial_handler(httpd_req_t *req) {
|
||||
// Note: WebSocket authentication is disabled because browsers don't support
|
||||
// custom Authorization headers on WebSocket connections.
|
||||
// TODO: Find a way to fix this
|
||||
|
||||
// For WebSocket handlers with is_websocket=true, ESP-IDF handles the handshake
|
||||
// internally. This handler is only called for frame processing.
|
||||
// Client tracking is done dynamically in the broadcast function.
|
||||
@@ -733,70 +854,90 @@ static esp_err_t api_ota_upload_handler(httpd_req_t *req) {
|
||||
// URI Registration
|
||||
// ============================================================================
|
||||
|
||||
// Authenticated handler wrappers
|
||||
AUTH_HANDLER(auth_index_handler, index_handler)
|
||||
AUTH_HANDLER(auth_api_gpio_get_all_handler, api_gpio_get_all_handler)
|
||||
AUTH_HANDLER(auth_api_gpio_set_handler, api_gpio_set_handler)
|
||||
AUTH_HANDLER(auth_api_gpio_config_handler, api_gpio_config_handler)
|
||||
AUTH_HANDLER(auth_api_power_status_handler, api_power_status_handler)
|
||||
AUTH_HANDLER(auth_api_power_on_handler, api_power_on_handler)
|
||||
AUTH_HANDLER(auth_api_power_off_handler, api_power_off_handler)
|
||||
AUTH_HANDLER(auth_api_power_reset_handler, api_power_reset_handler)
|
||||
AUTH_HANDLER(auth_api_serial_config_get_handler, api_serial_config_get_handler)
|
||||
AUTH_HANDLER(auth_api_serial_config_set_handler, api_serial_config_set_handler)
|
||||
AUTH_HANDLER(auth_api_serial_send_handler, api_serial_send_handler)
|
||||
AUTH_HANDLER(auth_api_system_info_handler, api_system_info_handler)
|
||||
AUTH_HANDLER(auth_api_system_status_handler, api_system_status_handler)
|
||||
AUTH_HANDLER(auth_api_ota_status_handler, api_ota_status_handler)
|
||||
AUTH_HANDLER(auth_api_ota_update_handler, api_ota_update_handler)
|
||||
AUTH_HANDLER(auth_api_ota_upload_handler, api_ota_upload_handler)
|
||||
AUTH_HANDLER(auth_api_logs_get_handler, api_logs_get_handler)
|
||||
AUTH_HANDLER(auth_api_logs_clear_handler, api_logs_clear_handler)
|
||||
|
||||
static const httpd_uri_t uri_index = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = index_handler,
|
||||
.handler = auth_index_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_gpio = {
|
||||
.uri = "/api/gpio",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_gpio_get_all_handler,
|
||||
.handler = auth_api_gpio_get_all_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_gpio_set = {
|
||||
.uri = "/api/gpio/set",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_gpio_set_handler,
|
||||
.handler = auth_api_gpio_set_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_gpio_config = {
|
||||
.uri = "/api/gpio/config",
|
||||
.method = HTTP_PUT,
|
||||
.handler = api_gpio_config_handler,
|
||||
.handler = auth_api_gpio_config_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_status = {
|
||||
.uri = "/api/power/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_power_status_handler,
|
||||
.handler = auth_api_power_status_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_on = {
|
||||
.uri = "/api/power/on",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_power_on_handler,
|
||||
.handler = auth_api_power_on_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_off = {
|
||||
.uri = "/api/power/off",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_power_off_handler,
|
||||
.handler = auth_api_power_off_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_reset = {
|
||||
.uri = "/api/power/reset",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_power_reset_handler,
|
||||
.handler = auth_api_power_reset_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_serial_config_get = {
|
||||
.uri = "/api/serial/config",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_serial_config_get_handler,
|
||||
.handler = auth_api_serial_config_get_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_serial_config_set = {
|
||||
.uri = "/api/serial/config",
|
||||
.method = HTTP_PUT,
|
||||
.handler = api_serial_config_set_handler,
|
||||
.handler = auth_api_serial_config_set_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_serial_send = {
|
||||
.uri = "/api/serial/send",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_serial_send_handler,
|
||||
.handler = auth_api_serial_send_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_ws_serial = {
|
||||
@@ -809,43 +950,43 @@ static const httpd_uri_t uri_ws_serial = {
|
||||
static const httpd_uri_t uri_api_system_info = {
|
||||
.uri = "/api/system/info",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_system_info_handler,
|
||||
.handler = auth_api_system_info_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_system_status = {
|
||||
.uri = "/api/system/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_system_status_handler,
|
||||
.handler = auth_api_system_status_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_ota_status = {
|
||||
.uri = "/api/ota/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_ota_status_handler,
|
||||
.handler = auth_api_ota_status_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_ota_update = {
|
||||
.uri = "/api/ota/update",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_ota_update_handler,
|
||||
.handler = auth_api_ota_update_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_ota_upload = {
|
||||
.uri = "/api/ota/upload",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_ota_upload_handler,
|
||||
.handler = auth_api_ota_upload_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_logs_get = {
|
||||
.uri = "/api/logs",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_logs_get_handler,
|
||||
.handler = auth_api_logs_get_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_logs_clear = {
|
||||
.uri = "/api/logs/clear",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_logs_clear_handler,
|
||||
.handler = auth_api_logs_clear_handler,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user