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;
|
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();
|
auto requested_path = LexicalPath::join("/", request.resource()).string();
|
||||||
dbgln_if(WEBSERVER_DEBUG, "Canonical requested path: '{}'", requested_path);
|
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");
|
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);
|
auto reason_phrase = HTTP::HttpResponse::reason_phrase_for_code(code);
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.appendff("HTTP/1.0 {} ", code);
|
builder.appendff("HTTP/1.0 {} ", code);
|
||||||
builder.append(reason_phrase);
|
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.append("<!DOCTYPE html><html><body><h1>");
|
||||||
builder.appendff("{} ", code);
|
builder.appendff("{} ", code);
|
||||||
builder.append(reason_phrase);
|
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());
|
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 handle_request(ReadonlyBytes);
|
||||||
void send_response(InputStream&, HTTP::HttpRequest const&, String const& content_type);
|
void send_response(InputStream&, HTTP::HttpRequest const&, String const& content_type);
|
||||||
void send_redirect(StringView redirect, HTTP::HttpRequest const&);
|
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 die();
|
||||||
void log_response(unsigned code, HTTP::HttpRequest const&);
|
void log_response(unsigned code, HTTP::HttpRequest const&);
|
||||||
void handle_directory_listing(String const& requested_path, String const& real_path, 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;
|
NonnullRefPtr<Core::TCPSocket> m_socket;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Optional.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
|
#include <LibHTTP/HttpRequest.h>
|
||||||
|
|
||||||
namespace WebServer {
|
namespace WebServer {
|
||||||
|
|
||||||
|
@ -15,13 +17,16 @@ public:
|
||||||
Configuration(String root_path);
|
Configuration(String root_path);
|
||||||
|
|
||||||
String const& root_path() const { return m_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_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();
|
static Configuration const& the();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String m_root_path;
|
String m_root_path;
|
||||||
|
Optional<HTTP::HttpRequest::BasicAuthenticationCredentials> m_credentials;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <LibCore/EventLoop.h>
|
#include <LibCore/EventLoop.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibCore/TCPServer.h>
|
#include <LibCore/TCPServer.h>
|
||||||
|
#include <LibHTTP/HttpRequest.h>
|
||||||
#include <WebServer/Client.h>
|
#include <WebServer/Client.h>
|
||||||
#include <WebServer/Configuration.h>
|
#include <WebServer/Configuration.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -19,14 +20,18 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
String default_listen_address = "0.0.0.0";
|
String default_listen_address = "0.0.0.0";
|
||||||
u16 default_port = 8000;
|
u16 default_port = 8000;
|
||||||
const char* root_path = "/www";
|
String root_path = "/www";
|
||||||
|
|
||||||
String listen_address = default_listen_address;
|
String listen_address = default_listen_address;
|
||||||
int port = default_port;
|
int port = default_port;
|
||||||
|
String username;
|
||||||
|
String password;
|
||||||
|
|
||||||
Core::ArgsParser args_parser;
|
Core::ArgsParser args_parser;
|
||||||
args_parser.add_option(listen_address, "IP address to listen on", "listen-address", 'l', "listen_address");
|
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(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.add_positional_argument(root_path, "Path to serve the contents of", "path", Core::ArgsParser::Required::No);
|
||||||
args_parser.parse(argc, argv);
|
args_parser.parse(argc, argv);
|
||||||
|
|
||||||
|
@ -41,6 +46,11 @@ int main(int argc, char** argv)
|
||||||
return 1;
|
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);
|
auto real_root_path = Core::File::real_path_for(root_path);
|
||||||
|
|
||||||
if (!Core::File::exists(real_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);
|
WebServer::Configuration configuration(real_root_path);
|
||||||
|
|
||||||
|
if (!username.is_empty() && !password.is_empty())
|
||||||
|
configuration.set_credentials(HTTP::HttpRequest::BasicAuthenticationCredentials { username, password });
|
||||||
|
|
||||||
Core::EventLoop loop;
|
Core::EventLoop loop;
|
||||||
|
|
||||||
auto server = Core::TCPServer::construct();
|
auto server = Core::TCPServer::construct();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue