Skip to content
This repository has been archived by the owner on Jan 26, 2025. It is now read-only.

Commit

Permalink
feat(manager): add csrf protection
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanOricil committed Jan 15, 2025
1 parent c09d25f commit bc2116c
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 25 deletions.
2 changes: 2 additions & 0 deletions site/api/esp32-mfa-authenticator-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ export default class ESP32MFAAuthenticatorClient {
baseURL || "/api/v1",
{
"Content-Type": "application/json",
// NOTE: this token is generated and replaced in the server
"x-csrf-token": "{{CSRF_TOKEN}}",
},
5000,
5
Expand Down
2 changes: 2 additions & 0 deletions site/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export default defineNuxtConfig({
// NOTE: https://github.com/rollup/rollup/issues/2756#issuecomment-951370238
// NOTE: a single js because esp32 server can't handle serving multiple ones in parallel, consistently. It throws an exception or fail to serve a partition.
manualChunks: () => "everything.js",
// NOTE: I had to prefix _nuxt because the js was not appearing in .output/public/_nuxt and I need it to be in that location because of the csrf token injection process in the server
entryFileNames: () => "_nuxt/index.js",
},
},
},
Expand Down
105 changes: 80 additions & 25 deletions src/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ static const char *TAG = "manager";

AsyncWebServer server(80);

// NOTE: we store the CSRF_TOKEN to validate it during requests made by the client
String CSRF_TOKEN = String(random(0xFFFFFFFF), HEX);

String get_cookie(AsyncWebServerRequest *request, const String &name)
{
String cookies = request->header("Cookie");
Expand Down Expand Up @@ -54,6 +57,28 @@ void is_authorized(
}
}

void is_csrf_valid(
AsyncWebServerRequest *request,
const std::function<void(AsyncWebServerRequest *)> &handler)
{
try
{
String csrf_token = request->header("x-csrf-token");
if (csrf_token != CSRF_TOKEN)
{
request->send(403, "application/json", "{\"message\":\"forbidden\"}");
ESP_LOGI(TAG, "forbidden");
return;
}
handler(request);
}
catch (const std::exception &e)
{
ESP_LOGE(TAG, "Error while validating the CSRF token: %s", e.what());
request->send(500, "application/json", "{\"message\":\"something went wrong\"}");
}
}

void init_manager(Configuration config, const char *local_network_ip)
{
ESP_LOGI(TAG, "initializing manager server");
Expand Down Expand Up @@ -99,6 +124,26 @@ void init_manager(Configuration config, const char *local_network_ip)
// NOTE: configuring assets routes
server.serveStatic("/_nuxt/", SPIFFS, "/_nuxt/");

// NOTE: here we add the csrf token in the client
server.on(
"/_nuxt/index.js",
HTTP_GET,
[](AsyncWebServerRequest *request)
{
File file = SPIFFS.open("/_nuxt/index.js", FILE_READ);
if (!file)
{
request->send(500, "text/plain", "something went wrong");
return;
}

String jsContent = file.readString();
file.close();

jsContent.replace("{{CSRF_TOKEN}}", CSRF_TOKEN);
request->send(200, "application/javascript", jsContent);
});

server.on(
"/favicon.ico",
HTTP_GET,
Expand Down Expand Up @@ -225,16 +270,21 @@ void init_manager(Configuration config, const char *local_network_ip)
request,
[config](AsyncWebServerRequest *request)
{
try
{
request->send(200, "application/json", config.to_json_string(true));
ESP_LOGI(TAG, "session is valid and can read config");
}
catch (const std::exception &e)
{
ESP_LOGE(TAG, "error while fetching config: %s", e.what());
request->send(500, "application/json", "{\"message\":\"something went wrong\"}");
}
is_csrf_valid(
request,
[config](AsyncWebServerRequest *request)
{
try
{
request->send(200, "application/json", config.to_json_string(true));
ESP_LOGI(TAG, "session is valid and can read config");
}
catch (const std::exception &e)
{
ESP_LOGE(TAG, "error while fetching config: %s", e.what());
request->send(500, "application/json", "{\"message\":\"something went wrong\"}");
}
});
});
});

Expand All @@ -249,22 +299,27 @@ void init_manager(Configuration config, const char *local_network_ip)
request,
[config, data](AsyncWebServerRequest *request)
{
try
{
String data_json = String((char *)data);
Configuration new_config = Configuration::parse(data_json);
new_config.manager = config.manager;
if (new_config.save())
is_csrf_valid(
request,
[config](AsyncWebServerRequest *request)
{
ESP_LOGI(TAG, "config updated successfully");
request->send(200, "application/json", "{\"message\":\"configuration updated\"}");
}
}
catch (const std::exception &e)
{
ESP_LOGE(TAG, "error while updating config: %s", e.what());
request->send(500, "application/json", "{\"message\":\"something went wrong\"}");
}
try
{
String data_json = String((char *)data);
Configuration new_config = Configuration::parse(data_json);
new_config.manager = config.manager;
if (new_config.save())
{
ESP_LOGI(TAG, "config updated successfully");
request->send(200, "application/json", "{\"message\":\"configuration updated\"}");
}
}
catch (const std::exception &e)
{
ESP_LOGE(TAG, "error while updating config: %s", e.what());
request->send(500, "application/json", "{\"message\":\"something went wrong\"}");
}
});
});
});

Expand Down

0 comments on commit bc2116c

Please sign in to comment.