Skip to main content

cloudreve_api/api/v4/
session.rs

1//! Session and authentication management for Cloudreve API v4
2
3use crate::Error;
4use crate::api::v4::ApiV4Client;
5use crate::api::v4::models::*;
6use serde::Serialize;
7
8impl ApiV4Client {
9    pub async fn prepare_login(&self, email: &str) -> Result<LoginPreparation, Error> {
10        let endpoint = format!("/session/prepare?email={}", email);
11        let response: crate::ApiResponse<LoginPreparation> = self.get(&endpoint).await?;
12        match response.data {
13            Some(preparation) => Ok(preparation),
14            None => Err(crate::Error::Api {
15                code: response.code,
16                message: response.msg,
17            }),
18        }
19    }
20
21    pub async fn prepare_openid_signin(
22        &self,
23        request: &OpenIdPrepareRequest<'_>,
24    ) -> Result<String, Error> {
25        let response: crate::ApiResponse<String> = self.put("/session/openid", request).await?;
26        match response.data {
27            Some(url) => Ok(url),
28            None => Err(crate::Error::Api {
29                code: response.code,
30                message: response.msg,
31            }),
32        }
33    }
34
35    pub async fn finish_openid_signin(
36        &self,
37        request: &OpenIdFinishRequest<'_>,
38    ) -> Result<LoginResponse, Error> {
39        let response: crate::ApiResponse<LoginResponse> =
40            self.post("/session/openid", request).await?;
41        match response.data {
42            Some(login_response) => Ok(login_response),
43            None => Err(crate::Error::Api {
44                code: response.code,
45                message: response.msg,
46            }),
47        }
48    }
49
50    pub async fn unlink_openid(&self, provider_id: i32) -> Result<(), Error> {
51        let endpoint = format!("/session/openid/{}", provider_id);
52        let response: crate::ApiResponse<()> = self.delete(&endpoint).await?;
53        if response.code == 0 {
54            Ok(())
55        } else {
56            Err(crate::Error::Api {
57                code: response.code,
58                message: response.msg,
59            })
60        }
61    }
62
63    pub async fn prepare_passkey_signin(&self) -> Result<PasskeySignInPreparation, Error> {
64        let response: crate::ApiResponse<PasskeySignInPreparation> =
65            self.put("/session/authn", &()).await?;
66        match response.data {
67            Some(preparation) => Ok(preparation),
68            None => Err(crate::Error::Api {
69                code: response.code,
70                message: response.msg,
71            }),
72        }
73    }
74
75    pub async fn finish_passkey_signin(
76        &self,
77        request: &PasskeySignInRequest<'_>,
78    ) -> Result<LoginResponse, Error> {
79        let response: crate::ApiResponse<LoginResponse> =
80            self.post("/session/authn", request).await?;
81        match response.data {
82            Some(login_response) => Ok(login_response),
83            None => Err(crate::Error::Api {
84                code: response.code,
85                message: response.msg,
86            }),
87        }
88    }
89
90    pub async fn login(&self, request: &LoginRequest<'_>) -> Result<LoginData, Error> {
91        // V4 login returns 203 status code when 2FA is required
92        // In this case, data contains the session ID as a string
93        let response_text = self.post_raw("/session/token", request).await?;
94
95        log::debug!("Raw login response: {}", response_text);
96
97        // First, parse as ApiResponse<String> to check for 2FA (code 203)
98        // When code is 203, data is a string (session ID)
99        if let Ok(api_response) = serde_json::from_str::<ApiResponse<String>>(&response_text) {
100            if api_response.code == 203 {
101                // 2FA required - data contains session ID
102                let session_id = api_response.data.unwrap_or_default();
103                log::debug!("2FA required, session ID: {}", session_id);
104                return Err(crate::Error::TwoFactorRequired(session_id));
105            }
106
107            // Check for other error codes
108            if api_response.code != 0 {
109                return Err(crate::Error::Api {
110                    code: api_response.code,
111                    message: api_response.msg,
112                });
113            }
114        }
115
116        // If not 2FA, parse as LoginData
117        match serde_json::from_str::<ApiResponse<LoginData>>(&response_text) {
118            Ok(api_response) => {
119                // Check for other error codes
120                if api_response.code != 0 {
121                    return Err(crate::Error::Api {
122                        code: api_response.code,
123                        message: api_response.msg,
124                    });
125                }
126
127                match api_response.data {
128                    Some(data) => Ok(data),
129                    None => Err(crate::Error::InvalidResponse(
130                        "Missing login data in response".to_string(),
131                    )),
132                }
133            }
134            Err(e) => {
135                log::error!("Failed to parse login response: {}", e);
136                log::error!("Response text: {}", response_text);
137                Err(crate::Error::InvalidResponse(format!(
138                    "Invalid login response format: {}",
139                    e
140                )))
141            }
142        }
143    }
144
145    /// Post a request and return the raw response text
146    async fn post_raw(&self, endpoint: &str, body: &impl Serialize) -> Result<String, Error> {
147        let url = self.get_url(endpoint);
148        let mut http_request = self.http_client.post(&url).json(body);
149
150        if let Some(token) = &self.token {
151            http_request = http_request.header("Authorization", format!("Bearer {}", token));
152        }
153
154        let response = http_request.send().await?;
155        let status = response.status();
156
157        let raw_text = response.text().await?;
158
159        if !status.is_success() {
160            return Err(crate::Error::Api {
161                code: status.as_u16() as i32,
162                message: raw_text.trim().to_string(),
163            });
164        }
165
166        Ok(raw_text)
167    }
168
169    pub async fn finish_2fa_login(
170        &self,
171        request: &TwoFactorLoginRequest<'_>,
172    ) -> Result<LoginData, Error> {
173        let response: ApiResponse<LoginData> = self.post("/session/token/2fa", request).await?;
174        match response.data {
175            Some(data) => Ok(data),
176            None => Err(crate::Error::Api {
177                code: response.code,
178                message: response.msg,
179            }),
180        }
181    }
182
183    pub async fn refresh_token(&self, request: &RefreshTokenRequest<'_>) -> Result<Token, Error> {
184        let response: crate::ApiResponse<Token> =
185            self.post("/session/token/refresh", request).await?;
186        match response.data {
187            Some(token) => Ok(token),
188            None => Err(crate::Error::Api {
189                code: response.code,
190                message: response.msg,
191            }),
192        }
193    }
194
195    pub async fn logout(&self) -> Result<(), Error> {
196        let _: crate::ApiResponse<()> = self.delete("/session/token").await?;
197        Ok(())
198    }
199}