Git repository hosting, collaboration, and discovery for the Fediverse.
Many things
parent: tbd commit: d782588
Showing 8 changed files with 539 insertions and 91 deletions
Cargo.toml
@@ -16,4 +16,7 @@ serde = { version = "1", features = ["derive"]} | ||
16 | 16 | serde_json = "1.0" |
17 | 17 | tracing-subscriber = "0.3" |
18 | 18 | rand = "*" |
19 | jsonwebtoken = { version = "*", features = ["use_pem"]} | |
19 | \ No newline at end of file | |
19 | jsonwebtoken = { version = "*", features = ["use_pem"]} | |
20 | chrono = { version = "0.4", features = [ "serde", "std" ] } | |
21 | reqwest = { version = "0.11" } | |
22 | anyhow = "*" | |
22 | \ No newline at end of file |
src/example_keys/giterated.key
@@ -0,0 +1,51 @@ | ||
1 | -----BEGIN RSA PRIVATE KEY----- | |
2 | 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 | |
51 | -----END RSA PRIVATE KEY----- | |
51 | \ No newline at end of file |
src/example_keys/giterated.key.pub
@@ -0,0 +1,13 @@ | ||
1 | -----BEGIN RSA PUBLIC KEY----- | |
2 | MIICCgKCAgEArGLMTdU15wfXOX3SwbQTZnAEvMfWK7JBH3t0ZHYcnQRJQt2/OAvH | |
3 | 9h8WwQAjXcoS9wEvFfEJA2OWdzHWJSSnDD8M5eI2F8+cZeQ5bBk6YGxyYkVopkV/ | |
4 | RxoitlFFXeSTksQO7VVG9qiw5lcsW+WRw9LJHSyviV8AXZY0GK3RHZwsQnEYIfmi | |
5 | waW1+gUEi2ztoBTQjJXH/MkXdYOXqINzVPta/cfw7lOJHOh1X4WSkKErfCR0GW4P | |
6 | hAdj3hQFHknta9Fm2SBmgEZt6P/RSprUuKuX7Ufr8mb5Vqv7HwCKapsFym7Dgvbe | |
7 | y3M8nCdpWc3w1B/X4vW0cePZg6XX3VAXmMgB6o9WW23IwAaWMWGe1v/oXOUj7y2+ | |
8 | BIzCTUbYdDruVZ9szpNvq9ddGqTGhpivKnpyAANe5e4ggppV4zZC4yKmVqDQO2MJ | |
9 | h+Qr0mIkbOFFbytvAzIlKBWL+zeMtloOjvIU5F6AoWCr3W0EoJy4oDR0ZVtovcqE | |
10 | TPtYuoFJ1Y5qlAIiC/J1TfKIRhB09WiuSzG5enEOH8thojulS46kIgJqKLMOvBi3 | |
11 | JUf5EHqEXGjIILcb9plAI6D3pHV4rUNIv3KY0ci2PqMfmHjE3v/ELlu4YKKqfhn/ | |
12 | fDMuYphZ41Ga0eaKRhV4ClwFWeI0hOVXhk6C01UZZct2ZbvayraTZCECAwEAAQ== | |
13 | -----END RSA PUBLIC KEY----- |
src/lib.rs
@@ -1,14 +1,25 @@ | ||
1 | use std::convert::Infallible; | |
2 | use std::str::FromStr; | |
1 | 3 | use std::{error::Error, net::SocketAddr}; |
2 | 4 | |
3 | 5 | use futures_util::{SinkExt, StreamExt}; |
6 | use giterated_daemon::messages::authentication::{RegisterAccountRequest, RegisterAccountResponse}; | |
7 | use giterated_daemon::messages::UnvalidatedUserAuthenticated; | |
8 | use giterated_daemon::model::repository::RepositoryVisibility; | |
9 | use giterated_daemon::model::user::User; | |
10 | use giterated_daemon::{version, validate_version}; | |
4 | 11 | use giterated_daemon::{ |
5 | 12 | handshake::{HandshakeFinalize, HandshakeMessage, InitiateHandshake}, |
6 | 13 | messages::{ |
14 | authentication::{ | |
15 | AuthenticationMessage, AuthenticationRequest, AuthenticationResponse, | |
16 | AuthenticationTokenRequest, TokenExtensionRequest, | |
17 | }, | |
7 | 18 | repository::{ |
8 | 19 | CreateRepositoryRequest, RepositoryInfoRequest, RepositoryMessage, |
9 | 20 | RepositoryMessageKind, RepositoryRequest, RepositoryResponse, |
10 | 21 | }, |
11 | MessageKind, | |
22 | InstanceAuthenticated, MessageKind, | |
12 | 23 | }, |
13 | 24 | model::{ |
14 | 25 | instance::Instance, |
@@ -24,38 +35,224 @@ type Socket = WebSocketStream<MaybeTlsStream<TcpStream>>; | ||
24 | 35 | #[macro_use] |
25 | 36 | extern crate tracing; |
26 | 37 | |
27 | pub struct GiteratedApi; | |
38 | pub struct GiteratedApiBuilder { | |
39 | our_instance: Instance, | |
40 | our_private_key: Option<String>, | |
41 | our_public_key: Option<String>, | |
42 | target_instance: Option<Instance>, | |
43 | } | |
44 | ||
45 | pub trait AsInstance { | |
46 | type Error: Error + Send + Sync + 'static; | |
47 | ||
48 | fn into_instance(self) -> Result<Instance, Self::Error>; | |
49 | } | |
50 | ||
51 | impl AsInstance for &str { | |
52 | type Error = <Instance as FromStr>::Err; | |
53 | ||
54 | fn into_instance(self) -> Result<Instance, Self::Error> { | |
55 | Instance::from_str(self) | |
56 | } | |
57 | } | |
58 | ||
59 | impl AsInstance for Instance { | |
60 | type Error = Infallible; | |
61 | ||
62 | fn into_instance(self) -> Result<Instance, Self::Error> { | |
63 | Ok(self) | |
64 | } | |
65 | } | |
66 | ||
67 | impl GiteratedApiBuilder { | |
68 | pub fn from_local(instance: impl AsInstance) -> Result<Self, anyhow::Error> { | |
69 | Ok(Self { | |
70 | our_instance: instance.into_instance()?, | |
71 | our_private_key: None, | |
72 | our_public_key: None, | |
73 | target_instance: None, | |
74 | }) | |
75 | } | |
76 | ||
77 | pub fn from_local_for_other( | |
78 | instance: impl AsInstance, | |
79 | other: impl AsInstance, | |
80 | ) -> Result<Self, anyhow::Error> { | |
81 | Ok(Self { | |
82 | our_instance: instance.into_instance()?, | |
83 | our_private_key: None, | |
84 | our_public_key: None, | |
85 | target_instance: Some(other.into_instance()?), | |
86 | }) | |
87 | } | |
88 | ||
89 | pub fn private_key(&mut self, key: impl ToString) -> &mut Self { | |
90 | self.our_private_key = Some(key.to_string()); | |
91 | ||
92 | self | |
93 | } | |
94 | ||
95 | pub fn public_key(&mut self, key: impl ToString) -> &mut Self { | |
96 | self.our_public_key = Some(key.to_string()); | |
97 | ||
98 | self | |
99 | } | |
100 | ||
101 | pub async fn build(&mut self) -> Result<GiteratedApi, anyhow::Error> { | |
102 | Ok(GiteratedApi::new( | |
103 | self.our_instance.clone(), | |
104 | self.our_private_key.clone().unwrap(), | |
105 | self.our_public_key.clone().unwrap(), | |
106 | self.target_instance.clone(), | |
107 | ) | |
108 | .await?) | |
109 | } | |
110 | } | |
111 | ||
112 | pub struct GiteratedApi { | |
113 | pub connection: Socket, | |
114 | pub our_instance: Instance, | |
115 | pub our_private_key: String, | |
116 | pub our_public_key: String, | |
117 | pub target_instance: Option<Instance>, | |
118 | pub target_public_key: Option<String>, | |
119 | } | |
28 | 120 | |
29 | 121 | impl GiteratedApi { |
122 | pub async fn new( | |
123 | local_instance: Instance, | |
124 | private_key: String, | |
125 | public_key: String, | |
126 | target_instance: Option<Instance>, | |
127 | ) -> Result<Self, anyhow::Error> { | |
128 | let connection = Self::connect_to( | |
129 | target_instance | |
130 | .clone() | |
131 | .unwrap_or_else(|| local_instance.clone()), | |
132 | ) | |
133 | .await?; | |
134 | ||
135 | let mut api = GiteratedApi { | |
136 | connection, | |
137 | our_instance: local_instance, | |
138 | our_private_key: private_key, | |
139 | our_public_key: public_key, | |
140 | target_instance, | |
141 | target_public_key: None, | |
142 | }; | |
143 | ||
144 | // Handle handshake | |
145 | api.handle_handshake().await?; | |
146 | ||
147 | Ok(api) | |
148 | } | |
149 | ||
150 | pub async fn public_key(&mut self) -> String { | |
151 | if let Some(public_key) = &self.target_public_key { | |
152 | public_key.clone() | |
153 | } else { | |
154 | let key = reqwest::get(format!( | |
155 | "https://{}/.giterated/pubkey.pem", | |
156 | self.target_instance | |
157 | .as_ref() | |
158 | .unwrap_or_else(|| &self.our_instance) | |
159 | .url | |
160 | )) | |
161 | .await | |
162 | .unwrap() | |
163 | .text() | |
164 | .await | |
165 | .unwrap(); | |
166 | ||
167 | self.target_public_key = Some(key.clone()); | |
168 | ||
169 | key | |
170 | } | |
171 | } | |
172 | ||
173 | /// Register on an [`Instance`]. | |
174 | /// | |
175 | /// # Authorization | |
176 | /// - Must be made by the same instance its being sent to | |
177 | pub async fn register( | |
178 | &mut self, | |
179 | username: String, | |
180 | email: Option<String>, | |
181 | password: String, | |
182 | ) -> Result<RegisterAccountResponse, Box<dyn Error>> { | |
183 | let message = InstanceAuthenticated::new( | |
184 | RegisterAccountRequest { | |
185 | username, | |
186 | email, | |
187 | password, | |
188 | }, | |
189 | self.our_instance.clone(), | |
190 | self.our_private_key.clone(), | |
191 | ) | |
192 | .unwrap(); | |
193 | ||
194 | self.send_message(&MessageKind::Authentication( | |
195 | AuthenticationMessage::Request(AuthenticationRequest::RegisterAccount(message)), | |
196 | )) | |
197 | .await?; | |
198 | ||
199 | while let Ok(payload) = self.next_payload().await { | |
200 | if let Ok(MessageKind::Authentication(AuthenticationMessage::Response( | |
201 | AuthenticationResponse::RegisterAccount(response), | |
202 | ))) = serde_json::from_slice(&payload) | |
203 | { | |
204 | return Ok(response); | |
205 | } | |
206 | } | |
207 | ||
208 | unreachable!() | |
209 | } | |
210 | ||
211 | /// Create repository on the target instance. | |
30 | 212 | pub async fn create_repository( |
31 | target: Instance, | |
32 | request: CreateRepositoryRequest, | |
213 | &mut self, | |
214 | user_token: String, | |
215 | name: String, | |
216 | description: Option<String>, | |
217 | visibility: RepositoryVisibility, | |
218 | default_branch: String, | |
219 | owner: User, | |
33 | 220 | ) -> Result<bool, Box<dyn Error>> { |
34 | let mut socket = Self::connect_to(&target.url).await?; | |
35 | let target = Repository { | |
36 | name: request.name.clone(), | |
37 | instance: target, | |
221 | let target_respository = Repository { | |
222 | owner: owner.clone(), | |
223 | name: name.clone(), | |
224 | instance: self | |
225 | .target_instance | |
226 | .as_ref() | |
227 | .unwrap_or(&self.our_instance) | |
228 | .clone(), | |
38 | 229 | }; |
39 | 230 | |
40 | Self::send_message( | |
41 | &MessageKind::Repository(RepositoryMessage { | |
42 | target, | |
43 | command: RepositoryMessageKind::Request(RepositoryRequest::CreateRepository( | |
44 | request, | |
45 | )), | |
46 | }), | |
47 | &mut socket, | |
48 | ) | |
231 | let request = CreateRepositoryRequest { | |
232 | name, | |
233 | description, | |
234 | visibility, | |
235 | default_branch, | |
236 | owner, | |
237 | }; | |
238 | ||
239 | let message = | |
240 | UnvalidatedUserAuthenticated::new(request, user_token, self.our_private_key.clone()) | |
241 | .unwrap(); | |
242 | ||
243 | self.send_message(&MessageKind::Repository(RepositoryMessage { | |
244 | target: target_respository, | |
245 | command: RepositoryMessageKind::Request(RepositoryRequest::CreateRepository(message)), | |
246 | })) | |
49 | 247 | .await?; |
50 | 248 | |
51 | while let Ok(payload) = Self::next_payload(&mut socket).await { | |
249 | while let Ok(payload) = self.next_payload().await { | |
52 | 250 | if let Ok(MessageKind::Repository(RepositoryMessage { |
53 | 251 | command: |
54 | 252 | RepositoryMessageKind::Response(RepositoryResponse::CreateRepository(_response)), |
55 | 253 | .. |
56 | 254 | })) = serde_json::from_slice(&payload) |
57 | 255 | { |
58 | socket.close(None).await?; | |
59 | 256 | return Ok(true); |
60 | 257 | } |
61 | 258 | } |
@@ -63,23 +260,32 @@ impl GiteratedApi { | ||
63 | 260 | unreachable!() |
64 | 261 | } |
65 | 262 | |
66 | pub async fn repository_info(repository: Repository) -> Result<RepositoryView, Box<dyn Error>> { | |
67 | let mut socket = Self::connect_to(&repository.instance.url).await?; | |
68 | ||
69 | Self::send_message( | |
70 | &MessageKind::Repository(RepositoryMessage { | |
71 | target: repository.clone(), | |
72 | command: RepositoryMessageKind::Request(RepositoryRequest::RepositoryInfo( | |
73 | RepositoryInfoRequest, | |
74 | )), | |
75 | }), | |
76 | &mut socket, | |
263 | pub async fn repository_info( | |
264 | &mut self, | |
265 | token: &str, | |
266 | repository: Repository, | |
267 | ) -> Result<RepositoryView, Box<dyn Error>> { | |
268 | let message = UnvalidatedUserAuthenticated::new( | |
269 | RepositoryInfoRequest { | |
270 | repository: repository.clone(), | |
271 | extra_metadata: true, | |
272 | rev: None, | |
273 | path: None, | |
274 | }, | |
275 | token.to_string(), | |
276 | self.our_private_key.clone(), | |
77 | 277 | ) |
278 | .unwrap(); | |
279 | ||
280 | self.send_message(&MessageKind::Repository(RepositoryMessage { | |
281 | target: repository.clone(), | |
282 | command: RepositoryMessageKind::Request(RepositoryRequest::RepositoryInfo(message)), | |
283 | })) | |
78 | 284 | .await?; |
79 | 285 | |
80 | 286 | loop { |
81 | 287 | // while let Ok(payload) = Self::next_payload(&mut socket).await { |
82 | let payload = match Self::next_payload(&mut socket).await { | |
288 | let payload = match self.next_payload().await { | |
83 | 289 | Ok(payload) => payload, |
84 | 290 | Err(err) => { |
85 | 291 | error!("Error while fetching next payload: {:?}", err); |
@@ -93,37 +299,116 @@ impl GiteratedApi { | ||
93 | 299 | .. |
94 | 300 | })) = serde_json::from_slice(&payload) |
95 | 301 | { |
96 | socket.close(None).await?; | |
97 | 302 | return Ok(response); |
98 | 303 | } |
99 | 304 | } |
305 | } | |
100 | 306 | |
101 | unreachable!() | |
307 | /// Requests an authentication token for the given login. | |
308 | /// | |
309 | /// # Authorization | |
310 | /// This request can only be sent to the same instance from which | |
311 | /// it is issued. | |
312 | pub async fn authentication_token( | |
313 | &mut self, | |
314 | secret_key: String, | |
315 | username: String, | |
316 | password: String, | |
317 | ) -> Result<String, Box<dyn Error>> { | |
318 | let request = InstanceAuthenticated::new( | |
319 | AuthenticationTokenRequest { | |
320 | secret_key, | |
321 | username, | |
322 | password, | |
323 | }, | |
324 | self.our_instance.clone(), | |
325 | include_str!("example_keys/giterated.key").to_string(), | |
326 | ) | |
327 | .unwrap(); | |
328 | ||
329 | self.send_message(&MessageKind::Authentication( | |
330 | AuthenticationMessage::Request(AuthenticationRequest::AuthenticationToken(request)), | |
331 | )) | |
332 | .await?; | |
333 | ||
334 | loop { | |
335 | // while let Ok(payload) = Self::next_payload(&mut socket).await { | |
336 | let payload = match self.next_payload().await { | |
337 | Ok(payload) => payload, | |
338 | Err(err) => { | |
339 | error!("Error while fetching next payload: {:?}", err); | |
340 | continue; | |
341 | } | |
342 | }; | |
343 | ||
344 | if let Ok(MessageKind::Authentication(AuthenticationMessage::Response( | |
345 | AuthenticationResponse::AuthenticationToken(response), | |
346 | ))) = serde_json::from_slice(&payload) | |
347 | { | |
348 | return Ok(response.token); | |
349 | } | |
350 | } | |
102 | 351 | } |
103 | 352 | |
104 | async fn connect_to(url: impl ToString) -> Result<Socket, Box<dyn Error>> { | |
105 | let url = url.to_string(); | |
106 | let (mut websocket, _response) = connect_async(&format!("ws://{}/.giterated/daemon", url)).await?; | |
107 | Self::handle_handshake(&mut websocket).await?; | |
353 | /// Requests a new token for the given login. | |
354 | /// | |
355 | /// # Authorization | |
356 | /// This request can only be sent to the same instance from which | |
357 | /// it is issued. | |
358 | pub async fn extend_token( | |
359 | &mut self, | |
360 | secret_key: String, | |
361 | token: String, | |
362 | ) -> Result<Option<String>, Box<dyn Error>> { | |
363 | let request = InstanceAuthenticated::new( | |
364 | TokenExtensionRequest { secret_key, token }, | |
365 | self.our_instance.clone(), | |
366 | self.our_private_key.clone(), | |
367 | ) | |
368 | .unwrap(); | |
369 | ||
370 | self.send_message(&MessageKind::Authentication( | |
371 | AuthenticationMessage::Request(AuthenticationRequest::TokenExtension(request)), | |
372 | )) | |
373 | .await?; | |
374 | ||
375 | while let Ok(payload) = self.next_payload().await { | |
376 | if let Ok(MessageKind::Authentication(AuthenticationMessage::Response( | |
377 | AuthenticationResponse::TokenExtension(response), | |
378 | ))) = serde_json::from_slice(&payload) | |
379 | { | |
380 | return Ok(response.new_token); | |
381 | } | |
382 | } | |
383 | ||
384 | todo!() | |
385 | } | |
386 | ||
387 | async fn connect_to(instance: Instance) -> Result<Socket, anyhow::Error> { | |
388 | let url = &instance.url; | |
389 | info!( | |
390 | "Connecting to {}", | |
391 | format!("wss://{}/.giterated/daemon/", url) | |
392 | ); | |
393 | let (websocket, _response) = | |
394 | connect_async(&format!("wss://{}/.giterated/daemon/", url)).await?; | |
395 | info!("Connection established with {}", url); | |
108 | 396 | |
109 | 397 | Ok(websocket) |
110 | 398 | } |
111 | 399 | |
112 | async fn handle_handshake(socket: &mut Socket) -> Result<(), Box<dyn Error>> { | |
400 | async fn handle_handshake(&mut self) -> Result<(), anyhow::Error> { | |
113 | 401 | // Send handshake initiation |
114 | 402 | |
115 | Self::send_message( | |
116 | &MessageKind::Handshake(HandshakeMessage::Initiate(InitiateHandshake { | |
117 | identity: Instance { | |
118 | url: String::from("foo.com"), | |
119 | }, | |
120 | version: String::from("0.1.0"), | |
121 | })), | |
122 | socket, | |
123 | ) | |
403 | self.send_message(&MessageKind::Handshake(HandshakeMessage::Initiate( | |
404 | InitiateHandshake { | |
405 | identity: self.our_instance.clone(), | |
406 | version: version(), | |
407 | }, | |
408 | ))) | |
124 | 409 | .await?; |
125 | 410 | |
126 | while let Some(message) = socket.next().await { | |
411 | while let Some(message) = self.connection.next().await { | |
127 | 412 | let message = match message { |
128 | 413 | Ok(message) => message, |
129 | 414 | Err(err) => { |
@@ -156,14 +441,25 @@ impl GiteratedApi { | ||
156 | 441 | if let MessageKind::Handshake(handshake) = message { |
157 | 442 | match handshake { |
158 | 443 | HandshakeMessage::Initiate(_) => unimplemented!(), |
159 | HandshakeMessage::Response(_) => { | |
444 | HandshakeMessage::Response(response) => { | |
445 | ||
446 | let message = if !validate_version(&response.version) { | |
447 | error!( | |
448 | "Version compatibility failure! Our Version: {}, Their Version: {}", | |
449 | version(), | |
450 | response.version | |
451 | ); | |
452 | ||
453 | HandshakeFinalize { success: false } | |
454 | } else { | |
455 | info!("Connected with a compatible version"); | |
456 | ||
457 | HandshakeFinalize { success: true } | |
458 | }; | |
160 | 459 | // Send HandshakeMessage::Finalize |
161 | Self::send_message( | |
162 | &MessageKind::Handshake(HandshakeMessage::Finalize( | |
163 | HandshakeFinalize { success: true }, | |
164 | )), | |
165 | socket, | |
166 | ) | |
460 | self.send_message(&MessageKind::Handshake(HandshakeMessage::Finalize( | |
461 | message | |
462 | ))) | |
167 | 463 | .await?; |
168 | 464 | } |
169 | 465 | HandshakeMessage::Finalize(finalize) => { |
@@ -180,18 +476,15 @@ impl GiteratedApi { | ||
180 | 476 | Ok(()) |
181 | 477 | } |
182 | 478 | |
183 | async fn send_message<T: Serialize>( | |
184 | message: &T, | |
185 | socket: &mut Socket, | |
186 | ) -> Result<(), Box<dyn Error>> { | |
187 | socket | |
479 | async fn send_message<T: Serialize>(&mut self, message: &T) -> Result<(), anyhow::Error> { | |
480 | self.connection | |
188 | 481 | .send(Message::Binary(serde_json::to_vec(&message).unwrap())) |
189 | 482 | .await?; |
190 | 483 | Ok(()) |
191 | 484 | } |
192 | 485 | |
193 | async fn next_payload(socket: &mut Socket) -> Result<Vec<u8>, Box<dyn Error>> { | |
194 | while let Some(message) = socket.next().await { | |
486 | async fn next_payload(&mut self) -> Result<Vec<u8>, Box<dyn Error>> { | |
487 | while let Some(message) = self.connection.next().await { | |
195 | 488 | let message = message?; |
196 | 489 | |
197 | 490 | match message { |
src/main.rs
@@ -1,9 +1,17 @@ | ||
1 | use std::collections::BTreeMap; | |
1 | use std::{collections::BTreeMap, str::FromStr, time::SystemTime}; | |
2 | 2 | |
3 | use giterated_api::GiteratedApi; | |
4 | use giterated_daemon::model::{instance::Instance, repository::Repository}; | |
5 | use jsonwebtoken::{encode, EncodingKey, Algorithm}; | |
6 | use serde::{Serialize, Deserialize}; | |
3 | use chrono::{DateTime, NaiveDateTime, Utc}; | |
4 | use giterated_api::{GiteratedApi, GiteratedApiBuilder}; | |
5 | use giterated_daemon::{ | |
6 | messages::repository::CreateRepositoryRequest, | |
7 | model::{ | |
8 | instance::Instance, | |
9 | repository::{Repository, RepositoryVisibility}, | |
10 | user::User, | |
11 | }, | |
12 | }; | |
13 | use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, TokenData, Validation}; | |
14 | use serde::{Deserialize, Serialize}; | |
7 | 15 | // use jwt::SignWithKey; |
8 | 16 | |
9 | 17 | #[macro_use] |
@@ -23,21 +31,113 @@ async fn main() { | ||
23 | 31 | // .await |
24 | 32 | // ); |
25 | 33 | |
26 | let encoding_key= EncodingKey::from_ec_pem(include_bytes!("example_keys/ec-private.pem")).unwrap(); | |
34 | // let encoding_key = | |
35 | // EncodingKey::from_rsa_pem(include_bytes!("example_keys/giterated.key")).unwrap(); | |
27 | 36 | |
28 | let claims = Claims { | |
29 | instance: String::from("giterated.dev"), | |
30 | username: String::from("ambee") | |
31 | }; | |
37 | // let claims = UserTokenMetadata { | |
38 | // user: User { | |
39 | // username: String::from("ambee"), | |
40 | // instance: Instance { | |
41 | // url: String::from("giterated.dev"), | |
42 | // }, | |
43 | // }, | |
44 | // generated_for: Instance { | |
45 | // url: String::from("giterated.dev"), | |
46 | // }, | |
47 | // exp: SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs(), | |
48 | // }; | |
49 | ||
50 | // let token = encode( | |
51 | // &jsonwebtoken::Header::new(Algorithm::RS256), | |
52 | // &claims, | |
53 | // &encoding_key, | |
54 | // ) | |
55 | // .unwrap(); | |
56 | ||
57 | let mut api = GiteratedApiBuilder::from_local("giterated.dev") | |
58 | .unwrap() | |
59 | .private_key(include_str!("example_keys/giterated.key")) | |
60 | .public_key(include_str!("example_keys/giterated.key")) | |
61 | .build() | |
62 | .await | |
63 | .unwrap(); | |
64 | ||
65 | info!("Lets try to make an account!"); | |
66 | ||
67 | let response = api | |
68 | .register( | |
69 | String::from("ambee"), | |
70 | None, | |
71 | String::from("lolthisisinthecommithistory"), | |
72 | ) | |
73 | .await; | |
32 | 74 | |
33 | let token = encode(&jsonwebtoken::Header::new(Algorithm::ES256), &claims, &encoding_key).unwrap(); | |
75 | info!("Registration response: {:?}", response); | |
76 | ||
77 | let token = api | |
78 | .authentication_token( | |
79 | String::from("foobar"), | |
80 | String::from("ambee"), | |
81 | String::from("password"), | |
82 | ) | |
83 | .await | |
84 | .unwrap(); | |
34 | 85 | |
35 | 86 | println!("Token: {}", token); |
36 | } | |
37 | 87 | |
88 | let public_key = api.public_key().await; | |
89 | ||
90 | println!("Server public key:\n{}", public_key); | |
91 | let verification_key = DecodingKey::from_rsa_pem(public_key.as_bytes()).unwrap(); | |
92 | let data: TokenData<UserTokenMetadata> = decode( | |
93 | &token, | |
94 | &verification_key, | |
95 | &Validation::new(Algorithm::RS256), | |
96 | ) | |
97 | .unwrap(); | |
98 | ||
99 | println!("The token was valid! Data:\n{:#?}", data.claims); | |
100 | ||
101 | info!("Lets extend that token!"); | |
102 | ||
103 | let new_token = api | |
104 | .extend_token(String::from("foobar"), token.clone()) | |
105 | .await | |
106 | .unwrap(); | |
107 | info!("New Token Returned:\n{:?}", new_token); | |
108 | ||
109 | info!("Try to create a repository? uwu"); | |
110 | ||
111 | let repository = api | |
112 | .create_repository( | |
113 | new_token.unwrap(), | |
114 | String::from("super-repository"), | |
115 | None, | |
116 | RepositoryVisibility::Public, | |
117 | String::from("master"), | |
118 | User::from_str("ambee:giterated.dev").unwrap(), | |
119 | ) | |
120 | .await | |
121 | .unwrap(); | |
122 | ||
123 | assert!(repository); | |
124 | ||
125 | info!("Lets view our repository!"); | |
126 | ||
127 | let view = api | |
128 | .repository_info( | |
129 | &token, | |
130 | Repository::from_str("ambee:giterated.dev/[email protected]").unwrap(), | |
131 | ) | |
132 | .await | |
133 | .unwrap(); | |
134 | ||
135 | info!("Repository Info:\n{:#?}", view); | |
136 | } | |
38 | 137 | |
39 | 138 | #[derive(Debug, Serialize, Deserialize)] |
40 | struct Claims { | |
41 | username: String, | |
42 | instance: String | |
43 | } | |
43 | \ No newline at end of file | |
139 | struct UserTokenMetadata { | |
140 | user: User, | |
141 | generated_for: Instance, | |
142 | exp: u64, | |
143 | } |