Begin new protocol refactor
parent: tbd commit: 26651b1
Showing 30 changed files with 1223 insertions and 564 deletions
Cargo.lock
@@ -699,6 +699,7 @@ dependencies = [ | ||
699 | 699 | "rand", |
700 | 700 | "reqwest", |
701 | 701 | "rsa", |
702 | "secrecy", | |
702 | 703 | "semver", |
703 | 704 | "serde", |
704 | 705 | "serde_json", |
@@ -730,6 +731,7 @@ dependencies = [ | ||
730 | 731 | "rand", |
731 | 732 | "reqwest", |
732 | 733 | "rsa", |
734 | "secrecy", | |
733 | 735 | "semver", |
734 | 736 | "serde", |
735 | 737 | "serde_json", |
@@ -1713,6 +1715,16 @@ dependencies = [ | ||
1713 | 1715 | ] |
1714 | 1716 | |
1715 | 1717 | [[package]] |
1718 | name = "secrecy" | |
1719 | version = "0.8.0" | |
1720 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
1721 | checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" | |
1722 | dependencies = [ | |
1723 | "serde", | |
1724 | "zeroize", | |
1725 | ] | |
1726 | ||
1727 | [[package]] | |
1716 | 1728 | name = "security-framework" |
1717 | 1729 | version = "2.9.2" |
1718 | 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" |
giterated-daemon/Cargo.toml
@@ -38,5 +38,6 @@ git2 = "0.17" | ||
38 | 38 | thiserror = "1" |
39 | 39 | anyhow = "1" |
40 | 40 | sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls", "postgres", "macros", "migrate", "chrono" ] } |
41 | secrecy = "0.8.0" | |
41 | 42 | |
42 | 43 | #uuid = { version = "1.4", features = [ "v4", "serde" ] } |
giterated-daemon/src/authentication.rs
@@ -1,11 +1,8 @@ | ||
1 | 1 | use anyhow::Error; |
2 | use giterated_models::{ | |
3 | messages::authentication::{AuthenticationTokenResponse, TokenExtensionResponse}, | |
4 | model::{ | |
5 | authenticated::{UserAuthenticationToken, UserTokenMetadata}, | |
6 | instance::Instance, | |
7 | user::User, | |
8 | }, | |
2 | use giterated_models::model::{ | |
3 | authenticated::{UserAuthenticationToken, UserTokenMetadata}, | |
4 | instance::Instance, | |
5 | user::User, | |
9 | 6 | }; |
10 | 7 | use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, TokenData, Validation}; |
11 | 8 | use std::{sync::Arc, time::SystemTime}; |
@@ -21,9 +18,6 @@ pub struct AuthenticationTokenGranter { | ||
21 | 18 | |
22 | 19 | impl AuthenticationTokenGranter { |
23 | 20 | async fn private_key(&self) -> Vec<u8> { |
24 | let _secret_key = self.config["authentication"]["secret_key"] | |
25 | .as_str() | |
26 | .unwrap(); | |
27 | 21 | let mut file = File::open( |
28 | 22 | self.config["giterated"]["keys"]["private"] |
29 | 23 | .as_str() |
@@ -68,7 +62,7 @@ impl AuthenticationTokenGranter { | ||
68 | 62 | issued_for: impl ToOwned<Owned = Instance>, |
69 | 63 | username: String, |
70 | 64 | _password: String, |
71 | ) -> Result<AuthenticationTokenResponse, Error> { | |
65 | ) -> Result<UserAuthenticationToken, Error> { | |
72 | 66 | let private_key = { |
73 | 67 | let mut file = File::open( |
74 | 68 | self.config["giterated"]["keys"]["private"] |
@@ -104,9 +98,7 @@ impl AuthenticationTokenGranter { | ||
104 | 98 | ) |
105 | 99 | .unwrap(); |
106 | 100 | |
107 | Ok(AuthenticationTokenResponse { | |
108 | token: UserAuthenticationToken::from(token), | |
109 | }) | |
101 | Ok(UserAuthenticationToken::from(token)) | |
110 | 102 | } |
111 | 103 | |
112 | 104 | pub async fn extension_request( |
@@ -114,7 +106,7 @@ impl AuthenticationTokenGranter { | ||
114 | 106 | issued_for: &Instance, |
115 | 107 | key_cache: &Arc<Mutex<PublicKeyCache>>, |
116 | 108 | token: UserAuthenticationToken, |
117 | ) -> Result<TokenExtensionResponse, Error> { | |
109 | ) -> Result<Option<UserAuthenticationToken>, Error> { | |
118 | 110 | let mut key_cache = key_cache.lock().await; |
119 | 111 | let server_public_key = key_cache.get(issued_for).await?; |
120 | 112 | drop(key_cache); |
@@ -167,8 +159,6 @@ impl AuthenticationTokenGranter { | ||
167 | 159 | ) |
168 | 160 | .unwrap(); |
169 | 161 | |
170 | Ok(TokenExtensionResponse { | |
171 | new_token: Some(token), | |
172 | }) | |
162 | Ok(Some(UserAuthenticationToken::from(token))) | |
173 | 163 | } |
174 | 164 | } |
giterated-daemon/src/backend/git.rs
@@ -1,22 +1,23 @@ | ||
1 | 1 | use anyhow::Error; |
2 | 2 | use async_trait::async_trait; |
3 | 3 | use futures_util::StreamExt; |
4 | use git2::ObjectType; | |
4 | ||
5 | 5 | use giterated_models::{ |
6 | messages::repository::{ | |
7 | RepositoryCreateRequest, RepositoryCreateResponse, RepositoryFileInspectRequest, | |
8 | RepositoryFileInspectionResponse, RepositoryInfoRequest, RepositoryIssueLabelsRequest, | |
9 | RepositoryIssueLabelsResponse, RepositoryIssuesCountRequest, RepositoryIssuesCountResponse, | |
10 | RepositoryIssuesRequest, RepositoryIssuesResponse, | |
11 | }, | |
12 | 6 | model::{ |
13 | 7 | instance::Instance, |
14 | 8 | repository::{ |
15 | Commit, Repository, RepositoryObjectType, RepositorySummary, RepositoryTreeEntry, | |
16 | RepositoryView, RepositoryVisibility, | |
9 | Commit, IssueLabel, Repository, RepositoryIssue, RepositorySummary, | |
10 | RepositoryTreeEntry, RepositoryVisibility, | |
17 | 11 | }, |
18 | 12 | user::User, |
19 | 13 | }, |
14 | operation::{ | |
15 | instance::RepositoryCreateRequest, | |
16 | repository::{ | |
17 | RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, | |
18 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
19 | }, | |
20 | }, | |
20 | 21 | }; |
21 | 22 | use sqlx::{Either, PgPool}; |
22 | 23 | use std::path::{Path, PathBuf}; |
@@ -208,11 +209,21 @@ impl GitBackend { | ||
208 | 209 | |
209 | 210 | #[async_trait] |
210 | 211 | impl RepositoryBackend for GitBackend { |
212 | async fn exists(&mut self, repository: &Repository) -> Result<bool, Error> { | |
213 | if let Ok(_repository) = self | |
214 | .find_by_owner_user_name(&repository.owner, &repository.name) | |
215 | .await | |
216 | { | |
217 | return Ok(true); | |
218 | } else { | |
219 | return Ok(false); | |
220 | } | |
221 | } | |
211 | 222 | async fn create_repository( |
212 | 223 | &mut self, |
213 | 224 | _user: &User, |
214 | 225 | request: &RepositoryCreateRequest, |
215 | ) -> Result<RepositoryCreateResponse, GitBackendError> { | |
226 | ) -> Result<Repository, GitBackendError> { | |
216 | 227 | // Check if repository already exists in the database |
217 | 228 | if let Ok(repository) = self |
218 | 229 | .find_by_owner_user_name(&request.owner, &request.name) |
@@ -255,7 +266,11 @@ impl RepositoryBackend for GitBackend { | ||
255 | 266 | "Created new repository with the name {}/{}/{}", |
256 | 267 | request.owner.instance.url, request.owner.username, request.name |
257 | 268 | ); |
258 | Ok(RepositoryCreateResponse) | |
269 | Ok(Repository { | |
270 | owner: request.owner.clone(), | |
271 | name: request.name.clone(), | |
272 | instance: request.instance.as_ref().unwrap_or(&self.instance).clone(), | |
273 | }) | |
259 | 274 | } |
260 | 275 | Err(err) => { |
261 | 276 | let err = GitBackendError::FailedCreatingRepository(err); |
@@ -271,171 +286,171 @@ impl RepositoryBackend for GitBackend { | ||
271 | 286 | } |
272 | 287 | } |
273 | 288 | |
274 | async fn repository_info( | |
275 | &mut self, | |
276 | requester: Option<&User>, | |
277 | request: &RepositoryInfoRequest, | |
278 | ) -> Result<RepositoryView, Error> { | |
279 | let repository = match self | |
280 | .find_by_owner_user_name( | |
281 | // &request.owner.instance.url, | |
282 | &request.repository.owner, | |
283 | &request.repository.name, | |
284 | ) | |
285 | .await | |
286 | { | |
287 | Ok(repository) => repository, | |
288 | Err(err) => return Err(Box::new(err).into()), | |
289 | }; | |
290 | ||
291 | if let Some(requester) = requester { | |
292 | if !repository.can_user_view_repository(Some(&requester)) { | |
293 | return Err(Box::new(GitBackendError::RepositoryNotFound { | |
294 | owner_user: request.repository.owner.to_string(), | |
295 | name: request.repository.name.clone(), | |
296 | }) | |
297 | .into()); | |
298 | } | |
299 | } else if matches!(repository.visibility, RepositoryVisibility::Private) { | |
300 | info!("Unauthenticated"); | |
301 | // Unauthenticated users can never view private repositories | |
302 | ||
303 | return Err(Box::new(GitBackendError::RepositoryNotFound { | |
304 | owner_user: request.repository.owner.to_string(), | |
305 | name: request.repository.name.clone(), | |
306 | }) | |
307 | .into()); | |
308 | } | |
309 | ||
310 | let git = match repository.open_git2_repository(&self.repository_folder) { | |
311 | Ok(git) => git, | |
312 | Err(err) => return Err(Box::new(err).into()), | |
313 | }; | |
314 | ||
315 | let rev_name = match &request.rev { | |
316 | None => { | |
317 | if let Ok(head) = git.head() { | |
318 | head.name().unwrap().to_string() | |
319 | } else { | |
320 | // Nothing in database, render empty tree. | |
321 | return Ok(RepositoryView { | |
322 | name: repository.name, | |
323 | owner: request.repository.owner.clone(), | |
324 | description: repository.description, | |
325 | visibility: repository.visibility, | |
326 | default_branch: repository.default_branch, | |
327 | latest_commit: None, | |
328 | tree_rev: None, | |
329 | tree: vec![], | |
330 | }); | |
331 | } | |
332 | } | |
333 | Some(rev_name) => { | |
334 | // Find the reference, otherwise return GitBackendError | |
335 | match git | |
336 | .find_reference(format!("refs/heads/{}", rev_name).as_str()) | |
337 | .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) | |
338 | { | |
339 | Ok(reference) => reference.name().unwrap().to_string(), | |
340 | Err(err) => return Err(Box::new(err).into()), | |
341 | } | |
342 | } | |
343 | }; | |
344 | ||
345 | // Get the git object as a commit | |
346 | let rev = match git | |
347 | .revparse_single(rev_name.as_str()) | |
348 | .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) | |
349 | { | |
350 | Ok(rev) => rev, | |
351 | Err(err) => return Err(Box::new(err).into()), | |
352 | }; | |
353 | let commit = rev.as_commit().unwrap(); | |
354 | ||
355 | // this is stupid | |
356 | let mut current_path = rev_name.replace("refs/heads/", ""); | |
357 | ||
358 | // Get the commit tree | |
359 | let git_tree = if let Some(path) = &request.path { | |
360 | // Add it to our full path string | |
361 | current_path.push_str(format!("/{}", path).as_str()); | |
362 | // Get the specified path, return an error if it wasn't found. | |
363 | let entry = match commit | |
364 | .tree() | |
365 | .unwrap() | |
366 | .get_path(&PathBuf::from(path)) | |
367 | .map_err(|_| GitBackendError::PathNotFound(path.to_string())) | |
368 | { | |
369 | Ok(entry) => entry, | |
370 | Err(err) => return Err(Box::new(err).into()), | |
371 | }; | |
372 | // Turn the entry into a git tree | |
373 | entry.to_object(&git).unwrap().as_tree().unwrap().clone() | |
374 | } else { | |
375 | commit.tree().unwrap() | |
376 | }; | |
377 | ||
378 | // Iterate over the git tree and collect it into our own tree types | |
379 | let mut tree = git_tree | |
380 | .iter() | |
381 | .map(|entry| { | |
382 | let object_type = match entry.kind().unwrap() { | |
383 | ObjectType::Tree => RepositoryObjectType::Tree, | |
384 | ObjectType::Blob => RepositoryObjectType::Blob, | |
385 | _ => unreachable!(), | |
386 | }; | |
387 | let mut tree_entry = | |
388 | RepositoryTreeEntry::new(entry.name().unwrap(), object_type, entry.filemode()); | |
389 | ||
390 | if request.extra_metadata { | |
391 | // Get the file size if It's a blob | |
392 | let object = entry.to_object(&git).unwrap(); | |
393 | if let Some(blob) = object.as_blob() { | |
394 | tree_entry.size = Some(blob.size()); | |
395 | } | |
396 | ||
397 | // Could possibly be done better | |
398 | let path = if let Some(path) = current_path.split_once('/') { | |
399 | format!("{}/{}", path.1, entry.name().unwrap()) | |
400 | } else { | |
401 | entry.name().unwrap().to_string() | |
402 | }; | |
403 | ||
404 | // Get the last commit made to the entry | |
405 | if let Ok(last_commit) = | |
406 | GitBackend::get_last_commit_of_file(&path, &git, commit) | |
407 | { | |
408 | tree_entry.last_commit = Some(last_commit); | |
409 | } | |
410 | } | |
411 | ||
412 | tree_entry | |
413 | }) | |
414 | .collect::<Vec<RepositoryTreeEntry>>(); | |
415 | ||
416 | // Sort the tree alphabetically and with tree first | |
417 | tree.sort_unstable_by_key(|entry| entry.name.to_lowercase()); | |
418 | tree.sort_unstable_by_key(|entry| { | |
419 | std::cmp::Reverse(format!("{:?}", entry.object_type).to_lowercase()) | |
420 | }); | |
421 | ||
422 | Ok(RepositoryView { | |
423 | name: repository.name, | |
424 | owner: request.repository.owner.clone(), | |
425 | description: repository.description, | |
426 | visibility: repository.visibility, | |
427 | default_branch: repository.default_branch, | |
428 | latest_commit: Some(Commit::from(commit.clone())), | |
429 | tree_rev: Some(rev_name), | |
430 | tree, | |
431 | }) | |
432 | } | |
289 | // async fn repository_info( | |
290 | // &mut self, | |
291 | // requester: Option<&User>, | |
292 | // request: &RepositoryInfoRequest, | |
293 | // ) -> Result<RepositoryView, Error> { | |
294 | // let repository = match self | |
295 | // .find_by_owner_user_name( | |
296 | // // &request.owner.instance.url, | |
297 | // &request.repository.owner, | |
298 | // &request.repository.name, | |
299 | // ) | |
300 | // .await | |
301 | // { | |
302 | // Ok(repository) => repository, | |
303 | // Err(err) => return Err(Box::new(err).into()), | |
304 | // }; | |
305 | ||
306 | // if let Some(requester) = requester { | |
307 | // if !repository.can_user_view_repository(Some(&requester)) { | |
308 | // return Err(Box::new(GitBackendError::RepositoryNotFound { | |
309 | // owner_user: request.repository.owner.to_string(), | |
310 | // name: request.repository.name.clone(), | |
311 | // }) | |
312 | // .into()); | |
313 | // } | |
314 | // } else if matches!(repository.visibility, RepositoryVisibility::Private) { | |
315 | // info!("Unauthenticated"); | |
316 | // // Unauthenticated users can never view private repositories | |
317 | ||
318 | // return Err(Box::new(GitBackendError::RepositoryNotFound { | |
319 | // owner_user: request.repository.owner.to_string(), | |
320 | // name: request.repository.name.clone(), | |
321 | // }) | |
322 | // .into()); | |
323 | // } | |
324 | ||
325 | // let git = match repository.open_git2_repository(&self.repository_folder) { | |
326 | // Ok(git) => git, | |
327 | // Err(err) => return Err(Box::new(err).into()), | |
328 | // }; | |
329 | ||
330 | // let rev_name = match &request.rev { | |
331 | // None => { | |
332 | // if let Ok(head) = git.head() { | |
333 | // head.name().unwrap().to_string() | |
334 | // } else { | |
335 | // // Nothing in database, render empty tree. | |
336 | // return Ok(RepositoryView { | |
337 | // name: repository.name, | |
338 | // owner: request.repository.owner.clone(), | |
339 | // description: repository.description, | |
340 | // visibility: repository.visibility, | |
341 | // default_branch: repository.default_branch, | |
342 | // latest_commit: None, | |
343 | // tree_rev: None, | |
344 | // tree: vec![], | |
345 | // }); | |
346 | // } | |
347 | // } | |
348 | // Some(rev_name) => { | |
349 | // // Find the reference, otherwise return GitBackendError | |
350 | // match git | |
351 | // .find_reference(format!("refs/heads/{}", rev_name).as_str()) | |
352 | // .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) | |
353 | // { | |
354 | // Ok(reference) => reference.name().unwrap().to_string(), | |
355 | // Err(err) => return Err(Box::new(err).into()), | |
356 | // } | |
357 | // } | |
358 | // }; | |
359 | ||
360 | // // Get the git object as a commit | |
361 | // let rev = match git | |
362 | // .revparse_single(rev_name.as_str()) | |
363 | // .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) | |
364 | // { | |
365 | // Ok(rev) => rev, | |
366 | // Err(err) => return Err(Box::new(err).into()), | |
367 | // }; | |
368 | // let commit = rev.as_commit().unwrap(); | |
369 | ||
370 | // // this is stupid | |
371 | // let mut current_path = rev_name.replace("refs/heads/", ""); | |
372 | ||
373 | // // Get the commit tree | |
374 | // let git_tree = if let Some(path) = &request.path { | |
375 | // // Add it to our full path string | |
376 | // current_path.push_str(format!("/{}", path).as_str()); | |
377 | // // Get the specified path, return an error if it wasn't found. | |
378 | // let entry = match commit | |
379 | // .tree() | |
380 | // .unwrap() | |
381 | // .get_path(&PathBuf::from(path)) | |
382 | // .map_err(|_| GitBackendError::PathNotFound(path.to_string())) | |
383 | // { | |
384 | // Ok(entry) => entry, | |
385 | // Err(err) => return Err(Box::new(err).into()), | |
386 | // }; | |
387 | // // Turn the entry into a git tree | |
388 | // entry.to_object(&git).unwrap().as_tree().unwrap().clone() | |
389 | // } else { | |
390 | // commit.tree().unwrap() | |
391 | // }; | |
392 | ||
393 | // // Iterate over the git tree and collect it into our own tree types | |
394 | // let mut tree = git_tree | |
395 | // .iter() | |
396 | // .map(|entry| { | |
397 | // let object_type = match entry.kind().unwrap() { | |
398 | // ObjectType::Tree => RepositoryObjectType::Tree, | |
399 | // ObjectType::Blob => RepositoryObjectType::Blob, | |
400 | // _ => unreachable!(), | |
401 | // }; | |
402 | // let mut tree_entry = | |
403 | // RepositoryTreeEntry::new(entry.name().unwrap(), object_type, entry.filemode()); | |
404 | ||
405 | // if request.extra_metadata { | |
406 | // // Get the file size if It's a blob | |
407 | // let object = entry.to_object(&git).unwrap(); | |
408 | // if let Some(blob) = object.as_blob() { | |
409 | // tree_entry.size = Some(blob.size()); | |
410 | // } | |
411 | ||
412 | // // Could possibly be done better | |
413 | // let path = if let Some(path) = current_path.split_once('/') { | |
414 | // format!("{}/{}", path.1, entry.name().unwrap()) | |
415 | // } else { | |
416 | // entry.name().unwrap().to_string() | |
417 | // }; | |
418 | ||
419 | // // Get the last commit made to the entry | |
420 | // if let Ok(last_commit) = | |
421 | // GitBackend::get_last_commit_of_file(&path, &git, commit) | |
422 | // { | |
423 | // tree_entry.last_commit = Some(last_commit); | |
424 | // } | |
425 | // } | |
426 | ||
427 | // tree_entry | |
428 | // }) | |
429 | // .collect::<Vec<RepositoryTreeEntry>>(); | |
430 | ||
431 | // // Sort the tree alphabetically and with tree first | |
432 | // tree.sort_unstable_by_key(|entry| entry.name.to_lowercase()); | |
433 | // tree.sort_unstable_by_key(|entry| { | |
434 | // std::cmp::Reverse(format!("{:?}", entry.object_type).to_lowercase()) | |
435 | // }); | |
436 | ||
437 | // Ok(RepositoryView { | |
438 | // name: repository.name, | |
439 | // owner: request.repository.owner.clone(), | |
440 | // description: repository.description, | |
441 | // visibility: repository.visibility, | |
442 | // default_branch: repository.default_branch, | |
443 | // latest_commit: Some(Commit::from(commit.clone())), | |
444 | // tree_rev: Some(rev_name), | |
445 | // tree, | |
446 | // }) | |
447 | // } | |
433 | 448 | |
434 | 449 | async fn repository_file_inspect( |
435 | 450 | &mut self, |
436 | 451 | _requester: Option<&User>, |
437 | 452 | _request: &RepositoryFileInspectRequest, |
438 | ) -> Result<RepositoryFileInspectionResponse, Error> { | |
453 | ) -> Result<Vec<RepositoryTreeEntry>, Error> { | |
439 | 454 | todo!() |
440 | 455 | } |
441 | 456 | |
@@ -484,7 +499,7 @@ impl IssuesBackend for GitBackend { | ||
484 | 499 | &mut self, |
485 | 500 | _requester: Option<&User>, |
486 | 501 | _request: &RepositoryIssuesCountRequest, |
487 | ) -> Result<RepositoryIssuesCountResponse, Error> { | |
502 | ) -> Result<u64, Error> { | |
488 | 503 | todo!() |
489 | 504 | } |
490 | 505 | |
@@ -492,7 +507,7 @@ impl IssuesBackend for GitBackend { | ||
492 | 507 | &mut self, |
493 | 508 | _requester: Option<&User>, |
494 | 509 | _request: &RepositoryIssueLabelsRequest, |
495 | ) -> Result<RepositoryIssueLabelsResponse, Error> { | |
510 | ) -> Result<Vec<IssueLabel>, Error> { | |
496 | 511 | todo!() |
497 | 512 | } |
498 | 513 | |
@@ -500,7 +515,7 @@ impl IssuesBackend for GitBackend { | ||
500 | 515 | &mut self, |
501 | 516 | _requester: Option<&User>, |
502 | 517 | _request: &RepositoryIssuesRequest, |
503 | ) -> Result<RepositoryIssuesResponse, Error> { | |
518 | ) -> Result<Vec<RepositoryIssue>, Error> { | |
504 | 519 | todo!() |
505 | 520 | } |
506 | 521 | } |
giterated-daemon/src/backend/mod.rs
@@ -10,27 +10,21 @@ use serde_json::Value; | ||
10 | 10 | |
11 | 11 | use crate::backend::git::GitBackendError; |
12 | 12 | use giterated_models::{ |
13 | messages::{ | |
14 | authentication::{ | |
15 | AuthenticationTokenRequest, AuthenticationTokenResponse, RegisterAccountRequest, | |
16 | RegisterAccountResponse, | |
17 | }, | |
18 | repository::{ | |
19 | RepositoryCreateRequest, RepositoryCreateResponse, RepositoryFileInspectRequest, | |
20 | RepositoryFileInspectionResponse, RepositoryInfoRequest, RepositoryIssueLabelsRequest, | |
21 | RepositoryIssueLabelsResponse, RepositoryIssuesCountRequest, | |
22 | RepositoryIssuesCountResponse, RepositoryIssuesRequest, RepositoryIssuesResponse, | |
23 | }, | |
24 | user::{ | |
25 | UserBioRequest, UserBioResponse, UserDisplayImageRequest, UserDisplayImageResponse, | |
26 | UserDisplayNameRequest, UserDisplayNameResponse, | |
27 | }, | |
28 | }, | |
29 | 13 | model::{ |
14 | authenticated::UserAuthenticationToken, | |
30 | 15 | instance::Instance, |
31 | repository::{Repository, RepositorySummary, RepositoryView}, | |
16 | repository::{ | |
17 | IssueLabel, Repository, RepositoryIssue, RepositorySummary, RepositoryTreeEntry, | |
18 | }, | |
32 | 19 | user::User, |
33 | 20 | }, |
21 | operation::{ | |
22 | instance::{AuthenticationTokenRequest, RegisterAccountRequest, RepositoryCreateRequest}, | |
23 | repository::{ | |
24 | RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, | |
25 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
26 | }, | |
27 | }, | |
34 | 28 | }; |
35 | 29 | |
36 | 30 | #[async_trait] |
@@ -39,22 +33,18 @@ pub trait RepositoryBackend: IssuesBackend { | ||
39 | 33 | &mut self, |
40 | 34 | user: &User, |
41 | 35 | request: &RepositoryCreateRequest, |
42 | ) -> Result<RepositoryCreateResponse, GitBackendError>; | |
43 | async fn repository_info( | |
44 | &mut self, | |
45 | requester: Option<&User>, | |
46 | request: &RepositoryInfoRequest, | |
47 | ) -> Result<RepositoryView, Error>; | |
36 | ) -> Result<Repository, GitBackendError>; | |
48 | 37 | async fn repository_file_inspect( |
49 | 38 | &mut self, |
50 | 39 | requester: Option<&User>, |
51 | 40 | request: &RepositoryFileInspectRequest, |
52 | ) -> Result<RepositoryFileInspectionResponse, Error>; | |
41 | ) -> Result<Vec<RepositoryTreeEntry>, Error>; | |
53 | 42 | async fn repositories_for_user( |
54 | 43 | &mut self, |
55 | 44 | requester: Option<&User>, |
56 | 45 | user: &User, |
57 | 46 | ) -> Result<Vec<RepositorySummary>, Error>; |
47 | async fn exists(&mut self, repository: &Repository) -> Result<bool, Error>; | |
58 | 48 | } |
59 | 49 | |
60 | 50 | pub trait IssuesBackend { |
@@ -62,17 +52,17 @@ pub trait IssuesBackend { | ||
62 | 52 | &mut self, |
63 | 53 | requester: Option<&User>, |
64 | 54 | request: &RepositoryIssuesCountRequest, |
65 | ) -> Result<RepositoryIssuesCountResponse, Error>; | |
55 | ) -> Result<u64, Error>; | |
66 | 56 | fn issue_labels( |
67 | 57 | &mut self, |
68 | 58 | requester: Option<&User>, |
69 | 59 | request: &RepositoryIssueLabelsRequest, |
70 | ) -> Result<RepositoryIssueLabelsResponse, Error>; | |
60 | ) -> Result<Vec<IssueLabel>, Error>; | |
71 | 61 | fn issues( |
72 | 62 | &mut self, |
73 | 63 | requester: Option<&User>, |
74 | 64 | request: &RepositoryIssuesRequest, |
75 | ) -> Result<RepositoryIssuesResponse, Error>; | |
65 | ) -> Result<Vec<RepositoryIssue>, Error>; | |
76 | 66 | } |
77 | 67 | |
78 | 68 | #[async_trait::async_trait] |
@@ -80,28 +70,17 @@ pub trait AuthBackend { | ||
80 | 70 | async fn register( |
81 | 71 | &mut self, |
82 | 72 | request: RegisterAccountRequest, |
83 | ) -> Result<RegisterAccountResponse, Error>; | |
73 | ) -> Result<UserAuthenticationToken, Error>; | |
84 | 74 | |
85 | 75 | async fn login( |
86 | 76 | &mut self, |
87 | 77 | source: &Instance, |
88 | 78 | request: AuthenticationTokenRequest, |
89 | ) -> Result<AuthenticationTokenResponse, Error>; | |
79 | ) -> Result<UserAuthenticationToken, Error>; | |
90 | 80 | } |
91 | 81 | |
92 | 82 | #[async_trait::async_trait] |
93 | 83 | pub trait UserBackend: AuthBackend { |
94 | async fn display_name( | |
95 | &mut self, | |
96 | request: UserDisplayNameRequest, | |
97 | ) -> Result<UserDisplayNameResponse, Error>; | |
98 | ||
99 | async fn display_image( | |
100 | &mut self, | |
101 | request: UserDisplayImageRequest, | |
102 | ) -> Result<UserDisplayImageResponse, Error>; | |
103 | ||
104 | async fn bio(&mut self, request: UserBioRequest) -> Result<UserBioResponse, Error>; | |
105 | 84 | async fn exists(&mut self, user: &User) -> Result<bool, Error>; |
106 | 85 | } |
107 | 86 |
giterated-daemon/src/backend/user.rs
@@ -6,22 +6,8 @@ use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit}; | ||
6 | 6 | use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; |
7 | 7 | use base64::{engine::general_purpose::STANDARD, Engine as _}; |
8 | 8 | use giterated_models::{ |
9 | messages::{ | |
10 | authentication::{ | |
11 | AuthenticationTokenRequest, AuthenticationTokenResponse, RegisterAccountRequest, | |
12 | RegisterAccountResponse, | |
13 | }, | |
14 | user::{ | |
15 | UserBioRequest, UserBioResponse, UserDisplayImageRequest, UserDisplayImageResponse, | |
16 | UserDisplayNameRequest, UserDisplayNameResponse, | |
17 | }, | |
18 | }, | |
19 | model::{ | |
20 | authenticated::UserAuthenticationToken, | |
21 | instance::Instance, | |
22 | settings::{Setting, UserBio, UserDisplayImage, UserDisplayName}, | |
23 | user::User, | |
24 | }, | |
9 | model::{authenticated::UserAuthenticationToken, instance::Instance, user::User}, | |
10 | operation::instance::{AuthenticationTokenRequest, RegisterAccountRequest}, | |
25 | 11 | }; |
26 | 12 | use rsa::{ |
27 | 13 | pkcs8::{EncodePrivateKey, EncodePublicKey}, |
@@ -29,6 +15,7 @@ use rsa::{ | ||
29 | 15 | RsaPrivateKey, RsaPublicKey, |
30 | 16 | }; |
31 | 17 | |
18 | use secrecy::ExposeSecret; | |
32 | 19 | use sqlx::PgPool; |
33 | 20 | use tokio::sync::Mutex; |
34 | 21 | |
@@ -61,71 +48,6 @@ impl UserAuth { | ||
61 | 48 | |
62 | 49 | #[async_trait::async_trait] |
63 | 50 | impl UserBackend for UserAuth { |
64 | async fn display_name( | |
65 | &mut self, | |
66 | request: UserDisplayNameRequest, | |
67 | ) -> Result<UserDisplayNameResponse, Error> { | |
68 | let mut settings_backend = self.settings_provider.lock().await; | |
69 | let settings = settings_backend.user_get(&request.user).await?; | |
70 | drop(settings_backend); | |
71 | ||
72 | let name = settings | |
73 | .iter() | |
74 | .find(|setting| &setting.0 == UserDisplayName::name()); | |
75 | ||
76 | if let Some((_, name)) = name { | |
77 | let name: UserDisplayName = serde_json::from_value(name.clone()).unwrap(); | |
78 | ||
79 | Ok(UserDisplayNameResponse { | |
80 | display_name: Some(name.0), | |
81 | }) | |
82 | } else { | |
83 | Ok(UserDisplayNameResponse { display_name: None }) | |
84 | } | |
85 | } | |
86 | ||
87 | async fn display_image( | |
88 | &mut self, | |
89 | request: UserDisplayImageRequest, | |
90 | ) -> Result<UserDisplayImageResponse, anyhow::Error> { | |
91 | let mut settings_backend: tokio::sync::MutexGuard<'_, dyn SettingsBackend> = | |
92 | self.settings_provider.lock().await; | |
93 | let settings = settings_backend.user_get(&request.user).await?; | |
94 | drop(settings_backend); | |
95 | ||
96 | let image = settings | |
97 | .iter() | |
98 | .find(|setting| &setting.0 == UserDisplayImage::name()); | |
99 | ||
100 | if let Some((_, image)) = image { | |
101 | let image: UserDisplayImage = serde_json::from_value(image.clone()).unwrap(); | |
102 | ||
103 | Ok(UserDisplayImageResponse { | |
104 | image_url: Some(image.0), | |
105 | }) | |
106 | } else { | |
107 | Ok(UserDisplayImageResponse { image_url: None }) | |
108 | } | |
109 | } | |
110 | ||
111 | async fn bio(&mut self, request: UserBioRequest) -> Result<UserBioResponse, Error> { | |
112 | let mut settings_backend = self.settings_provider.lock().await; | |
113 | let settings = settings_backend.user_get(&request.user).await?; | |
114 | drop(settings_backend); | |
115 | ||
116 | let bio = settings | |
117 | .iter() | |
118 | .find(|setting| &setting.0 == UserBio::name()); | |
119 | ||
120 | if let Some((_, bio)) = bio { | |
121 | let bio: UserBio = serde_json::from_value(bio.clone()).unwrap(); | |
122 | ||
123 | Ok(UserBioResponse { bio: Some(bio.0) }) | |
124 | } else { | |
125 | Ok(UserBioResponse { bio: None }) | |
126 | } | |
127 | } | |
128 | ||
129 | 51 | async fn exists(&mut self, user: &User) -> Result<bool, Error> { |
130 | 52 | Ok(sqlx::query_as!( |
131 | 53 | UserRow, |
@@ -134,7 +56,7 @@ impl UserBackend for UserAuth { | ||
134 | 56 | ) |
135 | 57 | .fetch_one(&self.pg_pool.clone()) |
136 | 58 | .await |
137 | .is_err()) | |
59 | .is_ok()) | |
138 | 60 | } |
139 | 61 | } |
140 | 62 | |
@@ -143,7 +65,7 @@ impl AuthBackend for UserAuth { | ||
143 | 65 | async fn register( |
144 | 66 | &mut self, |
145 | 67 | request: RegisterAccountRequest, |
146 | ) -> Result<RegisterAccountResponse, Error> { | |
68 | ) -> Result<UserAuthenticationToken, Error> { | |
147 | 69 | const BITS: usize = 2048; |
148 | 70 | |
149 | 71 | let private_key = RsaPrivateKey::new(&mut OsRng, BITS).unwrap(); |
@@ -153,13 +75,13 @@ impl AuthBackend for UserAuth { | ||
153 | 75 | let mut target: [u8; 32] = [0; 32]; |
154 | 76 | |
155 | 77 | let mut index = 0; |
156 | let mut iterator = request.password.as_bytes().iter(); | |
78 | let mut iterator = request.password.expose_secret().0.as_bytes().iter(); | |
157 | 79 | while index < 32 { |
158 | 80 | if let Some(next) = iterator.next() { |
159 | 81 | target[index] = *next; |
160 | 82 | index += 1; |
161 | 83 | } else { |
162 | iterator = request.password.as_bytes().iter(); | |
84 | iterator = request.password.expose_secret().0.as_bytes().iter(); | |
163 | 85 | } |
164 | 86 | } |
165 | 87 | |
@@ -180,7 +102,7 @@ impl AuthBackend for UserAuth { | ||
180 | 102 | let argon2 = Argon2::default(); |
181 | 103 | |
182 | 104 | let password_hash = argon2 |
183 | .hash_password(request.password.as_bytes(), &salt) | |
105 | .hash_password(request.password.expose_secret().0.as_bytes(), &salt) | |
184 | 106 | .unwrap() |
185 | 107 | .to_string(); |
186 | 108 | |
@@ -217,14 +139,15 @@ impl AuthBackend for UserAuth { | ||
217 | 139 | ) |
218 | 140 | .await; |
219 | 141 | |
220 | Ok(RegisterAccountResponse { token }) | |
142 | Ok(UserAuthenticationToken::from(token)) | |
221 | 143 | } |
222 | 144 | |
223 | 145 | async fn login( |
224 | 146 | &mut self, |
225 | 147 | source: &Instance, |
226 | 148 | request: AuthenticationTokenRequest, |
227 | ) -> Result<AuthenticationTokenResponse, Error> { | |
149 | ) -> Result<UserAuthenticationToken, Error> { | |
150 | info!("fetching!"); | |
228 | 151 | let user = sqlx::query_as!( |
229 | 152 | UserRow, |
230 | 153 | r#"SELECT * FROM users WHERE username = $1"#, |
@@ -235,11 +158,11 @@ impl AuthBackend for UserAuth { | ||
235 | 158 | |
236 | 159 | let hash = PasswordHash::new(&user.password).unwrap(); |
237 | 160 | |
238 | if !matches!( | |
239 | Argon2::default().verify_password(request.password.as_bytes(), &hash), | |
240 | Ok(()) | |
241 | ) { | |
242 | // Invalid password! | |
161 | if Argon2::default() | |
162 | .verify_password(request.password.expose_secret().0.as_bytes(), &hash) | |
163 | .is_err() | |
164 | { | |
165 | info!("invalid password"); | |
243 | 166 | return Err(Error::from(AuthenticationError::InvalidPassword)); |
244 | 167 | } |
245 | 168 | |
@@ -254,9 +177,7 @@ impl AuthBackend for UserAuth { | ||
254 | 177 | ) |
255 | 178 | .await; |
256 | 179 | |
257 | Ok(AuthenticationTokenResponse { | |
258 | token: UserAuthenticationToken::from(token), | |
259 | }) | |
180 | Ok(UserAuthenticationToken::from(token)) | |
260 | 181 | } |
261 | 182 | } |
262 | 183 |
giterated-daemon/src/connection.rs
@@ -1,25 +1,20 @@ | ||
1 | pub mod authentication; | |
2 | pub mod forwarded; | |
3 | pub mod handshake; | |
4 | pub mod repository; | |
5 | pub mod user; | |
1 | // pub mod authentication; | |
2 | // pub mod forwarded; | |
3 | // pub mod handshake; | |
4 | // pub mod repository; | |
5 | // pub mod user; | |
6 | 6 | pub mod wrapper; |
7 | 7 | |
8 | 8 | use std::{any::type_name, collections::HashMap}; |
9 | 9 | |
10 | 10 | use anyhow::Error; |
11 | use giterated_models::{ | |
12 | messages::ErrorMessage, | |
13 | model::instance::{Instance, InstanceMeta}, | |
14 | }; | |
11 | use giterated_models::model::instance::{Instance, InstanceMeta}; | |
15 | 12 | use serde::{de::DeserializeOwned, Serialize}; |
16 | 13 | use tokio::{net::TcpStream, task::JoinHandle}; |
17 | 14 | use tokio_tungstenite::WebSocketStream; |
18 | 15 | |
19 | 16 | #[derive(Debug, thiserror::Error)] |
20 | 17 | pub enum ConnectionError { |
21 | #[error("connection error message {0}")] | |
22 | ErrorMessage(#[from] ErrorMessage), | |
23 | 18 | #[error("connection should close")] |
24 | 19 | Shutdown, |
25 | 20 | #[error("internal error {0}")] |
giterated-daemon/src/connection/wrapper.rs
@@ -1,17 +1,11 @@ | ||
1 | 1 | use std::{ |
2 | 2 | net::SocketAddr, |
3 | sync::{ | |
4 | atomic::{AtomicBool, Ordering}, | |
5 | Arc, | |
6 | }, | |
3 | sync::{atomic::AtomicBool, Arc}, | |
7 | 4 | }; |
8 | 5 | |
9 | 6 | use anyhow::Error; |
10 | 7 | use futures_util::{SinkExt, StreamExt}; |
11 | use giterated_models::{ | |
12 | messages::error::ConnectionError, | |
13 | model::{authenticated::AuthenticatedPayload, instance::Instance}, | |
14 | }; | |
8 | use giterated_models::model::instance::Instance; | |
15 | 9 | |
16 | 10 | use serde::Serialize; |
17 | 11 | |
@@ -22,16 +16,11 @@ use toml::Table; | ||
22 | 16 | use crate::{ |
23 | 17 | authentication::AuthenticationTokenGranter, |
24 | 18 | backend::{RepositoryBackend, SettingsBackend, UserBackend}, |
25 | connection::forwarded::wrap_forwarded, | |
26 | 19 | federation::connections::InstanceConnections, |
27 | 20 | keys::PublicKeyCache, |
28 | message::NetworkMessage, | |
29 | 21 | }; |
30 | 22 | |
31 | use super::{ | |
32 | authentication::authentication_handle, handshake::handshake_handle, | |
33 | repository::repository_handle, user::user_handle, Connections, | |
34 | }; | |
23 | use super::Connections; | |
35 | 24 | |
36 | 25 | pub async fn connection_wrapper( |
37 | 26 | socket: WebSocketStream<TcpStream>, |
@@ -45,7 +34,7 @@ pub async fn connection_wrapper( | ||
45 | 34 | instance_connections: Arc<Mutex<InstanceConnections>>, |
46 | 35 | config: Table, |
47 | 36 | ) { |
48 | let connection_state = ConnectionState { | |
37 | let _connection_state = ConnectionState { | |
49 | 38 | socket: Arc::new(Mutex::new(socket)), |
50 | 39 | connections, |
51 | 40 | repository_backend, |
@@ -60,118 +49,118 @@ pub async fn connection_wrapper( | ||
60 | 49 | config, |
61 | 50 | }; |
62 | 51 | |
63 | let mut handshaked = false; | |
64 | ||
65 | loop { | |
66 | let mut socket = connection_state.socket.lock().await; | |
67 | let message = socket.next().await; | |
68 | drop(socket); | |
69 | ||
70 | match message { | |
71 | Some(Ok(message)) => { | |
72 | let payload = match message { | |
73 | Message::Binary(payload) => payload, | |
74 | Message::Ping(_) => { | |
75 | let mut socket = connection_state.socket.lock().await; | |
76 | let _ = socket.send(Message::Pong(vec![])).await; | |
77 | drop(socket); | |
78 | continue; | |
79 | } | |
80 | Message::Close(_) => return, | |
81 | _ => continue, | |
82 | }; | |
83 | info!("one payload"); | |
84 | ||
85 | let message = NetworkMessage(payload.clone()); | |
86 | ||
87 | if !handshaked { | |
88 | info!("im foo baring"); | |
89 | if handshake_handle(&message, &connection_state).await.is_ok() { | |
90 | if connection_state.handshaked.load(Ordering::SeqCst) { | |
91 | handshaked = true; | |
92 | } | |
93 | } | |
94 | } else { | |
95 | let raw = serde_json::from_slice::<AuthenticatedPayload>(&payload).unwrap(); | |
96 | ||
97 | if let Some(target_instance) = &raw.target_instance { | |
98 | if connection_state.instance != *target_instance { | |
99 | // Forward request | |
100 | info!("Forwarding message to {}", target_instance.url); | |
101 | let mut instance_connections = instance_connections.lock().await; | |
102 | let pool = instance_connections.get_or_open(&target_instance).unwrap(); | |
103 | let pool_clone = pool.clone(); | |
104 | drop(pool); | |
105 | ||
106 | let result = wrap_forwarded(&pool_clone, raw).await; | |
107 | ||
108 | let mut socket = connection_state.socket.lock().await; | |
109 | let _ = socket.send(result).await; | |
110 | ||
111 | continue; | |
112 | } | |
113 | } | |
114 | ||
115 | let message_type = &raw.message_type; | |
116 | ||
117 | info!("Handling message with type: {}", message_type); | |
118 | ||
119 | match authentication_handle(message_type, &message, &connection_state).await { | |
120 | Err(e) => { | |
121 | let _ = connection_state | |
122 | .send_raw(ConnectionError(e.to_string())) | |
123 | .await; | |
124 | } | |
125 | Ok(true) => continue, | |
126 | Ok(false) => {} | |
127 | } | |
128 | ||
129 | match repository_handle(message_type, &message, &connection_state).await { | |
130 | Err(e) => { | |
131 | let _ = connection_state | |
132 | .send_raw(ConnectionError(e.to_string())) | |
133 | .await; | |
134 | } | |
135 | Ok(true) => continue, | |
136 | Ok(false) => {} | |
137 | } | |
138 | ||
139 | match user_handle(message_type, &message, &connection_state).await { | |
140 | Err(e) => { | |
141 | let _ = connection_state | |
142 | .send_raw(ConnectionError(e.to_string())) | |
143 | .await; | |
144 | } | |
145 | Ok(true) => continue, | |
146 | Ok(false) => {} | |
147 | } | |
148 | ||
149 | match authentication_handle(message_type, &message, &connection_state).await { | |
150 | Err(e) => { | |
151 | let _ = connection_state | |
152 | .send_raw(ConnectionError(e.to_string())) | |
153 | .await; | |
154 | } | |
155 | Ok(true) => continue, | |
156 | Ok(false) => {} | |
157 | } | |
158 | ||
159 | error!( | |
160 | "Message completely unhandled: {}", | |
161 | std::str::from_utf8(&payload).unwrap() | |
162 | ); | |
163 | } | |
164 | } | |
165 | Some(Err(e)) => { | |
166 | error!("Closing connection for {:?} for {}", e, addr); | |
167 | return; | |
168 | } | |
169 | _ => { | |
170 | info!("Unhandled"); | |
171 | continue; | |
172 | } | |
173 | } | |
174 | } | |
52 | let _handshaked = false; | |
53 | ||
54 | // loop { | |
55 | // let mut socket = connection_state.socket.lock().await; | |
56 | // let message = socket.next().await; | |
57 | // drop(socket); | |
58 | ||
59 | // match message { | |
60 | // Some(Ok(message)) => { | |
61 | // let payload = match message { | |
62 | // Message::Binary(payload) => payload, | |
63 | // Message::Ping(_) => { | |
64 | // let mut socket = connection_state.socket.lock().await; | |
65 | // let _ = socket.send(Message::Pong(vec![])).await; | |
66 | // drop(socket); | |
67 | // continue; | |
68 | // } | |
69 | // Message::Close(_) => return, | |
70 | // _ => continue, | |
71 | // }; | |
72 | // info!("one payload"); | |
73 | ||
74 | // let message = NetworkMessage(payload.clone()); | |
75 | ||
76 | // if !handshaked { | |
77 | // info!("im foo baring"); | |
78 | // if handshake_handle(&message, &connection_state).await.is_ok() { | |
79 | // if connection_state.handshaked.load(Ordering::SeqCst) { | |
80 | // handshaked = true; | |
81 | // } | |
82 | // } | |
83 | // } else { | |
84 | // let raw = serde_json::from_slice::<AuthenticatedPayload>(&payload).unwrap(); | |
85 | ||
86 | // if let Some(target_instance) = &raw.target_instance { | |
87 | // if connection_state.instance != *target_instance { | |
88 | // // Forward request | |
89 | // info!("Forwarding message to {}", target_instance.url); | |
90 | // let mut instance_connections = instance_connections.lock().await; | |
91 | // let pool = instance_connections.get_or_open(&target_instance).unwrap(); | |
92 | // let pool_clone = pool.clone(); | |
93 | // drop(pool); | |
94 | ||
95 | // let result = wrap_forwarded(&pool_clone, raw).await; | |
96 | ||
97 | // let mut socket = connection_state.socket.lock().await; | |
98 | // let _ = socket.send(result).await; | |
99 | ||
100 | // continue; | |
101 | // } | |
102 | // } | |
103 | ||
104 | // let message_type = &raw.message_type; | |
105 | ||
106 | // info!("Handling message with type: {}", message_type); | |
107 | ||
108 | // match authentication_handle(message_type, &message, &connection_state).await { | |
109 | // Err(e) => { | |
110 | // let _ = connection_state | |
111 | // .send_raw(ConnectionError(e.to_string())) | |
112 | // .await; | |
113 | // } | |
114 | // Ok(true) => continue, | |
115 | // Ok(false) => {} | |
116 | // } | |
117 | ||
118 | // match repository_handle(message_type, &message, &connection_state).await { | |
119 | // Err(e) => { | |
120 | // let _ = connection_state | |
121 | // .send_raw(ConnectionError(e.to_string())) | |
122 | // .await; | |
123 | // } | |
124 | // Ok(true) => continue, | |
125 | // Ok(false) => {} | |
126 | // } | |
127 | ||
128 | // match user_handle(message_type, &message, &connection_state).await { | |
129 | // Err(e) => { | |
130 | // let _ = connection_state | |
131 | // .send_raw(ConnectionError(e.to_string())) | |
132 | // .await; | |
133 | // } | |
134 | // Ok(true) => continue, | |
135 | // Ok(false) => {} | |
136 | // } | |
137 | ||
138 | // match authentication_handle(message_type, &message, &connection_state).await { | |
139 | // Err(e) => { | |
140 | // let _ = connection_state | |
141 | // .send_raw(ConnectionError(e.to_string())) | |
142 | // .await; | |
143 | // } | |
144 | // Ok(true) => continue, | |
145 | // Ok(false) => {} | |
146 | // } | |
147 | ||
148 | // error!( | |
149 | // "Message completely unhandled: {}", | |
150 | // std::str::from_utf8(&payload).unwrap() | |
151 | // ); | |
152 | // } | |
153 | // } | |
154 | // Some(Err(e)) => { | |
155 | // error!("Closing connection for {:?} for {}", e, addr); | |
156 | // return; | |
157 | // } | |
158 | // _ => { | |
159 | // info!("Unhandled"); | |
160 | // continue; | |
161 | // } | |
162 | // } | |
163 | // } | |
175 | 164 | } |
176 | 165 | |
177 | 166 | #[derive(Clone)] |
giterated-daemon/src/database_backend/mod.rs
@@ -0,0 +1,109 @@ | ||
1 | use std::{str::FromStr, sync::Arc}; | |
2 | ||
3 | use giterated_models::{ | |
4 | error::OperationError, | |
5 | model::{instance::Instance, repository::Repository, user::User}, | |
6 | operation::{GiteratedObject, GiteratedOperation, Object, ObjectBackend, ObjectRequestError}, | |
7 | }; | |
8 | use sqlx::PgPool; | |
9 | use std::fmt::Debug; | |
10 | use tokio::sync::Mutex; | |
11 | ||
12 | use crate::backend::{RepositoryBackend, UserBackend}; | |
13 | ||
14 | #[derive(Clone, Debug)] | |
15 | pub struct Foobackend {} | |
16 | ||
17 | #[async_trait::async_trait] | |
18 | impl ObjectBackend for Foobackend { | |
19 | async fn object_operation<O: GiteratedObject + Debug, D: GiteratedOperation<O> + Debug>( | |
20 | &self, | |
21 | _object: O, | |
22 | _operation: D, | |
23 | ) -> Result<D::Success, OperationError<D::Failure>> { | |
24 | // We don't handle operations with this backend | |
25 | Err(OperationError::Unhandled) | |
26 | } | |
27 | ||
28 | async fn get_object<O: GiteratedObject + Debug>( | |
29 | &self, | |
30 | _object_str: &str, | |
31 | ) -> Result<Object<O, Self>, OperationError<ObjectRequestError>> { | |
32 | todo!() | |
33 | } | |
34 | } | |
35 | ||
36 | /// A backend implementation which attempts to resolve data from the instance's database. | |
37 | #[derive(Clone)] | |
38 | pub struct DatabaseBackend<'b> { | |
39 | our_instance: Object<'b, Instance, Foobackend>, | |
40 | user_backend: Arc<Mutex<dyn UserBackend + Send>>, | |
41 | repository_backend: Arc<Mutex<dyn RepositoryBackend + Send>>, | |
42 | pool: PgPool, | |
43 | } | |
44 | ||
45 | impl Debug for DatabaseBackend<'_> { | |
46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
47 | f.debug_struct("DatabaseBackend").finish() | |
48 | } | |
49 | } | |
50 | ||
51 | #[async_trait::async_trait] | |
52 | impl<'b> ObjectBackend for DatabaseBackend<'b> { | |
53 | async fn object_operation<O: GiteratedObject + Debug, D: GiteratedOperation<O> + Debug>( | |
54 | &self, | |
55 | _object: O, | |
56 | _operation: D, | |
57 | ) -> Result<D::Success, OperationError<D::Failure>> { | |
58 | // We don't handle operations with this backend | |
59 | Err(OperationError::Unhandled) | |
60 | } | |
61 | ||
62 | async fn get_object<O: GiteratedObject + Debug>( | |
63 | &self, | |
64 | object_str: &str, | |
65 | ) -> Result<Object<O, Self>, OperationError<ObjectRequestError>> { | |
66 | if let Ok(user) = User::from_str(object_str) { | |
67 | let mut user_backend = self.user_backend.lock().await; | |
68 | ||
69 | if user_backend | |
70 | .exists(&user) | |
71 | .await | |
72 | .map_err(|e| OperationError::Internal(e.to_string()))? | |
73 | { | |
74 | Ok(unsafe { | |
75 | Object::new_unchecked( | |
76 | O::from_object_str(object_str) | |
77 | .map_err(|e| ObjectRequestError::Deserialization(e.to_string()))?, | |
78 | self.clone(), | |
79 | ) | |
80 | }) | |
81 | } else { | |
82 | return Err(OperationError::Unhandled); | |
83 | } | |
84 | } else if let Ok(repository) = Repository::from_str(object_str) { | |
85 | let mut repository_backend = self.repository_backend.lock().await; | |
86 | ||
87 | if repository_backend | |
88 | .exists(&repository) | |
89 | .await | |
90 | .map_err(|e| OperationError::Internal(e.to_string()))? | |
91 | { | |
92 | Ok(unsafe { | |
93 | Object::new_unchecked( | |
94 | O::from_object_str(object_str) | |
95 | .map_err(|e| ObjectRequestError::Deserialization(e.to_string()))?, | |
96 | self.clone(), | |
97 | ) | |
98 | }) | |
99 | } else { | |
100 | return Err(OperationError::Unhandled); | |
101 | } | |
102 | } else if Instance::from_str(object_str).is_ok() { | |
103 | return Err(OperationError::Unhandled); | |
104 | } else { | |
105 | // Invalid object type | |
106 | return Err(OperationError::Unhandled); | |
107 | } | |
108 | } | |
109 | } |
giterated-daemon/src/lib.rs
@@ -5,6 +5,7 @@ use semver::{Version, VersionReq}; | ||
5 | 5 | pub mod authentication; |
6 | 6 | pub mod backend; |
7 | 7 | pub mod connection; |
8 | pub mod database_backend; | |
8 | 9 | pub mod federation; |
9 | 10 | pub mod keys; |
10 | 11 | pub mod message; |
giterated-models/Cargo.toml
@@ -24,6 +24,7 @@ aes-gcm = "0.10.2" | ||
24 | 24 | semver = {version = "*", features = ["serde"]} |
25 | 25 | tower = "*" |
26 | 26 | bincode = "*" |
27 | secrecy = { version = "0.8.0", features = ["serde"] } | |
27 | 28 | |
28 | 29 | toml = { version = "0.7" } |
29 | 30 |
giterated-models/src/error.rs
@@ -0,0 +1,28 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | #[derive(Debug, thiserror::Error, Deserialize, Serialize)] | |
4 | pub enum InstanceError { | |
5 | #[error("registration failed")] | |
6 | RegistrationFailure, | |
7 | #[error("authentication failed")] | |
8 | AuthenticationFailed, | |
9 | } | |
10 | ||
11 | #[derive(Debug, thiserror::Error, Serialize, Deserialize)] | |
12 | pub enum RepositoryError {} | |
13 | ||
14 | #[derive(Debug, thiserror::Error, Deserialize, Serialize)] | |
15 | pub enum UserError {} | |
16 | ||
17 | #[derive(Debug, thiserror::Error, Serialize, Deserialize)] | |
18 | pub enum GetValueError {} | |
19 | ||
20 | #[derive(Serialize, Deserialize, Debug, thiserror::Error)] | |
21 | pub enum OperationError<B> { | |
22 | #[error("the operation was handled but an error occured")] | |
23 | Operation(#[from] B), | |
24 | #[error("an internal error occured")] | |
25 | Internal(String), | |
26 | #[error("the operation was unhandled or unrecognized")] | |
27 | Unhandled, | |
28 | } |
giterated-models/src/handshake.rs
@@ -0,0 +1,22 @@ | ||
1 | use semver::Version; | |
2 | use serde::{Deserialize, Serialize}; | |
3 | ||
4 | use crate::model::instance::Instance; | |
5 | ||
6 | /// Sent by the initiator of a new inter-daemon connection. | |
7 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
8 | pub struct InitiateHandshake { | |
9 | pub version: Version, | |
10 | } | |
11 | ||
12 | /// Sent in response to [`InitiateHandshake`] | |
13 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
14 | pub struct HandshakeResponse { | |
15 | pub identity: Instance, | |
16 | pub version: Version, | |
17 | } | |
18 | ||
19 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
20 | pub struct HandshakeFinalize { | |
21 | pub success: bool, | |
22 | } |
giterated-models/src/lib.rs
@@ -1,2 +1,5 @@ | ||
1 | pub mod messages; | |
1 | pub mod error; | |
2 | pub mod handshake; | |
2 | 3 | pub mod model; |
4 | pub mod operation; | |
5 | pub mod values; |
giterated-models/src/messages/authentication.rs
@@ -1,3 +1,4 @@ | ||
1 | use secrecy::{Secret, SerializableSecret, Zeroize, CloneableSecret, DebugSecret}; | |
1 | 2 | use serde::{Deserialize, Serialize}; |
2 | 3 | |
3 | 4 | use crate::model::{authenticated::UserAuthenticationToken, instance::Instance}; |
@@ -13,7 +14,7 @@ use super::MessageTarget; | ||
13 | 14 | pub struct RegisterAccountRequest { |
14 | 15 | pub username: String, |
15 | 16 | pub email: Option<String>, |
16 | pub password: String, | |
17 | pub password: Secret<Password>, | |
17 | 18 | } |
18 | 19 | |
19 | 20 | impl MessageTarget for RegisterAccountRequest { |
@@ -42,9 +43,22 @@ pub struct RegisterAccountResponse { | ||
42 | 43 | pub struct AuthenticationTokenRequest { |
43 | 44 | pub instance: Instance, |
44 | 45 | pub username: String, |
45 | pub password: String, | |
46 | pub password: Secret<Password>, | |
46 | 47 | } |
47 | 48 | |
49 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
50 | pub struct Password(pub String); | |
51 | ||
52 | impl Zeroize for Password { | |
53 | fn zeroize(&mut self) { | |
54 | self.0.zeroize() | |
55 | } | |
56 | } | |
57 | ||
58 | impl SerializableSecret for Password {} | |
59 | impl CloneableSecret for Password {} | |
60 | impl DebugSecret for Password {} | |
61 | ||
48 | 62 | impl MessageTarget for AuthenticationTokenRequest { |
49 | 63 | fn target(&self) -> Option<Instance> { |
50 | 64 | Some(self.instance.clone()) |
giterated-models/src/messages/repository.rs
@@ -56,13 +56,12 @@ pub struct RepositoryCreateResponse; | ||
56 | 56 | /// - Potential User permissions checks |
57 | 57 | #[derive(Clone, Debug, Serialize, Deserialize)] |
58 | 58 | pub struct RepositoryFileInspectRequest { |
59 | pub repository: Repository, | |
60 | 59 | pub path: RepositoryTreeEntry, |
61 | 60 | } |
62 | 61 | |
63 | 62 | impl MessageTarget for RepositoryFileInspectRequest { |
64 | 63 | fn target(&self) -> Option<Instance> { |
65 | Some(self.repository.instance.clone()) | |
64 | None | |
66 | 65 | } |
67 | 66 | } |
68 | 67 | |
@@ -118,11 +117,6 @@ pub struct RepositoryIssueLabelsResponse { | ||
118 | 117 | pub labels: Vec<IssueLabel>, |
119 | 118 | } |
120 | 119 | |
121 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
122 | pub struct IssueLabel { | |
123 | pub name: String, | |
124 | pub color: String, | |
125 | } | |
126 | 120 | |
127 | 121 | /// A request to get a repository's issue labels. |
128 | 122 | /// |
@@ -143,23 +137,6 @@ pub struct RepositoryIssuesResponse { | ||
143 | 137 | pub issues: Vec<RepositoryIssue>, |
144 | 138 | } |
145 | 139 | |
146 | /// A request to get a repository's issues. | |
147 | /// | |
148 | /// # Authentication | |
149 | /// - Instance Authentication | |
150 | /// - Validate request against the `issued_for` public key | |
151 | /// - Validate User token against the user's instance's public key | |
152 | /// # Authorization | |
153 | /// - User Authorization | |
154 | /// - Potential User permissions checks | |
155 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
156 | pub struct RepositoryIssue { | |
157 | pub author: User, | |
158 | pub id: u64, | |
159 | pub title: String, | |
160 | pub contents: String, | |
161 | pub labels: Vec<IssueLabel>, | |
162 | } | |
163 | 140 | |
164 | 141 | #[derive(Clone, Debug, Serialize, Deserialize)] |
165 | 142 | pub struct RepositoryInfoRequest { |
giterated-models/src/model/authenticated.rs
@@ -1,4 +1,4 @@ | ||
1 | use std::{any::type_name, fmt::Debug}; | |
1 | use std::fmt::Debug; | |
2 | 2 | |
3 | 3 | use rsa::{ |
4 | 4 | pkcs1::DecodeRsaPrivateKey, |
@@ -10,9 +10,9 @@ use rsa::{ | ||
10 | 10 | use serde::{Deserialize, Serialize}; |
11 | 11 | use tracing::info; |
12 | 12 | |
13 | use crate::messages::MessageTarget; | |
13 | use crate::operation::{GiteratedMessage, GiteratedObject, GiteratedOperation}; | |
14 | 14 | |
15 | use super::{instance::Instance, user::User}; | |
15 | use super::{instance::Instance, user::User, MessageTarget}; | |
16 | 16 | |
17 | 17 | #[derive(Debug, Serialize, Deserialize)] |
18 | 18 | pub struct UserTokenMetadata { |
@@ -22,37 +22,33 @@ pub struct UserTokenMetadata { | ||
22 | 22 | } |
23 | 23 | |
24 | 24 | #[derive(Debug)] |
25 | pub struct Authenticated<T: Serialize> { | |
26 | pub target_instance: Option<Instance>, | |
25 | pub struct Authenticated<O: GiteratedObject, D: GiteratedOperation<O>> { | |
27 | 26 | pub source: Vec<Box<dyn AuthenticationSourceProvider + Send + Sync>>, |
28 | pub message_type: String, | |
29 | pub message: T, | |
27 | pub message: GiteratedMessage<O, D>, | |
30 | 28 | } |
31 | 29 | |
32 | 30 | #[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] |
33 | 31 | pub struct AuthenticatedPayload { |
34 | pub target_instance: Option<Instance>, | |
35 | 32 | pub source: Vec<AuthenticationSource>, |
36 | pub message_type: String, | |
37 | 33 | pub payload: Vec<u8>, |
38 | 34 | } |
39 | 35 | |
40 | impl<T: Serialize> From<Authenticated<T>> for AuthenticatedPayload { | |
41 | fn from(mut value: Authenticated<T>) -> Self { | |
42 | let payload = bincode::serialize(&value.message).unwrap(); | |
36 | // impl<T: Serialize> From<Authenticated<T>> for AuthenticatedPayload { | |
37 | // fn from(mut value: Authenticated<T>) -> Self { | |
38 | // let payload = bincode::serialize(&value.message).unwrap(); | |
43 | 39 | |
44 | AuthenticatedPayload { | |
45 | target_instance: value.target_instance, | |
46 | source: value | |
47 | .source | |
48 | .drain(..) | |
49 | .map(|provider| provider.as_ref().authenticate(&payload)) | |
50 | .collect::<Vec<_>>(), | |
51 | message_type: value.message_type, | |
52 | payload, | |
53 | } | |
54 | } | |
55 | } | |
40 | // AuthenticatedPayload { | |
41 | // target_instance: value.target_instance, | |
42 | // source: value | |
43 | // .source | |
44 | // .drain(..) | |
45 | // .map(|provider| provider.as_ref().authenticate(&payload)) | |
46 | // .collect::<Vec<_>>(), | |
47 | // message_type: value.message_type, | |
48 | // payload, | |
49 | // } | |
50 | // } | |
51 | // } | |
56 | 52 | |
57 | 53 | pub trait AuthenticationSourceProvider: Debug { |
58 | 54 | fn authenticate(&self, payload: &Vec<u8>) -> AuthenticationSource; |
@@ -83,30 +79,10 @@ where | ||
83 | 79 | } |
84 | 80 | } |
85 | 81 | |
86 | impl<T: Serialize + Debug + MessageTarget> Authenticated<T> { | |
87 | pub fn new(message: T) -> Self { | |
88 | Self { | |
89 | source: vec![], | |
90 | message_type: type_name::<T>().to_string(), | |
91 | target_instance: message.target(), | |
92 | message, | |
93 | } | |
94 | } | |
95 | ||
96 | pub fn new_for(instance: impl ToOwned<Owned = Instance>, message: T) -> Self { | |
82 | impl<O: GiteratedObject, D: GiteratedOperation<O>> Authenticated<O, D> { | |
83 | pub fn new(message: GiteratedMessage<O, D>) -> Self { | |
97 | 84 | Self { |
98 | 85 | source: vec![], |
99 | message_type: type_name::<T>().to_string(), | |
100 | message, | |
101 | target_instance: Some(instance.to_owned()), | |
102 | } | |
103 | } | |
104 | ||
105 | pub fn new_empty(message: T) -> Self { | |
106 | Self { | |
107 | source: vec![], | |
108 | message_type: type_name::<T>().to_string(), | |
109 | target_instance: message.target(), | |
110 | 86 | message, |
111 | 87 | } |
112 | 88 | } |
@@ -126,8 +102,17 @@ impl<T: Serialize + Debug + MessageTarget> Authenticated<T> { | ||
126 | 102 | .push(Box::new(authentication) as Box<dyn AuthenticationSourceProvider + Send + Sync>); |
127 | 103 | } |
128 | 104 | |
129 | pub fn into_payload(self) -> AuthenticatedPayload { | |
130 | self.into() | |
105 | pub fn into_payload(mut self) -> AuthenticatedPayload { | |
106 | let payload = bincode::serialize(&self.message).unwrap(); | |
107 | ||
108 | AuthenticatedPayload { | |
109 | source: self | |
110 | .source | |
111 | .drain(..) | |
112 | .map(|provider| provider.as_ref().authenticate(&payload)) | |
113 | .collect::<Vec<_>>(), | |
114 | payload, | |
115 | } | |
131 | 116 | } |
132 | 117 | } |
133 | 118 | |
@@ -160,7 +145,7 @@ impl AuthenticationSourceProvider for InstanceAuthenticator { | ||
160 | 145 | |
161 | 146 | let private_key = RsaPrivateKey::from_pkcs1_pem(&self.private_key).unwrap(); |
162 | 147 | let signing_key = SigningKey::<Sha256>::new(private_key); |
163 | let signature = signing_key.sign_with_rng(&mut rng, &payload); | |
148 | let signature = signing_key.sign_with_rng(&mut rng, payload); | |
164 | 149 | |
165 | 150 | AuthenticationSource::Instance { |
166 | 151 | instance: self.instance.clone(), |
giterated-models/src/model/instance.rs
@@ -3,6 +3,8 @@ use std::str::FromStr; | ||
3 | 3 | use serde::{Deserialize, Serialize}; |
4 | 4 | use thiserror::Error; |
5 | 5 | |
6 | use crate::operation::GiteratedObject; | |
7 | ||
6 | 8 | pub struct InstanceMeta { |
7 | 9 | pub url: String, |
8 | 10 | pub public_key: String, |
@@ -30,6 +32,16 @@ pub struct Instance { | ||
30 | 32 | pub url: String, |
31 | 33 | } |
32 | 34 | |
35 | impl GiteratedObject for Instance { | |
36 | fn object_name(&self) -> &str { | |
37 | "instance" | |
38 | } | |
39 | ||
40 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
41 | Ok(Instance::from_str(object_str).unwrap()) | |
42 | } | |
43 | } | |
44 | ||
33 | 45 | impl ToString for Instance { |
34 | 46 | fn to_string(&self) -> String { |
35 | 47 | self.url.clone() |
giterated-models/src/model/mod.rs
@@ -3,9 +3,17 @@ | ||
3 | 3 | //! All network data model types that are not directly associated with |
4 | 4 | //! individual requests or responses. |
5 | 5 | |
6 | use self::instance::Instance; | |
7 | ||
6 | 8 | pub mod authenticated; |
7 | 9 | pub mod discovery; |
8 | 10 | pub mod instance; |
9 | 11 | pub mod repository; |
10 | 12 | pub mod settings; |
11 | 13 | pub mod user; |
14 | ||
15 | pub trait MessageTarget { | |
16 | fn target(&self) -> Option<Instance> { | |
17 | todo!() | |
18 | } | |
19 | } |
giterated-models/src/model/repository.rs
@@ -3,6 +3,8 @@ use std::str::FromStr; | ||
3 | 3 | |
4 | 4 | use serde::{Deserialize, Serialize}; |
5 | 5 | |
6 | use crate::operation::GiteratedObject; | |
7 | ||
6 | 8 | use super::{instance::Instance, user::User}; |
7 | 9 | |
8 | 10 | /// A repository, defined by the instance it exists on along with |
@@ -36,6 +38,16 @@ pub struct Repository { | ||
36 | 38 | pub instance: Instance, |
37 | 39 | } |
38 | 40 | |
41 | impl GiteratedObject for Repository { | |
42 | fn object_name(&self) -> &str { | |
43 | "repository" | |
44 | } | |
45 | ||
46 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
47 | Ok(Repository::from_str(object_str).unwrap()) | |
48 | } | |
49 | } | |
50 | ||
39 | 51 | impl ToString for Repository { |
40 | 52 | fn to_string(&self) -> String { |
41 | 53 | format!("{}/{}@{}", self.owner, self.name, self.instance.to_string()) |
@@ -216,3 +228,18 @@ pub struct RepositorySummary { | ||
216 | 228 | pub description: Option<String>, |
217 | 229 | pub last_commit: Option<Commit>, |
218 | 230 | } |
231 | ||
232 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
233 | pub struct IssueLabel { | |
234 | pub name: String, | |
235 | pub color: String, | |
236 | } | |
237 | ||
238 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
239 | pub struct RepositoryIssue { | |
240 | pub author: User, | |
241 | pub id: u64, | |
242 | pub title: String, | |
243 | pub contents: String, | |
244 | pub labels: Vec<IssueLabel>, | |
245 | } |
giterated-models/src/model/user.rs
@@ -1,8 +1,11 @@ | ||
1 | 1 | use std::fmt::{Display, Formatter}; |
2 | 2 | use std::str::FromStr; |
3 | 3 | |
4 | use secrecy::{CloneableSecret, DebugSecret, SerializableSecret, Zeroize}; | |
4 | 5 | use serde::{Deserialize, Serialize}; |
5 | 6 | |
7 | use crate::operation::GiteratedObject; | |
8 | ||
6 | 9 | use super::instance::Instance; |
7 | 10 | |
8 | 11 | /// A user, defined by its username and instance. |
@@ -31,6 +34,16 @@ pub struct User { | ||
31 | 34 | pub instance: Instance, |
32 | 35 | } |
33 | 36 | |
37 | impl GiteratedObject for User { | |
38 | fn object_name(&self) -> &str { | |
39 | "user" | |
40 | } | |
41 | ||
42 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
43 | Ok(User::from_str(object_str).unwrap()) | |
44 | } | |
45 | } | |
46 | ||
34 | 47 | impl Display for User { |
35 | 48 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
36 | 49 | write!(f, "{}:{}", self.username, self.instance.url) |
@@ -54,3 +67,16 @@ impl FromStr for User { | ||
54 | 67 | Ok(Self { username, instance }) |
55 | 68 | } |
56 | 69 | } |
70 | ||
71 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
72 | pub struct Password(pub String); | |
73 | ||
74 | impl Zeroize for Password { | |
75 | fn zeroize(&mut self) { | |
76 | self.0.zeroize() | |
77 | } | |
78 | } | |
79 | ||
80 | impl SerializableSecret for Password {} | |
81 | impl CloneableSecret for Password {} | |
82 | impl DebugSecret for Password {} |
giterated-models/src/operation/instance.rs
@@ -0,0 +1,174 @@ | ||
1 | use secrecy::Secret; | |
2 | use serde::{Deserialize, Serialize}; | |
3 | ||
4 | use crate::{ | |
5 | error::InstanceError, | |
6 | model::{ | |
7 | authenticated::UserAuthenticationToken, | |
8 | instance::Instance, | |
9 | repository::{Repository, RepositoryVisibility}, | |
10 | user::{Password, User}, | |
11 | }, | |
12 | }; | |
13 | ||
14 | use super::{GiteratedOperation, Object, ObjectBackend}; | |
15 | ||
16 | /// An account registration request. | |
17 | /// | |
18 | /// # Authentication | |
19 | /// - Instance Authentication | |
20 | /// - **ONLY ACCEPTED WHEN SAME-INSTANCE** | |
21 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
22 | pub struct RegisterAccountRequest { | |
23 | pub username: String, | |
24 | pub email: Option<String>, | |
25 | pub password: Secret<Password>, | |
26 | } | |
27 | ||
28 | impl GiteratedOperation<Instance> for RegisterAccountRequest { | |
29 | type Success = UserAuthenticationToken; | |
30 | type Failure = InstanceError; | |
31 | } | |
32 | ||
33 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
34 | pub struct RegisterAccountResponse { | |
35 | pub token: String, | |
36 | } | |
37 | ||
38 | /// An authentication token request. | |
39 | /// | |
40 | /// AKA Login Request | |
41 | /// | |
42 | /// # Authentication | |
43 | /// - Instance Authentication | |
44 | /// - Identifies the Instance to issue the token for | |
45 | /// # Authorization | |
46 | /// - Credentials ([`crate::backend::AuthBackend`]-based) | |
47 | /// - Identifies the User account to issue a token for | |
48 | /// - Decrypts user private key to issue to | |
49 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
50 | pub struct AuthenticationTokenRequest { | |
51 | pub instance: Instance, | |
52 | pub username: String, | |
53 | pub password: Secret<Password>, | |
54 | } | |
55 | ||
56 | impl GiteratedOperation<Instance> for AuthenticationTokenRequest { | |
57 | type Success = UserAuthenticationToken; | |
58 | type Failure = InstanceError; | |
59 | } | |
60 | ||
61 | /// An authentication token extension request. | |
62 | /// | |
63 | /// # Authentication | |
64 | /// - Instance Authentication | |
65 | /// - Identifies the Instance to issue the token for | |
66 | /// - User Authentication | |
67 | /// - Authenticates the validity of the token | |
68 | /// # Authorization | |
69 | /// - Token-based | |
70 | /// - Validates authorization using token's authenticity | |
71 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
72 | pub struct TokenExtensionRequest { | |
73 | pub token: UserAuthenticationToken, | |
74 | } | |
75 | ||
76 | impl GiteratedOperation<Instance> for TokenExtensionRequest { | |
77 | type Success = Option<UserAuthenticationToken>; | |
78 | type Failure = InstanceError; | |
79 | } | |
80 | ||
81 | /// A request to create a repository. | |
82 | /// | |
83 | /// # Authentication | |
84 | /// - Instance Authentication | |
85 | /// - Used to validate User token `issued_for` | |
86 | /// - User Authentication | |
87 | /// - Used to source owning user | |
88 | /// - Used to authorize user token against user's instance | |
89 | /// # Authorization | |
90 | /// - Instance Authorization | |
91 | /// - Used to authorize action using User token requiring a correct `issued_for` and valid issuance from user's instance | |
92 | /// - User Authorization | |
93 | /// - Potential User permissions checks | |
94 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
95 | pub struct RepositoryCreateRequest { | |
96 | pub instance: Option<Instance>, | |
97 | pub name: String, | |
98 | pub description: Option<String>, | |
99 | pub visibility: RepositoryVisibility, | |
100 | pub default_branch: String, | |
101 | pub owner: User, | |
102 | } | |
103 | ||
104 | impl GiteratedOperation<Instance> for RepositoryCreateRequest { | |
105 | type Success = Repository; | |
106 | type Failure = InstanceError; | |
107 | } | |
108 | ||
109 | impl<B: ObjectBackend + std::fmt::Debug> Object<'_, Instance, B> { | |
110 | pub async fn register_account( | |
111 | &mut self, | |
112 | email: Option<&str>, | |
113 | username: &str, | |
114 | password: &Secret<Password>, | |
115 | ) -> Result<UserAuthenticationToken, InstanceError> { | |
116 | self.request::<RegisterAccountRequest>(RegisterAccountRequest { | |
117 | username: username.to_string(), | |
118 | email: email.map(|s| s.to_string()), | |
119 | password: password.clone(), | |
120 | }) | |
121 | } | |
122 | ||
123 | pub async fn authentication_token( | |
124 | &mut self, | |
125 | username: &str, | |
126 | password: &Secret<Password>, | |
127 | ) -> Result<UserAuthenticationToken, InstanceError> { | |
128 | self.request::<AuthenticationTokenRequest>(AuthenticationTokenRequest { | |
129 | instance: self.inner.clone(), | |
130 | username: username.to_string(), | |
131 | password: password.clone(), | |
132 | }) | |
133 | } | |
134 | ||
135 | pub async fn authentication_token_for( | |
136 | &mut self, | |
137 | instance: &Instance, | |
138 | username: &str, | |
139 | password: &Secret<Password>, | |
140 | ) -> Result<UserAuthenticationToken, InstanceError> { | |
141 | self.request::<AuthenticationTokenRequest>(AuthenticationTokenRequest { | |
142 | instance: instance.clone(), | |
143 | username: username.to_string(), | |
144 | password: password.clone(), | |
145 | }) | |
146 | } | |
147 | ||
148 | pub async fn token_extension( | |
149 | &mut self, | |
150 | token: &UserAuthenticationToken, | |
151 | ) -> Result<Option<UserAuthenticationToken>, InstanceError> { | |
152 | self.request::<TokenExtensionRequest>(TokenExtensionRequest { | |
153 | token: token.clone(), | |
154 | }) | |
155 | } | |
156 | ||
157 | pub async fn create_repository( | |
158 | &mut self, | |
159 | instance: &Instance, | |
160 | name: &str, | |
161 | visibility: RepositoryVisibility, | |
162 | default_branch: &str, | |
163 | owner: &User, | |
164 | ) -> Result<Repository, InstanceError> { | |
165 | self.request::<RepositoryCreateRequest>(RepositoryCreateRequest { | |
166 | instance: Some(instance.clone()), | |
167 | name: name.to_string(), | |
168 | description: None, | |
169 | visibility, | |
170 | default_branch: default_branch.to_string(), | |
171 | owner: owner.clone(), | |
172 | }) | |
173 | } | |
174 | } |
giterated-models/src/operation/mod.rs
@@ -0,0 +1,179 @@ | ||
1 | use std::{any::type_name, fmt::Debug, marker::PhantomData}; | |
2 | ||
3 | use anyhow::Error; | |
4 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; | |
5 | ||
6 | use crate::{ | |
7 | error::{GetValueError, OperationError}, | |
8 | model::{instance::Instance, MessageTarget}, | |
9 | }; | |
10 | ||
11 | pub mod instance; | |
12 | pub mod repository; | |
13 | pub mod user; | |
14 | ||
15 | pub trait GiteratedObject: Send + Serialize + DeserializeOwned { | |
16 | fn object_name(&self) -> &str; | |
17 | ||
18 | fn from_object_str(object_str: &str) -> Result<Self, Error>; | |
19 | } | |
20 | ||
21 | pub trait GiteratedOperation<O: GiteratedObject>: Send + Serialize + DeserializeOwned { | |
22 | type Success: Serialize + DeserializeOwned + Send; | |
23 | type Failure: Serialize + DeserializeOwned + Send; | |
24 | ||
25 | fn operation_name(&self) -> &'static str { | |
26 | type_name::<Self>() | |
27 | } | |
28 | } | |
29 | ||
30 | pub trait GiteratedObjectValue: Serialize + DeserializeOwned { | |
31 | type Object: GiteratedObject; | |
32 | ||
33 | fn value_name() -> &'static str; | |
34 | } | |
35 | ||
36 | #[derive(Debug, Clone)] | |
37 | pub struct Object<'b, O: GiteratedObject, B: ObjectBackend + 'b + Send + Sync + Clone> { | |
38 | inner: O, | |
39 | backend: B, | |
40 | _marker: PhantomData<&'b ()>, | |
41 | } | |
42 | ||
43 | #[async_trait::async_trait] | |
44 | pub trait ObjectBackend: Send + Sync + Sized + Clone { | |
45 | async fn object_operation<O: GiteratedObject + Debug, D: GiteratedOperation<O> + Debug>( | |
46 | &self, | |
47 | object: O, | |
48 | operation: D, | |
49 | ) -> Result<D::Success, OperationError<D::Failure>>; | |
50 | ||
51 | async fn get_object<O: GiteratedObject + Debug>( | |
52 | &self, | |
53 | object_str: &str, | |
54 | ) -> Result<Object<O, Self>, OperationError<ObjectRequestError>>; | |
55 | } | |
56 | ||
57 | impl<'b, B: ObjectBackend + Send + Sync + Clone, O: GiteratedObject> Object<'b, O, B> { | |
58 | pub unsafe fn new_unchecked(object: O, backend: B) -> Object<'b, O, B> { | |
59 | Object { | |
60 | inner: object, | |
61 | backend, | |
62 | _marker: PhantomData, | |
63 | } | |
64 | } | |
65 | } | |
66 | ||
67 | // impl<'b, O: GiteratedObject, B: ObjectBackend> Object<'b, O, B> { | |
68 | // pub unsafe fn new_unchecked(value: O, backend: Arc<B>) -> Object<'b, O, B> { | |
69 | // todo!() | |
70 | // } | |
71 | // } | |
72 | ||
73 | impl<'b, O: GiteratedObject + Clone + Debug, B: ObjectBackend + Debug + Send + Sync + Clone> | |
74 | Object<'b, O, B> | |
75 | { | |
76 | pub async fn get<V: GiteratedObjectValue<Object = O> + Send>( | |
77 | &self, | |
78 | ) -> Result<V, OperationError<ObjectRequestError>> { | |
79 | let operation: GetValue<V> = GetValue { | |
80 | value_name: V::value_name().to_string(), | |
81 | _marker: PhantomData, | |
82 | }; | |
83 | ||
84 | let _message: GiteratedMessage<O, _> = GiteratedMessage { | |
85 | object: self.inner.clone(), | |
86 | operation: operation.operation_name().to_string(), | |
87 | payload: operation, | |
88 | }; | |
89 | ||
90 | todo!() | |
91 | } | |
92 | ||
93 | pub fn request<R: GiteratedOperation<O> + Debug>( | |
94 | &mut self, | |
95 | request: R, | |
96 | ) -> Result<R::Success, R::Failure> { | |
97 | self.backend.object_operation(self.inner.clone(), request); | |
98 | ||
99 | todo!() | |
100 | } | |
101 | } | |
102 | ||
103 | #[derive(Serialize, Deserialize)] | |
104 | pub struct GetValue<V: GiteratedObjectValue> { | |
105 | value_name: String, | |
106 | _marker: PhantomData<V>, | |
107 | } | |
108 | ||
109 | impl<O: GiteratedObject + Send, V: GiteratedObjectValue<Object = O> + Send> GiteratedOperation<O> | |
110 | for GetValue<V> | |
111 | { | |
112 | fn operation_name(&self) -> &'static str { | |
113 | "get_value" | |
114 | } | |
115 | type Success = V; | |
116 | type Failure = GetValueError; | |
117 | } | |
118 | ||
119 | #[derive(Serialize)] | |
120 | #[serde(bound(deserialize = "O: GiteratedObject, V: GiteratedOperation<O>"))] | |
121 | pub struct GiteratedMessage<O: GiteratedObject, V: GiteratedOperation<O>> { | |
122 | pub object: O, | |
123 | pub operation: String, | |
124 | pub payload: V, | |
125 | } | |
126 | ||
127 | impl<O: GiteratedObject, V: GiteratedOperation<O>> MessageTarget for GiteratedMessage<O, V> {} | |
128 | ||
129 | impl<V: GiteratedOperation<O> + Debug, O: GiteratedObject + Debug> Debug | |
130 | for GiteratedMessage<O, V> | |
131 | { | |
132 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
133 | f.debug_struct("GiteratedMessage") | |
134 | .field("object", &self.object) | |
135 | .field("operation", &self.operation) | |
136 | .field("payload", &self.payload) | |
137 | .finish() | |
138 | } | |
139 | } | |
140 | ||
141 | #[derive(Debug, Serialize, Deserialize)] | |
142 | pub struct ObjectRequest(pub String); | |
143 | ||
144 | #[derive(Serialize, Deserialize)] | |
145 | pub struct ObjectResponse(pub Vec<u8>); | |
146 | ||
147 | impl GiteratedOperation<Instance> for ObjectRequest { | |
148 | type Success = ObjectResponse; | |
149 | ||
150 | type Failure = ObjectRequestError; | |
151 | } | |
152 | ||
153 | #[derive(Debug, thiserror::Error, Serialize, Deserialize)] | |
154 | pub enum ObjectRequestError { | |
155 | #[error("error decoding the object")] | |
156 | Deserialization(String), | |
157 | } | |
158 | ||
159 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
160 | pub struct AnyObject(Vec<u8>); | |
161 | ||
162 | impl GiteratedObject for AnyObject { | |
163 | fn object_name(&self) -> &str { | |
164 | "any" | |
165 | } | |
166 | ||
167 | fn from_object_str(object_str: &str) -> Result<Self, Error> { | |
168 | Ok(Self(Vec::from(object_str.as_bytes()))) | |
169 | } | |
170 | } | |
171 | ||
172 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
173 | pub struct AnyOperation(Vec<u8>); | |
174 | ||
175 | impl<O: GiteratedObject> GiteratedOperation<O> for AnyOperation { | |
176 | type Success = Vec<u8>; | |
177 | ||
178 | type Failure = Vec<u8>; | |
179 | } |
giterated-models/src/operation/repository.rs
@@ -0,0 +1,101 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{ | |
4 | error::RepositoryError, | |
5 | model::repository::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry}, | |
6 | }; | |
7 | ||
8 | use super::{GiteratedOperation, Object, ObjectBackend}; | |
9 | ||
10 | /// A request to get a repository's information. | |
11 | /// | |
12 | /// # Authentication | |
13 | /// - Instance Authentication | |
14 | /// - Validate request against the `issued_for` public key | |
15 | /// - Validate User token against the user's instance's public key | |
16 | /// # Authorization | |
17 | /// - User Authorization | |
18 | /// - Potential User permissions checks | |
19 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
20 | pub struct RepositoryIssuesCountRequest; | |
21 | ||
22 | impl GiteratedOperation<Repository> for RepositoryIssuesCountRequest { | |
23 | type Success = u64; | |
24 | type Failure = RepositoryError; | |
25 | } | |
26 | ||
27 | /// A request to get a repository's issues count. | |
28 | /// | |
29 | /// # Authentication | |
30 | /// - Instance Authentication | |
31 | /// - Validate request against the `issued_for` public key | |
32 | /// - Validate User token against the user's instance's public key | |
33 | /// # Authorization | |
34 | /// - User Authorization | |
35 | /// - Potential User permissions checks | |
36 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
37 | pub struct RepositoryIssueLabelsRequest; | |
38 | ||
39 | impl GiteratedOperation<Repository> for RepositoryIssueLabelsRequest { | |
40 | type Success = Vec<IssueLabel>; | |
41 | type Failure = RepositoryError; | |
42 | } | |
43 | ||
44 | /// A request to get a repository's issue labels. | |
45 | /// | |
46 | /// # Authentication | |
47 | /// - Instance Authentication | |
48 | /// - Validate request against the `issued_for` public key | |
49 | /// - Validate User token against the user's instance's public key | |
50 | /// # Authorization | |
51 | /// - User Authorization | |
52 | /// - Potential User permissions checks | |
53 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
54 | pub struct RepositoryIssuesRequest; | |
55 | ||
56 | impl GiteratedOperation<Repository> for RepositoryIssuesRequest { | |
57 | type Success = Vec<RepositoryIssue>; | |
58 | type Failure = RepositoryError; | |
59 | } | |
60 | ||
61 | /// A request to inspect the tree of a repository. | |
62 | /// | |
63 | /// # Authentication | |
64 | /// - Instance Authentication | |
65 | /// - Validate request against the `issued_for` public key | |
66 | /// - Validate User token against the user's instance's public key | |
67 | /// # Authorization | |
68 | /// - User Authorization | |
69 | /// - Potential User permissions checks | |
70 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
71 | pub struct RepositoryFileInspectRequest { | |
72 | pub path: RepositoryTreeEntry, | |
73 | } | |
74 | ||
75 | impl GiteratedOperation<Repository> for RepositoryFileInspectRequest { | |
76 | type Success = Vec<RepositoryTreeEntry>; | |
77 | type Failure = RepositoryError; | |
78 | } | |
79 | ||
80 | impl<B: ObjectBackend + std::fmt::Debug> Object<'_, Repository, B> { | |
81 | pub async fn issues_count(&mut self) -> Result<u64, RepositoryError> { | |
82 | self.request::<RepositoryIssuesCountRequest>(RepositoryIssuesCountRequest) | |
83 | } | |
84 | ||
85 | pub async fn issue_labels(&mut self) -> Result<Vec<IssueLabel>, RepositoryError> { | |
86 | self.request::<RepositoryIssueLabelsRequest>(RepositoryIssueLabelsRequest) | |
87 | } | |
88 | ||
89 | pub async fn issues(&mut self) -> Result<Vec<RepositoryIssue>, RepositoryError> { | |
90 | self.request::<RepositoryIssuesRequest>(RepositoryIssuesRequest) | |
91 | } | |
92 | ||
93 | pub async fn inspect_files( | |
94 | &mut self, | |
95 | entry: &RepositoryTreeEntry, | |
96 | ) -> Result<Vec<RepositoryTreeEntry>, RepositoryError> { | |
97 | self.request::<RepositoryFileInspectRequest>(RepositoryFileInspectRequest { | |
98 | path: entry.clone(), | |
99 | }) | |
100 | } | |
101 | } |
giterated-models/src/operation/user.rs
@@ -0,0 +1,31 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{ | |
4 | error::UserError, | |
5 | model::{instance::Instance, repository::Repository, user::User}, | |
6 | }; | |
7 | ||
8 | use super::{GiteratedOperation, Object, ObjectBackend}; | |
9 | ||
10 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
11 | pub struct UserRepositoriesRequest { | |
12 | pub instance: Instance, | |
13 | pub user: User, | |
14 | } | |
15 | ||
16 | impl GiteratedOperation<User> for UserRepositoriesRequest { | |
17 | type Success = Vec<Repository>; | |
18 | type Failure = UserError; | |
19 | } | |
20 | ||
21 | impl<B: ObjectBackend + std::fmt::Debug> Object<'_, User, B> { | |
22 | pub async fn repositories( | |
23 | &mut self, | |
24 | instance: &Instance, | |
25 | ) -> Result<Vec<Repository>, UserError> { | |
26 | self.request::<UserRepositoriesRequest>(UserRepositoriesRequest { | |
27 | instance: instance.clone(), | |
28 | user: self.inner.clone(), | |
29 | }) | |
30 | } | |
31 | } |
giterated-models/src/values/instance.rs
@@ -0,0 +1 @@ | ||
1 |
giterated-models/src/values/mod.rs
@@ -0,0 +1,3 @@ | ||
1 | pub mod instance; | |
2 | pub mod repository; | |
3 | pub mod user; |
giterated-models/src/values/repository.rs
@@ -0,0 +1,40 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{ | |
4 | model::repository::{Repository, RepositoryVisibility}, | |
5 | operation::GiteratedObjectValue, | |
6 | }; | |
7 | ||
8 | // pub struct RepositorySetting<V: GiteratedObjectValue>(pub V); | |
9 | ||
10 | // impl<O: GiteratedObject, V: GiteratedObjectValue<Object = O> + Send> GiteratedOperation<O> | |
11 | // for RepositorySetting<V> | |
12 | // { | |
13 | // fn operation_name(&self) -> &'static str { | |
14 | // "setting_get" | |
15 | // } | |
16 | // type Success = V; | |
17 | // type Failure = GetValueError; | |
18 | // } | |
19 | ||
20 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
21 | pub struct Description(pub String); | |
22 | ||
23 | impl GiteratedObjectValue for Description { | |
24 | type Object = Repository; | |
25 | ||
26 | fn value_name() -> &'static str { | |
27 | "description" | |
28 | } | |
29 | } | |
30 | ||
31 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
32 | pub struct Visibility(pub RepositoryVisibility); | |
33 | ||
34 | impl GiteratedObjectValue for Visibility { | |
35 | type Object = Repository; | |
36 | ||
37 | fn value_name() -> &'static str { | |
38 | "visibility" | |
39 | } | |
40 | } |
giterated-models/src/values/user.rs
@@ -0,0 +1,37 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{model::user::User, operation::GiteratedObjectValue}; | |
4 | ||
5 | // pub struct UserSetting<V: GiteratedObjectValue>(pub V); | |
6 | ||
7 | // impl<O: GiteratedObject, V: GiteratedObjectValue<Object = O> + Send> GiteratedOperation<O> | |
8 | // for UserSetting<V> | |
9 | // { | |
10 | // fn operation_name(&self) -> &'static str { | |
11 | // "setting_get" | |
12 | // } | |
13 | // type Success = V; | |
14 | // type Failure = GetValueError; | |
15 | // } | |
16 | ||
17 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
18 | pub struct Bio(pub String); | |
19 | ||
20 | impl GiteratedObjectValue for Bio { | |
21 | type Object = User; | |
22 | ||
23 | fn value_name() -> &'static str { | |
24 | "bio" | |
25 | } | |
26 | } | |
27 | ||
28 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
29 | pub struct DisplayName(pub String); | |
30 | ||
31 | impl GiteratedObjectValue for DisplayName { | |
32 | type Object = User; | |
33 | ||
34 | fn value_name() -> &'static str { | |
35 | "display_name" | |
36 | } | |
37 | } |