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