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
use serde::{Deserialize, Serialize};
use crate::CyberdropError;
use crate::client::CyberdropClient;
use crate::token::AuthToken;
/// Permission flags associated with a user/token verification response.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Permissions {
/// Whether the account has "user" privileges.
pub user: bool,
/// Whether the account has "poweruser" privileges.
pub poweruser: bool,
/// Whether the account has "moderator" privileges.
pub moderator: bool,
/// Whether the account has "admin" privileges.
pub admin: bool,
/// Whether the account has "superadmin" privileges.
pub superadmin: bool,
}
/// Result of verifying a token via [`crate::CyberdropClient::verify_token`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenVerification {
/// Whether the token verification succeeded.
pub success: bool,
/// Username associated with the token.
pub username: String,
/// Permission flags associated with the token.
pub permissions: Permissions,
}
#[derive(Debug, Serialize)]
pub(crate) struct LoginRequest {
pub(crate) username: String,
pub(crate) password: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct LoginResponse {
pub(crate) token: Option<AuthToken>,
}
#[derive(Debug, Serialize)]
pub(crate) struct RegisterRequest {
pub(crate) username: String,
pub(crate) password: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct RegisterResponse {
pub(crate) success: Option<bool>,
pub(crate) token: Option<AuthToken>,
pub(crate) message: Option<String>,
pub(crate) description: Option<String>,
}
#[derive(Debug, Serialize)]
pub(crate) struct VerifyTokenRequest {
pub(crate) token: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct VerifyTokenResponse {
pub(crate) success: Option<bool>,
pub(crate) username: Option<String>,
pub(crate) permissions: Option<Permissions>,
}
impl CyberdropClient {
/// Authenticate and retrieve a token.
///
/// The returned token can be installed on a client via [`CyberdropClient::with_auth_token`].
///
/// # Errors
///
/// - [`CyberdropError::AuthenticationFailed`] / [`CyberdropError::RequestFailed`] for non-2xx statuses
/// - [`CyberdropError::MissingToken`] if the response body omits the token field
/// - [`CyberdropError::Http`] for transport failures (including timeouts)
pub async fn login(
&self,
username: impl Into<String>,
password: impl Into<String>,
) -> Result<AuthToken, CyberdropError> {
let payload = LoginRequest {
username: username.into(),
password: password.into(),
};
let response: LoginResponse = self.post_json("api/login", &payload, false).await?;
response.token.ok_or(CyberdropError::MissingToken)
}
/// Register a new account and retrieve a token.
///
/// The returned token can be installed on a client via [`CyberdropClient::with_auth_token`].
///
/// Note: the API returns HTTP 200 even for validation failures; this method converts
/// `{"success":false,...}` responses into [`CyberdropError::Api`].
///
/// # Errors
///
/// - [`CyberdropError::Api`] if the API reports a validation failure (e.g. username taken)
/// - [`CyberdropError::MissingToken`] if the response body omits the token field on success
/// - [`CyberdropError::Http`] for transport failures (including timeouts)
pub async fn register(
&self,
username: impl Into<String>,
password: impl Into<String>,
) -> Result<AuthToken, CyberdropError> {
let payload = RegisterRequest {
username: username.into(),
password: password.into(),
};
let response: RegisterResponse = self.post_json("api/register", &payload, false).await?;
if response.success.unwrap_or(false) {
return response.token.ok_or(CyberdropError::MissingToken);
}
let msg = response
.description
.or(response.message)
.unwrap_or_else(|| "registration failed".to_string());
Err(CyberdropError::Api(msg))
}
/// Verify a token and fetch associated permissions.
///
/// This request does not require the client to be authenticated; the token to verify is
/// supplied in the request body.
///
/// # Errors
///
/// - [`CyberdropError::AuthenticationFailed`] / [`CyberdropError::RequestFailed`] for non-2xx statuses
/// - [`CyberdropError::MissingField`] if expected fields are missing in the response body
/// - [`CyberdropError::Http`] for transport failures (including timeouts)
pub async fn verify_token(
&self,
token: impl Into<String>,
) -> Result<TokenVerification, CyberdropError> {
let payload = VerifyTokenRequest {
token: token.into(),
};
let response: VerifyTokenResponse =
self.post_json("api/tokens/verify", &payload, false).await?;
let success = response.success.ok_or(CyberdropError::MissingField(
"verification response missing success",
))?;
let username = response.username.ok_or(CyberdropError::MissingField(
"verification response missing username",
))?;
let permissions = response.permissions.ok_or(CyberdropError::MissingField(
"verification response missing permissions",
))?;
Ok(TokenVerification {
success,
username,
permissions,
})
}
}