biurs_core/
client.rs

1use jsonwebtoken::EncodingKey;
2
3#[derive(Debug, thiserror::Error)]
4pub enum ClientError {
5    #[error(transparent)]
6    TonicStatus(#[from] tonic::Status),
7    #[error(transparent)]
8    Transport(#[from] tonic::transport::Error),
9    #[error(transparent)]
10    Type(#[from] crate::types::TypeError),
11    #[error("Expected message")]
12    MessageExpected,
13    #[error("Unexpected message type")]
14    UnexpectedMessageType,
15    #[error("Channel send error: {0}")]
16    Send(String),
17    #[error(transparent)]
18    Jwt(#[from] jsonwebtoken::errors::Error),
19    #[error(transparent)]
20    InvalidMetadata(#[from] tonic::metadata::errors::InvalidMetadataValue),
21    #[error("Unauthorized")]
22    Unauthorized,
23    #[error(transparent)]
24    Json(#[from] serde_json::Error),
25}
26
27#[async_trait::async_trait]
28pub trait Client {
29    async fn auth(&self) -> Result<String, ClientError>;
30
31    async fn status(
32        &self,
33        request: crate::types::status::StatusRequest,
34        auth_token: &str,
35    ) -> Result<crate::types::status::StatusResponse, ClientError>;
36
37    async fn list(&self, auth_token: &str) -> Result<Vec<crate::types::Metadata>, ClientError>;
38
39    async fn upload(
40        &self,
41        request: crate::types::upload::UploadFile,
42        auth_token: &str,
43    ) -> Result<(), ClientError>;
44
45    async fn download(
46        &self,
47        request: crate::types::download::DownloadRequest,
48        auth_token: &str,
49    ) -> Result<crate::types::download::DownloadedFile, ClientError>;
50}
51
52pub struct BiursClient {
53    client: crate::proto::biurs_v1::back_it_up_client::BackItUpClient<tonic::transport::Channel>,
54    encoding_key: jsonwebtoken::EncodingKey,
55}
56
57impl BiursClient {
58    pub async fn new(url: String, encoding_key: EncodingKey) -> Result<Self, ClientError> {
59        Ok(Self {
60            client: crate::proto::biurs_v1::back_it_up_client::BackItUpClient::connect(url).await?,
61            encoding_key,
62        })
63    }
64}
65
66#[async_trait::async_trait]
67impl Client for BiursClient {
68    async fn auth(&self) -> Result<String, ClientError> {
69        let (tx_req, rx_req) =
70            tokio::sync::mpsc::channel::<crate::proto::biurs_v1::AuthenticateRequest>(64);
71        let stream = tokio_stream::wrappers::ReceiverStream::new(rx_req);
72        let mut response = self
73            .client
74            .clone()
75            .authenticate(tonic::Request::new(stream))
76            .await?
77            .into_inner();
78
79        tracing::info!("sending challenge request");
80        let create_challenge = crate::types::auth::AuthenticateRequest::CreateChallenge;
81        tx_req
82            .send(create_challenge.into())
83            .await
84            .map_err(|err| ClientError::Send(err.to_string()))?;
85
86        tracing::info!("reading challenge");
87        let message: crate::types::auth::AuthenticateResponse = response
88            .message()
89            .await?
90            .ok_or(ClientError::MessageExpected)?
91            .try_into()?;
92        let signature = match message {
93            crate::types::auth::AuthenticateResponse::Challenge(challenge) => {
94                let header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::EdDSA);
95                let claims =
96                    serde_json::from_str::<crate::types::auth::ChallengeClaims>(&challenge)?;
97                jsonwebtoken::encode(&header, &claims, &self.encoding_key)?
98            }
99            crate::types::auth::AuthenticateResponse::Token(_) => {
100                return Err(ClientError::UnexpectedMessageType)
101            }
102            crate::types::auth::AuthenticateResponse::Unauthorized => {
103                return Err(ClientError::Unauthorized)
104            }
105        };
106
107        tracing::info!("sending signature");
108        tx_req
109            .send(crate::types::auth::AuthenticateRequest::VerifyChallenge(signature).into())
110            .await
111            .map_err(|err| ClientError::Send(err.to_string()))?;
112
113        tracing::info!("reading token");
114        let message: crate::types::auth::AuthenticateResponse = response
115            .message()
116            .await?
117            .ok_or(ClientError::MessageExpected)?
118            .try_into()?;
119        let token = match message {
120            crate::types::auth::AuthenticateResponse::Challenge(_) => {
121                return Err(ClientError::UnexpectedMessageType)
122            }
123            crate::types::auth::AuthenticateResponse::Token(token) => token,
124            crate::types::auth::AuthenticateResponse::Unauthorized => {
125                return Err(ClientError::Unauthorized)
126            }
127        };
128
129        Ok(token)
130    }
131
132    async fn status(
133        &self,
134        request: crate::types::status::StatusRequest,
135        auth_token: &str,
136    ) -> Result<crate::types::status::StatusResponse, ClientError> {
137        let mut request = tonic::Request::new(request.into());
138        request
139            .metadata_mut()
140            .append("authentication", auth_token.try_into()?);
141        let response = self.client.clone().status(request).await?;
142        Ok(response.into_inner().try_into()?)
143    }
144
145    async fn list(&self, auth_token: &str) -> Result<Vec<crate::types::Metadata>, ClientError> {
146        let mut request = tonic::Request::new(crate::proto::biurs_v1::ListRequest {});
147        request
148            .metadata_mut()
149            .append("authentication", auth_token.try_into()?);
150
151        let response = self.client.clone().list(request).await?;
152        Ok(response.into_inner().try_into()?)
153    }
154
155    async fn upload(
156        &self,
157        request: crate::types::upload::UploadFile,
158        auth_token: &str,
159    ) -> Result<(), ClientError> {
160        use futures::StreamExt;
161
162        let mut chunks = vec![crate::types::upload::UploadRequest::Meta(request.meta)];
163        for chunk in request.data.chunks(2048) {
164            chunks.push(crate::types::upload::UploadRequest::Data(chunk.to_vec()));
165        }
166
167        let streaming_req = StreamingRequestWithAuth {
168            auth_token: auth_token.try_into()?,
169            request: futures::stream::iter(chunks).map(|chunk| chunk.into()),
170        };
171
172        self.client.clone().upload(streaming_req).await?;
173        Ok(())
174    }
175
176    async fn download(
177        &self,
178        request: crate::types::download::DownloadRequest,
179        auth_token: &str,
180    ) -> Result<crate::types::download::DownloadedFile, ClientError> {
181        let mut request = tonic::Request::new(request.into());
182        request
183            .metadata_mut()
184            .append("authentication", auth_token.try_into()?);
185
186        let mut response = self.client.clone().download(request).await?.into_inner();
187
188        let meta = response
189            .message()
190            .await?
191            .ok_or(ClientError::MessageExpected)?;
192        let meta: crate::types::download::DownloadResponse = meta.try_into()?;
193        let meta = match meta {
194            crate::types::download::DownloadResponse::Meta(meta) => meta,
195            crate::types::download::DownloadResponse::Data(_) => {
196                return Err(ClientError::UnexpectedMessageType)
197            }
198        };
199
200        let mut data = Vec::<u8>::new();
201        while let Some(message) = response.message().await? {
202            let chunk: crate::types::download::DownloadResponse = message.try_into()?;
203            match chunk {
204                crate::types::download::DownloadResponse::Meta(_) => {
205                    return Err(ClientError::UnexpectedMessageType)
206                }
207                crate::types::download::DownloadResponse::Data(chunk) => data.extend(chunk),
208            }
209        }
210
211        Ok(crate::types::download::DownloadedFile { meta, data })
212    }
213}
214
215struct StreamingRequestWithAuth<T> {
216    auth_token: tonic::metadata::MetadataValue<tonic::metadata::Ascii>,
217    request: T,
218}
219
220impl<T: tonic::IntoStreamingRequest<Message = crate::proto::biurs_v1::UploadRequest>>
221    tonic::IntoStreamingRequest for StreamingRequestWithAuth<T>
222{
223    type Stream = T::Stream;
224    type Message = T::Message;
225
226    fn into_streaming_request(self) -> tonic::Request<Self::Stream> {
227        let mut request = self.request.into_streaming_request();
228        request
229            .metadata_mut()
230            .append("authentication", self.auth_token);
231        request
232    }
233}