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:
parent
1d990b3e7b
commit
79a47d9bd3
4 changed files with 53 additions and 4 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue