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}