pocketbase_rs/records/auth/auth_with_password.rs
1use serde::Serialize;
2use serde_json::Value;
3use thiserror::Error;
4
5use crate::{AuthStore, Collection, ErrorResponse};
6
7#[derive(Clone, Default, Serialize)]
8struct Credentials<'a> {
9 pub(crate) identity: &'a str,
10 pub(crate) password: &'a str,
11}
12
13/// Represents errors that can occur during the authentication process with the `PocketBase` API.
14///
15/// This enum defines various error types that may arise when attempting to authenticate,
16/// each providing details about the specific issue encountered.
17#[derive(Error, Debug)]
18pub enum AuthenticationError {
19 /// Communication with the `PocketBase` API was successful,
20 /// but returned a [400 Bad Request]("https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400") HTTP error response.
21 ///
22 /// Tip: The credentials you provided may be incorrect.
23 #[error("Authentication failed: Invalid Credentials. Given email and/or password is wrong.")]
24 InvalidCredentials,
25 /// Email and/or Password cannot be empty.
26 ///
27 /// This variant indicates that certain fields in the authentication request need to be validated.
28 /// The fields are represented as booleans:
29 ///
30 /// - `identity`: is blank and shouldn't be.
31 /// - `password`: is blank and shouldn't be.
32 #[error("Authentication failed: Empty Credential Field. Given email and/or password is empty.")]
33 EmptyField {
34 /// Is identity blank.
35 identity: bool,
36 /// Is password blank.
37 password: bool,
38 },
39 /// The provided identity must be an email address.
40 ///
41 /// This variant indicates that the authentication request failed because the provided identity
42 /// does not conform to the expected email format. The `PocketBase` API requires the identity to
43 /// be a valid email address for authentication.
44 #[error("Authentication failed. Given identity is not a valid email.")]
45 IdentityMustBeEmail,
46 /// An HTTP error occurred while communicating with the `PocketBase` API.
47 ///
48 /// This variant wraps a [`reqwest::Error`] and indicates that the request could not be completed
49 /// due to network issues, invalid URL, timeouts, etc.
50 #[error("Authentication failed. Couldn't reach the PocketBase API: {0}")]
51 HttpError(reqwest::Error),
52 /// When something unexpected was returned by the `PocketBase` REST API.
53 ///
54 /// Would usually mean that there is an error somewhere in this API wrapper.
55 #[error(
56 "Authentication failed due to an unexpected response. Usually means a problem in the PocketBase API's wrapper."
57 )]
58 UnexpectedResponse,
59 /// Occurs when you try to authenticate a `PocketBase` client without providing the collection name.
60 #[error(
61 "Authentication failed due to missing collection name. [Example: PocketBaseClientBuilder::new(\"\")"
62 )]
63 MissingCollection,
64}
65
66impl From<reqwest::Error> for AuthenticationError {
67 fn from(error: reqwest::Error) -> Self {
68 Self::HttpError(error)
69 }
70}
71
72impl Collection<'_> {
73 /// Authenticate with combination of **email**/**username** and **password**.
74 ///
75 /// On success, the auth token is automatically stored and used for subsequent requests.
76 ///
77 /// # Example
78 /// ```rust,ignore
79 /// let auth_data = pb.collection("users")
80 /// .auth_with_password("YOUR_EMAIL_OR_USERNAME", "YOUR_PASSWORD")
81 /// .await?;
82 ///
83 /// println!("Token: {}", auth_data.token);
84 /// ```
85 pub async fn auth_with_password(
86 &mut self,
87 identity: &str,
88 password: &str,
89 ) -> Result<AuthStore, AuthenticationError> {
90 let uri = format!(
91 "{}/api/collections/{}/auth-with-password",
92 self.client.base_url, self.name
93 );
94
95 let credentials = Credentials { identity, password };
96
97 let response = self
98 .client
99 .request_post_json(&uri, &credentials)
100 .send()
101 .await?;
102
103 if response.status().is_success() {
104 let auth_store = response.json::<AuthStore>().await?;
105
106 self.client.update_auth_store(auth_store.clone());
107
108 return Ok(auth_store);
109 }
110
111 if response.status() == reqwest::StatusCode::BAD_REQUEST {
112 let error_response: ErrorResponse =
113 response.json().await.unwrap_or_else(|_| ErrorResponse {
114 code: 400,
115 message: "Unknown error".to_string(),
116 data: None,
117 });
118
119 if let Some(ref data) = error_response.data {
120 // {
121 // "code": 400,
122 // "message": "Failed to authenticate.",
123 // "data": {}
124 // }
125 if data.as_object().is_some_and(serde_json::Map::is_empty) {
126 return Err(AuthenticationError::InvalidCredentials);
127 }
128
129 // Check for specific field validation errors
130 let identity_error = data
131 .get("identity")
132 .and_then(|v| v.get("code").and_then(Value::as_str));
133
134 match identity_error {
135 // {
136 // "code": 400,
137 // "message": "Something went wrong while processing your request.",
138 // "data": {
139 // "identity": {
140 // "code": "validation_is_email",
141 // "message": "Must be a valid email address."
142 // }
143 // }
144 // }
145 Some("validation_is_email") => {
146 return Err(AuthenticationError::IdentityMustBeEmail);
147 }
148
149 // {
150 // "code": 400,
151 // "message": "Something went wrong while processing your request.",
152 // "data": {
153 // "identity": {
154 // "code": "validation_required",
155 // "message": "Cannot be blank."
156 // },
157 // "password": {
158 // "code": "validation_required",
159 // "message": "Cannot be blank."
160 // }
161 // }
162 // }
163 Some("validation_required") => {
164 return Err(AuthenticationError::EmptyField {
165 identity: identity_error.is_some(),
166 password: data.get("password").is_some(),
167 });
168 }
169 None => {
170 let password_error = data.get("password").is_some();
171 return Err(AuthenticationError::EmptyField {
172 identity: false,
173 password: password_error,
174 });
175 }
176 _ => {}
177 }
178 }
179
180 // {
181 // "code": 400,
182 // "message": "Failed to authenticate.",
183 // "data": {}
184 // }
185 return Err(AuthenticationError::InvalidCredentials);
186 }
187
188 Err(AuthenticationError::UnexpectedResponse)
189 }
190}