diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 8fd44b67..034322e8 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -2,6 +2,7 @@ * xref:requests_responses.adoc[] * xref:cancellation.adoc[] * xref:serialization.adoc[] +* xref:auth.adoc[] * xref:logging.adoc[] * xref:sentinel.adoc[] * xref:benchmarks.adoc[] diff --git a/doc/modules/ROOT/pages/auth.adoc b/doc/modules/ROOT/pages/auth.adoc new file mode 100644 index 00000000..59e1644f --- /dev/null +++ b/doc/modules/ROOT/pages/auth.adoc @@ -0,0 +1,49 @@ +// +// Copyright (c) 2026 Marcelo Zimbres Silva (mzimbres@gmail.com), +// Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + += Authentication + +Boost.Redis supports connecting to servers that require authentication. +To achieve it, you must send a +https://redis.io/commands/hello/[HELLO] command with the appropriate +`AUTH` parameters during connection establishment. Boost.Redis allows you +to customize this handshake by using a _setup request_: a request that +is run automatically before any other request, +every time a physical connection to the server is established. + +Configuration is done on xref:reference:boost/redis/config.adoc[`config`]. +Set `use_setup` to `true` (required for backwards compatibility) +and build the desired setup request in +`config::setup`. By default, the library sends a plain `HELLO 3` (RESP3 +without auth). To authenticate, clear the default setup and push a +`HELLO` command that includes your credentials: + +[source,cpp] +---- +config cfg; +cfg.use_setup = true; +cfg.setup.clear(); // Remove the default HELLO 3 +cfg.setup.hello("my_username", "my_password"); + +co_await conn.async_run(cfg); +---- + +Authentication is just one use of this mechanism. +For example, to select a particular logical database (see the Redis +https://redis.io/commands/select/[SELECT] command), add a `SELECT` +command after the `HELLO`: + +[source,cpp] +---- +config cfg; +cfg.use_setup = true; +cfg.setup.push("SELECT", 42); // select the logical database 42 after the default HELLO 3 + +co_await conn.async_run(cfg); +---- + diff --git a/include/boost/redis/config.hpp b/include/boost/redis/config.hpp index 4e980679..98bb7624 100644 --- a/include/boost/redis/config.hpp +++ b/include/boost/redis/config.hpp @@ -164,7 +164,7 @@ struct config { */ std::string unix_socket; - /** @brief Username used for authentication during connection establishment. + /** @brief (Deprecated) Username used for authentication during connection establishment. * * If @ref use_setup is false (the default), during connection establishment, * authentication is performed by sending a `HELLO` command. @@ -176,10 +176,20 @@ struct config { * * When using Sentinel, this setting applies to masters and replicas. * Use @ref sentinel_config::setup to configure authorization for Sentinels. + * + * @par Deprecated + * This setting is deprecated and will be removed in a subsequent release. + * Please set @ref setup, instead: + * + * @code + * cfg.use_setup = true; + * cfg.setup.clear(); + * cfg.setup.hello("my_username", "my_password"); + * @endcode */ std::string username = "default"; - /** @brief Password used for authentication during connection establishment. + /** @brief (Deprecated) Password used for authentication during connection establishment. * * If @ref use_setup is false (the default), during connection establishment, * authentication is performed by sending a `HELLO` command. @@ -191,10 +201,20 @@ struct config { * * When using Sentinel, this setting applies to masters and replicas. * Use @ref sentinel_config::setup to configure authorization for Sentinels. + * + * @par Deprecated + * This setting is deprecated and will be removed in a subsequent release. + * Please set @ref setup, instead: + * + * @code + * cfg.use_setup = true; + * cfg.setup.clear(); + * cfg.setup.hello("my_username", "my_password"); + * @endcode */ std::string password; - /** @brief Client name parameter to use during connection establishment. + /** @brief (Deprecated) Client name parameter to use during connection establishment. * * If @ref use_setup is false (the default), during connection establishment, * a `HELLO` command is sent. If this field is not empty, the `HELLO` command @@ -202,10 +222,20 @@ struct config { * * When using Sentinel, this setting applies to masters and replicas. * Use @ref sentinel_config::setup to configure this value for Sentinels. + * + * @par Deprecated + * This setting is deprecated and will be removed in a subsequent release. + * Please set @ref setup, instead: + * + * @code + * cfg.use_setup = true; + * cfg.setup.clear(); + * cfg.setup.hello_setname("my_client_name"); + * @endcode */ std::string clientname = "Boost.Redis"; - /** @brief Database index to pass to the `SELECT` command during connection establishment. + /** @brief (Deprecated) Database index to pass to the `SELECT` command during connection establishment. * * If @ref use_setup is false (the default), and this field is set to a * non-empty optional, and its value is different than zero, @@ -213,6 +243,15 @@ struct config { * database index. By default, no `SELECT` command is sent. * * When using Sentinel, this setting applies to masters and replicas. + * + * @par Deprecated + * This setting is deprecated and will be removed in a subsequent release. + * Please set @ref setup, instead: + * + * @code + * cfg.use_setup = true; + * cfg.setup.push("SELECT", 4); // select database index 4 + * @endcode */ std::optional database_index = 0; diff --git a/include/boost/redis/impl/request.ipp b/include/boost/redis/impl/request.ipp index 72653897..e00d0f76 100644 --- a/include/boost/redis/impl/request.ipp +++ b/include/boost/redis/impl/request.ipp @@ -28,13 +28,15 @@ auto has_response(std::string_view cmd) -> bool request make_hello_request() { request req; - req.push("HELLO", "3"); + req.hello(); return req; } } // namespace boost::redis::detail -void boost::redis::request::append(const request& other) +namespace boost::redis { + +void request::append(const request& other) { // Remember the old payload size, to update offsets std::size_t old_offset = payload_.size(); @@ -55,7 +57,7 @@ void boost::redis::request::append(const request& other) } } -void boost::redis::request::add_pubsub_arg(detail::pubsub_change_type type, std::string_view value) +void request::add_pubsub_arg(detail::pubsub_change_type type, std::string_view value) { // Add the argument resp3::add_bulk(payload_, value); @@ -65,3 +67,25 @@ void boost::redis::request::add_pubsub_arg(detail::pubsub_change_type type, std: std::size_t offset = payload_.size() - value.size() - 2u; pubsub_changes_.push_back({type, offset, value.size()}); } + +void request::hello() { push("HELLO", "3"); } + +void request::hello(std::string_view username, std::string_view password) +{ + push("HELLO", "3", "AUTH", username, password); +} + +void request::hello_setname(std::string_view client_name) +{ + push("HELLO", "3", "SETNAME", client_name); +} + +void request::hello_setname( + std::string_view username, + std::string_view password, + std::string_view client_name) +{ + push("HELLO", "3", "AUTH", username, password, "SETNAME", client_name); +} + +} // namespace boost::redis diff --git a/include/boost/redis/impl/setup_request_utils.hpp b/include/boost/redis/impl/setup_request_utils.hpp index 2fb0c603..33451bae 100644 --- a/include/boost/redis/impl/setup_request_utils.hpp +++ b/include/boost/redis/impl/setup_request_utils.hpp @@ -53,13 +53,13 @@ inline void compose_setup_request( // Gather everything we can in a HELLO command if (send_auth && send_setname) - req.push("HELLO", "3", "AUTH", cfg.username, cfg.password, "SETNAME", cfg.clientname); + req.hello_setname(cfg.username, cfg.password, cfg.clientname); else if (send_auth) - req.push("HELLO", "3", "AUTH", cfg.username, cfg.password); + req.hello(cfg.username, cfg.password); else if (send_setname) - req.push("HELLO", "3", "SETNAME", cfg.clientname); + req.hello_setname(cfg.clientname); else - req.push("HELLO", "3"); + req.hello(); // SELECT is independent of HELLO if (cfg.database_index && cfg.database_index.value() != 0) diff --git a/include/boost/redis/request.hpp b/include/boost/redis/request.hpp index d3104a78..c6a4ef21 100644 --- a/include/boost/redis/request.hpp +++ b/include/boost/redis/request.hpp @@ -724,6 +724,51 @@ class request { patterns_end); } + /** @brief Appends a HELLO 3 command to the end of the request. + * + * Equivalent to adding the Redis command `HELLO 3`. + */ + void hello(); + + /** @brief Appends a HELLO 3 command with AUTH to the end of the request. + * + * Equivalent to the adding the following Redis command: + * @code + * HELLO 3 AUTH + * @endcode + * + * @param username The ACL username. + * @param password The password for the user. + */ + void hello(std::string_view username, std::string_view password); + + /** @brief Appends a HELLO 3 command with SETNAME to the end of the request. + * + * Equivalent to adding the following Redis command: + * @code + * HELLO 3 SETNAME + * @endcode + * + * @param client_name The client name (visible in CLIENT LIST). + */ + void hello_setname(std::string_view client_name); + + /** @brief Appends a HELLO 3 command with AUTH and SETNAME to the end of the request. + * + * Equivalent to adding the following Redis command: + * @code + * HELLO 3 AUTH SETNAME + * @endcode + * + * @param username The ACL username. + * @param password The password for the user. + * @param client_name The client name (visible in CLIENT LIST). + */ + void hello_setname( + std::string_view username, + std::string_view password, + std::string_view client_name); + private: void check_cmd(std::string_view cmd) { diff --git a/test/test_request.cpp b/test/test_request.cpp index 9777e790..a43469fb 100644 --- a/test/test_request.cpp +++ b/test/test_request.cpp @@ -467,6 +467,42 @@ void test_mix_pubsub_regular() check_pubsub_changes(req, expected_changes); } +// --- hello --- +void test_hello() +{ + request req; + req.hello(); + BOOST_TEST_EQ(req.payload(), "*2\r\n$5\r\nHELLO\r\n$1\r\n3\r\n"); +} + +void test_hello_auth() +{ + request req; + req.hello("user", "pass"); + BOOST_TEST_EQ( + req.payload(), + "*5\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$4\r\nAUTH\r\n$4\r\nuser\r\n$4\r\npass\r\n"); +} + +void test_hello_setname() +{ + request req; + req.hello_setname("myclient"); + BOOST_TEST_EQ( + req.payload(), + "*4\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$7\r\nSETNAME\r\n$8\r\nmyclient\r\n"); +} + +void test_hello_setname_auth() +{ + request req; + req.hello_setname("user", "pass", "myclient"); + BOOST_TEST_EQ( + req.payload(), + "*7\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$4\r\nAUTH\r\n$4\r\nuser\r\n$4\r\npass\r\n" + "$7\r\nSETNAME\r\n$8\r\nmyclient\r\n"); +} + // --- append --- void test_append() { @@ -703,6 +739,11 @@ int main() test_mix_pubsub_regular(); + test_hello(); + test_hello_auth(); + test_hello_setname(); + test_hello_setname_auth(); + test_append(); test_append_no_response(); test_append_flags();