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