JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Begin new protocol refactor

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨26651b1

Showing ⁨⁨30⁩ changed files⁩ with ⁨⁨1223⁩ insertions⁩ and ⁨⁨564⁩ deletions⁩

Cargo.lock

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -0,0 +1 @@
1

giterated-models/src/values/mod.rs

View file
@@ -0,0 +1,3 @@
1 pub mod instance;
2 pub mod repository;
3 pub mod user;

giterated-models/src/values/repository.rs

View file
@@ -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

View file
@@ -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 }