embedded_redis/commands/
auth.rs

1//! Abstraction of AUTH command.
2//!
3//! For general information about this command, see the [Redis documentation](<https://redis.io/commands/auth/>).
4//!
5//! *Authentication is done automatically by [ConnectionHandler](crate::network::ConnectionHandler), so there is usually no need for manual execution.*
6//!
7//! # Password-only
8//! ```
9//!# use core::str::FromStr;
10//!# use std::str::Bytes;
11//!# use core::net::SocketAddr;
12//!# use std_embedded_nal::Stack;
13//!# use std_embedded_time::StandardClock;
14//!# use embedded_redis::commands::auth::AuthCommand;
15//!# use embedded_redis::network::{ConnectionHandler, Credentials};
16//!#
17//! let mut stack = Stack::default();
18//! let clock = StandardClock::default();
19//!
20//! let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
21//! let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
22//!
23//! // Cast from Credentials
24//! let command = AuthCommand::from(&Credentials::password_only("secret123!"));
25//! let _ = client.send(command);
26//!
27//! // Directly creating Auth command:
28//! let command = AuthCommand::new(None as Option<&str>, "secret123!");
29//! let _ = client.send(command);
30//! ```
31//! # Username/Password (ACL based authentication)
32//! *Requires Redis version > 6.0 + serverside ACL configuration*
33//! ```
34//!# use core::str::FromStr;
35//!# use core::net::SocketAddr;
36//!# use std_embedded_nal::Stack;
37//!# use std_embedded_time::StandardClock;
38//!# use embedded_redis::commands::auth::AuthCommand;
39//!# use embedded_redis::network::{ConnectionHandler, Credentials};
40//!#
41//! let mut stack = Stack::default();
42//! let clock = StandardClock::default();
43//!
44//! let mut connection_handler = ConnectionHandler::resp3(SocketAddr::from_str("127.0.0.1:6379").unwrap());
45//! let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
46//!
47//! // Cast from Credentials
48//! let command = AuthCommand::from(&Credentials::acl("user01", "secret123!"));
49//! let _ = client.send(command);
50//!
51//! // Directly creating Auth command:
52//! let command = AuthCommand::new(Some("user01"), "secret123!");
53//! let _ = client.send(command);
54//! ```
55//! # Error handling
56//! Successful execution is terminated by returning `Ok(())` response.
57//!
58//! Authentication errors are normally signalled by Redis with an error response, which is mapped
59//! to [CommandErrors::ErrorResponse](crate::network::CommandErrors::ErrorResponse).
60//! ```
61//!# use core::str::FromStr;
62//!# use core::net::SocketAddr;
63//!# use std_embedded_nal::Stack;
64//!# use std_embedded_time::StandardClock;
65//!# use embedded_redis::commands::auth::AuthCommand;
66//!# use embedded_redis::network::CommandErrors;
67//!# use embedded_redis::network::{ConnectionHandler, Credentials};
68//!#
69//!# let mut stack = Stack::default();
70//!# let clock = StandardClock::default();
71//!#
72//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
73//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
74//!#
75//!# let error_string = "ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?".to_string();
76//! let command = AuthCommand::from(&Credentials::password_only("wrong_password"));
77//! let result = client.send(command).unwrap().wait().unwrap_err();
78//! assert_eq!(CommandErrors::ErrorResponse(error_string), result);
79//! ```
80
81use crate::commands::builder::{CommandBuilder, ToStringOption};
82use crate::commands::{Command, ResponseTypeError};
83use crate::network::handler::Credentials;
84use bytes::Bytes;
85
86pub struct AuthCommand {
87    /// Optionally sets a username for ACL based authentication, which requires
88    /// Redis version >= 6 + ACL enabled
89    username: Option<Bytes>,
90    password: Bytes,
91}
92
93impl AuthCommand {
94    pub fn new<U, P>(username: Option<U>, password: P) -> Self
95    where
96        U: Into<Bytes>,
97        P: Into<Bytes>,
98    {
99        let mut user_bytes = None;
100        if let Some(bytes) = username {
101            user_bytes = Some(bytes.into());
102        }
103
104        AuthCommand {
105            username: user_bytes,
106            password: password.into(),
107        }
108    }
109}
110
111impl<F> Command<F> for AuthCommand
112where
113    F: ToStringOption + From<CommandBuilder>,
114{
115    type Response = ();
116
117    fn encode(&self) -> F {
118        CommandBuilder::new("AUTH")
119            .arg_option(self.username.as_ref())
120            .arg(&self.password)
121            .into()
122    }
123
124    fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
125        if frame.to_string_option().ok_or(ResponseTypeError {})? != "OK" {
126            return Err(ResponseTypeError {});
127        }
128
129        Ok(())
130    }
131}
132
133impl From<&Credentials> for AuthCommand {
134    fn from(credentials: &Credentials) -> AuthCommand {
135        AuthCommand::new(credentials.username.clone(), credentials.password.clone())
136    }
137}