ESP32_ChinaDieselHeater_Con.../lib/esp32_https_server-master/examples/Authentication/Authentication.ino

353 lines
14 KiB
C++

/**
* Example for the ESP32 HTTP(S) Webserver
*
* IMPORTANT NOTE:
* To run this script, your need to
* 1) Enter your WiFi SSID and PSK below this comment
* 2) Make sure to have certificate data available. You will find a
* shell script and instructions to do so in the library folder
* under extras/
*
* This script will install an HTTPS Server on your ESP32 with the following
* functionalities:
* - Show simple page on web server root
* - Provide some "internal pages" that are protected by the server
* - Run a middleware that authenticates the user
* - Run a middleware that provides access control
* - 404 for everything else
* Authentication is done using HTTP Basic Auth, which is supported by the webserver,
* so you don't have to care about retrieving the login information from request
* headers.
*/
// TODO: Configure your WiFi here
#define WIFI_SSID "<your ssid goes here>"
#define WIFI_PSK "<your pre-shared key goes here>"
// Include certificate data (see note above)
#include "cert.h"
#include "private_key.h"
// We will use wifi
#include <WiFi.h>
// For the middleware
#include <functional>
// We define two new HTTP-Header names. Those headers will be used internally
// to store the user name and group after authentication. If the client provides
// these headers, they will be ignored to prevent authentication bypass.
#define HEADER_USERNAME "X-USERNAME"
#define HEADER_GROUP "X-GROUP"
// Includes for the server
#include <HTTPSServer.hpp>
#include <SSLCert.hpp>
#include <HTTPRequest.hpp>
#include <HTTPResponse.hpp>
// The HTTPS Server comes in a separate namespace. For easier use, include it here.
using namespace httpsserver;
// Create an SSL certificate object from the files included above
SSLCert cert = SSLCert(
example_crt_DER, example_crt_DER_len,
example_key_DER, example_key_DER_len
);
// Create an SSL-enabled server that uses the certificate
// The contstructor takes some more parameters, but we go for default values here.
HTTPSServer secureServer = HTTPSServer(&cert);
// Declare some handler functions for the various URLs on the server
void handleRoot(HTTPRequest * req, HTTPResponse * res);
void handleInternalPage(HTTPRequest * req, HTTPResponse * res);
void handleAdminPage(HTTPRequest * req, HTTPResponse * res);
void handlePublicPage(HTTPRequest * req, HTTPResponse * res);
void handle404(HTTPRequest * req, HTTPResponse * res);
// Declare a middleware function.
// Parameters:
// req: Request data, can be used to access URL, HTTP Method, Headers, ...
// res: Response data, can be used to access HTTP Status, Headers, ...
// next: This function is used to pass control down the chain. If you have done your work
// with the request object, you may decide if you want to process the request.
// If you do so, you call the next() function, and the next middleware function (if
// there is any) or the actual requestHandler will be called.
// If you want to skip the request, you do not call next, and set for example status
// code 403 on the response to show that the user is not allowed to access a specific
// resource.
// For more details, see the definition below.
void middlewareAuthentication(HTTPRequest * req, HTTPResponse * res, std::function<void()> next);
void middlewareAuthorization(HTTPRequest * req, HTTPResponse * res, std::function<void()> next);
void setup() {
// For logging
Serial.begin(115200);
// Connect to WiFi
Serial.println("Setting up WiFi");
WiFi.begin(WIFI_SSID, WIFI_PSK);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.print("Connected. IP=");
Serial.println(WiFi.localIP());
// For every resource available on the server, we need to create a ResourceNode
// The ResourceNode links URL and HTTP method to a handler function
ResourceNode * nodeRoot = new ResourceNode("/", "GET", &handleRoot);
ResourceNode * nodeInternal = new ResourceNode("/internal", "GET", &handleInternalPage);
ResourceNode * nodeAdmin = new ResourceNode("/internal/admin", "GET", &handleAdminPage);
ResourceNode * nodePublic = new ResourceNode("/public", "GET", &handlePublicPage);
ResourceNode * node404 = new ResourceNode("", "GET", &handle404);
// Add the nodes to the server
secureServer.registerNode(nodeRoot);
secureServer.registerNode(nodeInternal);
secureServer.registerNode(nodeAdmin);
secureServer.registerNode(nodePublic);
// Add the 404 not found node to the server.
// The path is ignored for the default node.
secureServer.setDefaultNode(node404);
// Add the middleware. These functions will be called globally for every request
// Note: The functions are called in the order they are added to the server.
// This means, we need to add the authentication middleware first, because the
// authorization middleware needs the headers that will be set by the authentication
// middleware (First we check the identity, then we see what the user is allowed to do)
secureServer.addMiddleware(&middlewareAuthentication);
secureServer.addMiddleware(&middlewareAuthorization);
Serial.println("Starting server...");
secureServer.start();
if (secureServer.isRunning()) {
Serial.println("Server ready.");
}
}
void loop() {
// This call will let the server do its work
secureServer.loop();
// Other code would go here...
delay(1);
}
/**
* The following middleware function is one of two functions dealing with access control. The
* middlewareAuthentication() will interpret the HTTP Basic Auth header, check usernames and password,
* and if they are valid, set the X-USERNAME and X-GROUP header.
*
* If they are invalid, the X-USERNAME and X-GROUP header will be unset. This is important because
* otherwise the client may manipulate those internal headers.
*
* Having that done, further middleware functions and the request handler functions will be able to just
* use req->getHeader("X-USERNAME") to find out if the user is logged in correctly.
*
* Furthermore, if the user supplies credentials and they are invalid, he will receive an 401 response
* without any other functions being called.
*/
void middlewareAuthentication(HTTPRequest * req, HTTPResponse * res, std::function<void()> next) {
// Unset both headers to discard any value from the client
// This prevents authentication bypass by a client that just sets X-USERNAME
req->setHeader(HEADER_USERNAME, "");
req->setHeader(HEADER_GROUP, "");
// Get login information from request
// If you use HTTP Basic Auth, you can retrieve the values from the request.
// The return values will be empty strings if the user did not provide any data,
// or if the format of the Authorization header is invalid (eg. no Basic Method
// for Authorization, or an invalid Base64 token)
std::string reqUsername = req->getBasicAuthUser();
std::string reqPassword = req->getBasicAuthPassword();
// If the user entered login information, we will check it
if (reqUsername.length() > 0 && reqPassword.length() > 0) {
// _Very_ simple hardcoded user database to check credentials and assign the group
bool authValid = true;
std::string group = "";
if (reqUsername == "admin" && reqPassword == "secret") {
group = "ADMIN";
} else if (reqUsername == "user" && reqPassword == "test") {
group = "USER";
} else {
authValid = false;
}
// If authentication was successful
if (authValid) {
// set custom headers and delegate control
req->setHeader(HEADER_USERNAME, reqUsername);
req->setHeader(HEADER_GROUP, group);
// The user tried to authenticate and was successful
// -> We proceed with this request.
next();
} else {
// Display error page
res->setStatusCode(401);
res->setStatusText("Unauthorized");
res->setHeader("Content-Type", "text/plain");
// This should trigger the browser user/password dialog, and it will tell
// the client how it can authenticate
res->setHeader("WWW-Authenticate", "Basic realm=\"ESP32 privileged area\"");
// Small error text on the response document. In a real-world scenario, you
// shouldn't display the login information on this page, of course ;-)
res->println("401. Unauthorized (try admin/secret or user/test)");
// NO CALL TO next() here, as the authentication failed.
// -> The code above did handle the request already.
}
} else {
// No attempt to authenticate
// -> Let the request pass through by calling next()
next();
}
}
/**
* This function plays together with the middlewareAuthentication(). While the first function checks the
* username/password combination and stores it in the request, this function makes use of this information
* to allow or deny access.
*
* This example only prevents unauthorized access to every ResourceNode stored under an /internal/... path.
*/
void middlewareAuthorization(HTTPRequest * req, HTTPResponse * res, std::function<void()> next) {
// Get the username (if any)
std::string username = req->getHeader(HEADER_USERNAME);
// Check that only logged-in users may get to the internal area (All URLs starting with /internal)
// Only a simple example, more complicated configuration is up to you.
if (username == "" && req->getRequestString().substr(0,9) == "/internal") {
// Same as the deny-part in middlewareAuthentication()
res->setStatusCode(401);
res->setStatusText("Unauthorized");
res->setHeader("Content-Type", "text/plain");
res->setHeader("WWW-Authenticate", "Basic realm=\"ESP32 privileged area\"");
res->println("401. Unauthorized (try admin/secret or user/test)");
// No call denies access to protected handler function.
} else {
// Everything else will be allowed, so we call next()
next();
}
}
// This is the internal page. It will greet the user with
// a personalized message and - if the user is in the ADMIN group -
// provide a link to the admin interface.
void handleInternalPage(HTTPRequest * req, HTTPResponse * res) {
// Header
res->setStatusCode(200);
res->setStatusText("OK");
res->setHeader("Content-Type", "text/html; charset=utf8");
// Write page
res->println("<!DOCTYPE html>");
res->println("<html>");
res->println("<head>");
res->println("<title>Internal Area</title>");
res->println("</head>");
res->println("<body>");
// Personalized greeting
res->print("<h1>Hello ");
// We can safely use the header value, this area is only accessible if it's
// set (the middleware takes care of this)
res->printStd(req->getHeader(HEADER_USERNAME));
res->print("!</h1>");
res->println("<p>Welcome to the internal area. Congratulations on successfully entering your password!</p>");
// The "admin area" will only be shown if the correct group has been assigned in the authenticationMiddleware
if (req->getHeader(HEADER_GROUP) == "ADMIN") {
res->println("<div style=\"border:1px solid red;margin: 20px auto;padding:10px;background:#ff8080\">");
res->println("<h2>You are an administrator</h2>");
res->println("<p>You are allowed to access the admin page:</p>");
res->println("<p><a href=\"/internal/admin\">Go to secret admin page</a></p>");
res->println("</div>");
}
// Link to the root page
res->println("<p><a href=\"/\">Go back home</a></p>");
res->println("</body>");
res->println("</html>");
}
void handleAdminPage(HTTPRequest * req, HTTPResponse * res) {
// Headers
res->setHeader("Content-Type", "text/html; charset=utf8");
std::string header = "<!DOCTYPE html><html><head><title>Secret Admin Page</title></head><body><h1>Secret Admin Page</h1>";
std::string footer = "</body></html>";
// Checking permissions can not only be done centrally in the middleware function but also in the actual request handler.
// This would be handy if you provide an API with lists of resources, but access rights are defined object-based.
if (req->getHeader(HEADER_GROUP) == "ADMIN") {
res->setStatusCode(200);
res->setStatusText("OK");
res->printStd(header);
res->println("<div style=\"border:1px solid red;margin: 20px auto;padding:10px;background:#ff8080\">");
res->println("<h1>Congratulations</h1>");
res->println("<p>You found the secret administrator page!</p>");
res->println("<p><a href=\"/internal\">Go back</a></p>");
res->println("</div>");
} else {
res->printStd(header);
res->setStatusCode(403);
res->setStatusText("Unauthorized");
res->println("<p><strong>403 Unauthorized</strong> You have no power here!</p>");
}
res->printStd(footer);
}
// Just a simple page for demonstration, very similar to the root page.
void handlePublicPage(HTTPRequest * req, HTTPResponse * res) {
res->setHeader("Content-Type", "text/html");
res->println("<!DOCTYPE html>");
res->println("<html>");
res->println("<head><title>Hello World!</title></head>");
res->println("<body>");
res->println("<h1>Hello World!</h1>");
res->print("<p>Your server is running for ");
res->print((int)(millis()/1000), DEC);
res->println(" seconds.</p>");
res->println("<p><a href=\"/\">Go back</a></p>");
res->println("</body>");
res->println("</html>");
}
// For details on the implementation of the hanlder functions, refer to the Static-Page example.
void handleRoot(HTTPRequest * req, HTTPResponse * res) {
res->setHeader("Content-Type", "text/html");
res->println("<!DOCTYPE html>");
res->println("<html>");
res->println("<head><title>Hello World!</title></head>");
res->println("<body>");
res->println("<h1>Hello World!</h1>");
res->println("<p>This is the authentication and authorization example. When asked for login "
"information, try admin/secret or user/test.</p>");
res->println("<p>Go to: <a href=\"/internal\">Internal Page</a> | <a href=\"/public\">Public Page</a></p>");
res->println("</body>");
res->println("</html>");
}
void handle404(HTTPRequest * req, HTTPResponse * res) {
req->discardRequestBody();
res->setStatusCode(404);
res->setStatusText("Not Found");
res->setHeader("Content-Type", "text/html");
res->println("<!DOCTYPE html>");
res->println("<html>");
res->println("<head><title>Not Found</title></head>");
res->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>");
res->println("</html>");
}