1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 06:27:45 +00:00

WebServer: Add support for HTTP basic authentication

This enables the WebServer to run protected by a username and password.
While it isn't possible to access such a protected server from inside
Serenity as of now (because neither the Browser nor pro(1) support
this), this may very well be the case in the future. :^)
This commit is contained in:
Max Wipfli 2021-06-06 17:06:10 +02:00 committed by Andreas Kling
parent 1d990b3e7b
commit 79a47d9bd3
4 changed files with 53 additions and 4 deletions

View file

@ -80,6 +80,15 @@ void Client::handle_request(ReadonlyBytes raw_request)
return;
}
// Check for credentials if they are required
if (Configuration::the().credentials().has_value()) {
bool has_authenticated = verify_credentials(request.headers());
if (!has_authenticated) {
send_error_response(401, request, { "WWW-Authenticate: Basic realm=\"WebServer\", charset=\"UTF-8\"" });
return;
}
}
auto requested_path = LexicalPath::join("/", request.resource()).string();
dbgln_if(WEBSERVER_DEBUG, "Canonical requested path: '{}'", requested_path);
@ -267,13 +276,20 @@ void Client::handle_directory_listing(String const& requested_path, String const
send_response(stream, request, "text/html");
}
void Client::send_error_response(unsigned code, HTTP::HttpRequest const& request)
void Client::send_error_response(unsigned code, HTTP::HttpRequest const& request, Vector<String> const& headers)
{
auto reason_phrase = HTTP::HttpResponse::reason_phrase_for_code(code);
StringBuilder builder;
builder.appendff("HTTP/1.0 {} ", code);
builder.append(reason_phrase);
builder.append("\r\n\r\n");
builder.append("\r\n");
for (auto& header : headers) {
builder.append(header);
builder.append("\r\n");
}
builder.append("\r\n");
builder.append("<!DOCTYPE html><html><body><h1>");
builder.appendff("{} ", code);
builder.append(reason_phrase);
@ -288,4 +304,18 @@ void Client::log_response(unsigned code, HTTP::HttpRequest const& request)
outln("{} :: {:03d} :: {} {}", Core::DateTime::now().to_string(), code, request.method_name(), request.resource());
}
bool Client::verify_credentials(Vector<HTTP::HttpRequest::Header> const& headers)
{
VERIFY(Configuration::the().credentials().has_value());
auto& configured_credentials = Configuration::the().credentials().value();
for (auto& header : headers) {
if (header.name.equals_ignoring_case("Authorization")) {
auto provided_credentials = HTTP::HttpRequest::parse_http_basic_authentication_header(header.value);
if (provided_credentials.has_value() && configured_credentials.username == provided_credentials->username && configured_credentials.password == provided_credentials->password)
return true;
}
}
return false;
}
}

View file

@ -24,10 +24,11 @@ private:
void handle_request(ReadonlyBytes);
void send_response(InputStream&, HTTP::HttpRequest const&, String const& content_type);
void send_redirect(StringView redirect, HTTP::HttpRequest const&);
void send_error_response(unsigned code, HTTP::HttpRequest const&);
void send_error_response(unsigned code, HTTP::HttpRequest const&, Vector<String> const& headers = {});
void die();
void log_response(unsigned code, HTTP::HttpRequest const&);
void handle_directory_listing(String const& requested_path, String const& real_path, HTTP::HttpRequest const&);
bool verify_credentials(Vector<HTTP::HttpRequest::Header> const&);
NonnullRefPtr<Core::TCPSocket> m_socket;
};

View file

@ -6,7 +6,9 @@
#pragma once
#include <AK/Optional.h>
#include <AK/String.h>
#include <LibHTTP/HttpRequest.h>
namespace WebServer {
@ -15,13 +17,16 @@ public:
Configuration(String root_path);
String const& root_path() const { return m_root_path; }
Optional<HTTP::HttpRequest::BasicAuthenticationCredentials> const& credentials() const { return m_credentials; }
void set_root_path(String root_path) { m_root_path = move(root_path); }
void set_credentials(Optional<HTTP::HttpRequest::BasicAuthenticationCredentials> credentials) { m_credentials = move(credentials); }
static Configuration const& the();
private:
String m_root_path;
Optional<HTTP::HttpRequest::BasicAuthenticationCredentials> m_credentials;
};
}

View file

@ -10,6 +10,7 @@
#include <LibCore/EventLoop.h>
#include <LibCore/File.h>
#include <LibCore/TCPServer.h>
#include <LibHTTP/HttpRequest.h>
#include <WebServer/Client.h>
#include <WebServer/Configuration.h>
#include <stdio.h>
@ -19,14 +20,18 @@ int main(int argc, char** argv)
{
String default_listen_address = "0.0.0.0";
u16 default_port = 8000;
const char* root_path = "/www";
String root_path = "/www";
String listen_address = default_listen_address;
int port = default_port;
String username;
String password;
Core::ArgsParser args_parser;
args_parser.add_option(listen_address, "IP address to listen on", "listen-address", 'l', "listen_address");
args_parser.add_option(port, "Port to listen on", "port", 'p', "port");
args_parser.add_option(username, "HTTP basic authentication username", "user", 'U', "username");
args_parser.add_option(password, "HTTP basic authentication password", "pass", 'P', "password");
args_parser.add_positional_argument(root_path, "Path to serve the contents of", "path", Core::ArgsParser::Required::No);
args_parser.parse(argc, argv);
@ -41,6 +46,11 @@ int main(int argc, char** argv)
return 1;
}
if (username.is_empty() != password.is_empty()) {
warnln("Both username and password are required for HTTP basic authentication.");
return 1;
}
auto real_root_path = Core::File::real_path_for(root_path);
if (!Core::File::exists(real_root_path)) {
@ -55,6 +65,9 @@ int main(int argc, char** argv)
WebServer::Configuration configuration(real_root_path);
if (!username.is_empty() && !password.is_empty())
configuration.set_credentials(HTTP::HttpRequest::BasicAuthenticationCredentials { username, password });
Core::EventLoop loop;
auto server = Core::TCPServer::construct();