Skip to main content

cyberdrop_client/
account.rs

1use serde::{Deserialize, Serialize};
2
3use crate::CyberdropError;
4use crate::client::CyberdropClient;
5use crate::token::AuthToken;
6
7/// Permission flags associated with a user/token verification response.
8#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
9pub struct Permissions {
10    /// Whether the account has "user" privileges.
11    pub user: bool,
12    /// Whether the account has "poweruser" privileges.
13    pub poweruser: bool,
14    /// Whether the account has "moderator" privileges.
15    pub moderator: bool,
16    /// Whether the account has "admin" privileges.
17    pub admin: bool,
18    /// Whether the account has "superadmin" privileges.
19    pub superadmin: bool,
20}
21
22/// Result of verifying a token via [`crate::CyberdropClient::verify_token`].
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct TokenVerification {
25    /// Whether the token verification succeeded.
26    pub success: bool,
27    /// Username associated with the token.
28    pub username: String,
29    /// Permission flags associated with the token.
30    pub permissions: Permissions,
31}
32
33#[derive(Debug, Serialize)]
34pub(crate) struct LoginRequest {
35    pub(crate) username: String,
36    pub(crate) password: String,
37}
38
39#[derive(Debug, Deserialize)]
40pub(crate) struct LoginResponse {
41    pub(crate) token: Option<AuthToken>,
42}
43
44#[derive(Debug, Serialize)]
45pub(crate) struct RegisterRequest {
46    pub(crate) username: String,
47    pub(crate) password: String,
48}
49
50#[derive(Debug, Deserialize)]
51pub(crate) struct RegisterResponse {
52    pub(crate) success: Option<bool>,
53    pub(crate) token: Option<AuthToken>,
54    pub(crate) message: Option<String>,
55    pub(crate) description: Option<String>,
56}
57
58#[derive(Debug, Serialize)]
59pub(crate) struct VerifyTokenRequest {
60    pub(crate) token: String,
61}
62
63#[derive(Debug, Deserialize)]
64pub(crate) struct VerifyTokenResponse {
65    pub(crate) success: Option<bool>,
66    pub(crate) username: Option<String>,
67    pub(crate) permissions: Option<Permissions>,
68}
69
70impl CyberdropClient {
71    /// Authenticate and retrieve a token.
72    ///
73    /// The returned token can be installed on a client via [`CyberdropClient::with_auth_token`].
74    ///
75    /// # Errors
76    ///
77    /// - [`CyberdropError::AuthenticationFailed`] / [`CyberdropError::RequestFailed`] for non-2xx statuses
78    /// - [`CyberdropError::MissingToken`] if the response body omits the token field
79    /// - [`CyberdropError::Http`] for transport failures (including timeouts)
80    pub async fn login(
81        &self,
82        username: impl Into<String>,
83        password: impl Into<String>,
84    ) -> Result<AuthToken, CyberdropError> {
85        let payload = LoginRequest {
86            username: username.into(),
87            password: password.into(),
88        };
89
90        let response: LoginResponse = self.post_json("api/login", &payload, false).await?;
91        response.token.ok_or(CyberdropError::MissingToken)
92    }
93
94    /// Register a new account and retrieve a token.
95    ///
96    /// The returned token can be installed on a client via [`CyberdropClient::with_auth_token`].
97    ///
98    /// Note: the API returns HTTP 200 even for validation failures; this method converts
99    /// `{"success":false,...}` responses into [`CyberdropError::Api`].
100    ///
101    /// # Errors
102    ///
103    /// - [`CyberdropError::Api`] if the API reports a validation failure (e.g. username taken)
104    /// - [`CyberdropError::MissingToken`] if the response body omits the token field on success
105    /// - [`CyberdropError::Http`] for transport failures (including timeouts)
106    pub async fn register(
107        &self,
108        username: impl Into<String>,
109        password: impl Into<String>,
110    ) -> Result<AuthToken, CyberdropError> {
111        let payload = RegisterRequest {
112            username: username.into(),
113            password: password.into(),
114        };
115
116        let response: RegisterResponse = self.post_json("api/register", &payload, false).await?;
117
118        if response.success.unwrap_or(false) {
119            return response.token.ok_or(CyberdropError::MissingToken);
120        }
121
122        let msg = response
123            .description
124            .or(response.message)
125            .unwrap_or_else(|| "registration failed".to_string());
126
127        Err(CyberdropError::Api(msg))
128    }
129
130    /// Verify a token and fetch associated permissions.
131    ///
132    /// This request does not require the client to be authenticated; the token to verify is
133    /// supplied in the request body.
134    ///
135    /// # Errors
136    ///
137    /// - [`CyberdropError::AuthenticationFailed`] / [`CyberdropError::RequestFailed`] for non-2xx statuses
138    /// - [`CyberdropError::MissingField`] if expected fields are missing in the response body
139    /// - [`CyberdropError::Http`] for transport failures (including timeouts)
140    pub async fn verify_token(
141        &self,
142        token: impl Into<String>,
143    ) -> Result<TokenVerification, CyberdropError> {
144        let payload = VerifyTokenRequest {
145            token: token.into(),
146        };
147
148        let response: VerifyTokenResponse =
149            self.post_json("api/tokens/verify", &payload, false).await?;
150
151        let success = response.success.ok_or(CyberdropError::MissingField(
152            "verification response missing success",
153        ))?;
154        let username = response.username.ok_or(CyberdropError::MissingField(
155            "verification response missing username",
156        ))?;
157        let permissions = response.permissions.ok_or(CyberdropError::MissingField(
158            "verification response missing permissions",
159        ))?;
160
161        Ok(TokenVerification {
162            success,
163            username,
164            permissions,
165        })
166    }
167}