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}