Skip to content

Commit

Permalink
feat(webserver): Middleware with default middleware for cors, authc, …
Browse files Browse the repository at this point in the history
…curl-like logging
  • Loading branch information
mathieucarbou committed Sep 25, 2024
1 parent ae052f4 commit 521bbaa
Show file tree
Hide file tree
Showing 16 changed files with 1,011 additions and 100 deletions.
5 changes: 5 additions & 0 deletions libraries/WebServer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
170 changes: 170 additions & 0 deletions libraries/WebServer/examples/Middleware/Middleware.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#include <WiFi.h>
#include <WebServer.h>
#include <Middlewares.h>

// Your AP WiFi Credentials
// ( This is the AP your ESP will broadcast )
const char *ap_ssid = "ESP32_Demo";
const char *ap_password = "";

WebServer server(80);

LoggingMiddleware logger;
CorsMiddleware cors;
AuthenticationMiddleware auth;

void setup(void) {
Serial.begin(115200);
WiFi.softAP(ap_ssid, ap_password);

Serial.print("IP address: ");
Serial.println(WiFi.AP.localIP());

// curl-like output example:
//
// > curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/
//
// Connection from 192.168.4.2:51683
// > OPTIONS / HTTP/1.1
// > Host: 192.168.4.1
// > User-Agent: curl/8.10.0
// > Accept: */*
// > origin: http://192.168.4.1
// >
// * Processed in 5 ms
// < HTTP/1.HTTP/1.1 200 OK
// < Content-Type: text/html
// < Access-Control-Allow-Origin: http://192.168.4.1
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
// < Access-Control-Allow-Headers: X-Custom-Header
// < Access-Control-Allow-Credentials: false
// < Access-Control-Max-Age: 600
// < Content-Length: 0
// < Connection: close
// <
logger.setOutput(Serial);

cors.setOrigin("http://192.168.4.1");
cors.setMethods("POST,GET,OPTIONS,DELETE");
cors.setHeaders("X-Custom-Header");
cors.setAllowCredentials(false);
cors.setMaxAge(600);

auth.setUsername("admin");
auth.setPassword("admin");
auth.setRealm("My Super App");
auth.setAuthMethod(DIGEST_AUTH);
auth.setAuthFailureMessage("Authentication Failed");

server.addMiddleware(&logger);
server.addMiddleware(&cors);

// Not authenticated
//
// Test CORS preflight request with:
// > curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/
//
// Test cross-domain request with:
// > curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/
//
server.on("/", []() {
server.send(200, "text/plain", "Home");
});

// Authenticated
//
// > curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/protected
//
// Outputs:
//
// * Connection from 192.168.4.2:51750
// > GET /protected HTTP/1.1
// > Host: 192.168.4.1
// > User-Agent: curl/8.10.0
// > Accept: */*
// > origin: http://192.168.4.1
// >
// * Processed in 7 ms
// < HTTP/1.HTTP/1.1 401 Unauthorized
// < Content-Type: text/html
// < Access-Control-Allow-Origin: http://192.168.4.1
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
// < Access-Control-Allow-Headers: X-Custom-Header
// < Access-Control-Allow-Credentials: false
// < Access-Control-Max-Age: 600
// < WWW-Authenticate: Digest realm="My Super App", qop="auth", nonce="ac388a64184e3e102aae6fff1c9e8d76", opaque="e7d158f2b54d25328142d118ff0f932d"
// < Content-Length: 21
// < Connection: close
// <
//
// > curl -v -X GET -H "origin: http://192.168.4.1" --digest -u admin:admin http://192.168.4.1/protected
//
// Outputs:
//
// * Connection from 192.168.4.2:53662
// > GET /protected HTTP/1.1
// > Authorization: Digest username="admin", realm="My Super App", nonce="db9e6824eb2a13bc7b2bf8f3c43db896", uri="/protected", cnonce="NTliZDZiNTcwODM2MzAyY2JjMDBmZGJmNzFiY2ZmNzk=", nc=00000001, qop=auth, response="6ebd145ba0d3496a4a73f5ae79ff5264", opaque="23d739c22810282ff820538cba98bda4"
// > Host: 192.168.4.1
// > User-Agent: curl/8.10.0
// > Accept: */*
// > origin: http://192.168.4.1
// >
// Request handling...
// * Processed in 7 ms
// < HTTP/1.HTTP/1.1 200 OK
// < Content-Type: text/plain
// < Access-Control-Allow-Origin: http://192.168.4.1
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
// < Access-Control-Allow-Headers: X-Custom-Header
// < Access-Control-Allow-Credentials: false
// < Access-Control-Max-Age: 600
// < Content-Length: 9
// < Connection: close
// <
server
.on(
"/protected",
[]() {
Serial.println("Request handling...");
server.send(200, "text/plain", "Protected");
}
)
.addMiddleware(&auth);

// Not found is also handled by global middleware
//
// curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/inexsting
//
// Outputs:
//
// * Connection from 192.168.4.2:53683
// > GET /inexsting HTTP/1.1
// > Host: 192.168.4.1
// > User-Agent: curl/8.10.0
// > Accept: */*
// > origin: http://192.168.4.1
// >
// * Processed in 16 ms
// < HTTP/1.HTTP/1.1 404 Not Found
// < Content-Type: text/plain
// < Access-Control-Allow-Origin: http://192.168.4.1
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
// < Access-Control-Allow-Headers: X-Custom-Header
// < Access-Control-Allow-Credentials: false
// < Access-Control-Max-Age: 600
// < Content-Length: 14
// < Connection: close
// <
server.onNotFound([]() {
server.send(404, "text/plain", "Page not found");
});

server.collectAllHeaders();
server.begin();
Serial.println("HTTP server started");
}

void loop(void) {
server.handleClient();
delay(2); //allow the cpu to switch to other tasks
}
109 changes: 109 additions & 0 deletions libraries/WebServer/examples/Middleware/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
This example shows how to load all request headers and use middleware.

### CORS Middleware

```bash
❯ curl -i -X OPTIONS http://192.168.4.1
HTTP/1.1 200 OK
Content-Type: text/html
Access-Control-Allow-Origin: http://192.168.4.1
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: false
Access-Control-Max-Age: 600
Content-Length: 0
Connection: close
```

Output of logger middleware:

```
* Connection from 192.168.4.2:57597
< OPTIONS / HTTP/1.1
< Host: 192.168.4.1
< User-Agent: curl/8.9.1
< Accept: */*
<
* Processed!
> HTTP/1.HTTP/1.1 200 OK
> Content-Type: text/html
> Access-Control-Allow-Origin: http://192.168.4.1
> Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
> Access-Control-Allow-Headers: X-Custom-Header
> Access-Control-Allow-Credentials: false
> Access-Control-Max-Age: 600
> Content-Length: 0
> Connection: close
>
```

### Authentication Middleware

```bash
❯ curl -i -X GET http://192.168.4.1
HTTP/1.1 401 Unauthorized
Content-Type: text/html
WWW-Authenticate: Basic realm=""
Content-Length: 0
Connection: close
```

Output of logger middleware:

```
* Connection from 192.168.4.2:57705
< GET / HTTP/1.1
< Host: 192.168.4.1
< User-Agent: curl/8.9.1
< Accept: */*
<
* Processed!
> HTTP/1.HTTP/1.1 401 Unauthorized
> Content-Type: text/html
> WWW-Authenticate: Basic realm=""
> Content-Length: 0
> Connection: close
>
```

Sending auth...

```bash
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 192.168.4.1:80...
* Connected to 192.168.4.1 (192.168.4.1) port 80
* Server auth using Basic with user 'admin'
> GET / HTTP/1.1
> Host: 192.168.4.1
> Authorization: Basic YWRtaW46YWRtaW4=
> User-Agent: curl/8.9.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 4
< Connection: close
<
* shutting down connection #0
Home

```

Output of logger middleware:

```
* Connection from 192.168.4.2:62099
< GET / HTTP/1.1
< Authorization: Basic YWRtaW46YWRtaW4=
< Host: 192.168.4.1
< User-Agent: curl/8.9.1
< Accept: */*
<
* Processed!
> HTTP/1.HTTP/1.1 200 OK
> Content-Type: text/plain
> Content-Length: 4
> Connection: close
>
```
5 changes: 5 additions & 0 deletions libraries/WebServer/examples/Middleware/ci.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"targets": {
"esp32h2": false
}
}
76 changes: 76 additions & 0 deletions libraries/WebServer/src/Middlewares.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#ifndef MIDDLEWARES_H
#define MIDDLEWARES_H

#include <WebServer.h>
#include <Stream.h>

#include <assert.h>

// curl-like logging middleware
class LoggingMiddleware : public Middleware {
public:
void setOutput(Print &output);

bool run(WebServer &server, Middleware::Callback next) override;

private:
Print *_out = nullptr;
};

class CorsMiddleware : public Middleware {
public:
CorsMiddleware &setOrigin(const char *origin);
CorsMiddleware &setMethods(const char *methods);
CorsMiddleware &setHeaders(const char *headers);
CorsMiddleware &setAllowCredentials(bool credentials);
CorsMiddleware &setMaxAge(uint32_t seconds);

void addCORSHeaders(WebServer &server);

bool run(WebServer &server, Middleware::Callback next) override;

private:
String _origin = F("*");
String _methods = F("*");
String _headers = F("*");
bool _credentials = true;
uint32_t _maxAge = 86400;
};

class AuthenticationMiddleware : public Middleware {
public:
AuthenticationMiddleware &setUsername(const char *username);
AuthenticationMiddleware &setPassword(const char *password);
AuthenticationMiddleware &setPasswordHash(const char *sha1AsBase64orHex);
AuthenticationMiddleware &setCallback(WebServer::THandlerFunctionAuthCheck fn);

AuthenticationMiddleware &setRealm(const char *realm);
AuthenticationMiddleware &setAuthMethod(HTTPAuthMethod method);
AuthenticationMiddleware &setAuthFailureMessage(const char *message);

bool isAllowed(WebServer &server) const;

bool run(WebServer &server, Middleware::Callback next) override;

private:
String _username;
String _password;
bool _hash = false;
WebServer::THandlerFunctionAuthCheck _callback;

const char *_realm = nullptr;
HTTPAuthMethod _method = BASIC_AUTH;
String _authFailMsg;

// AuthType _auth = AuthType::NOT_AUTHENTICATED;
// String _username;
// String _password;
// String _sha1;

// // authenticate request
// HTTPAuthMethod _mode = BASIC_AUTH;
// String _realm;
// String _authFailMsg;
};

#endif
Loading

0 comments on commit 521bbaa

Please sign in to comment.