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

ambee/giterated

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

Add docs

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨51aad53

Showing ⁨⁨19⁩ changed files⁩ with ⁨⁨593⁩ insertions⁩ and ⁨⁨257⁩ deletions⁩

.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json

View file
@@ -32,6 +32,16 @@
32 32 "ordinal": 5,
33 33 "name": "password",
34 34 "type_info": "Text"
35 },
36 {
37 "ordinal": 6,
38 "name": "public_key",
39 "type_info": "Text"
40 },
41 {
42 "ordinal": 7,
43 "name": "enc_private_key",
44 "type_info": "Text"
35 45 }
36 46 ],
37 47 "parameters": {
@@ -45,6 +55,8 @@
45 55 false,
46 56 true,
47 57 true,
58 false,
59 false,
48 60 false
49 61 ]
50 62 },

.sqlx/query-80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f.json

View file
@@ -0,0 +1,68 @@
1 {
2 "db_name": "PostgreSQL",
3 "query": "INSERT INTO users VALUES ($1, null, $2, null, null, $3, $4, $5) returning *",
4 "describe": {
5 "columns": [
6 {
7 "ordinal": 0,
8 "name": "username",
9 "type_info": "Text"
10 },
11 {
12 "ordinal": 1,
13 "name": "display_name",
14 "type_info": "Text"
15 },
16 {
17 "ordinal": 2,
18 "name": "image_url",
19 "type_info": "Text"
20 },
21 {
22 "ordinal": 3,
23 "name": "bio",
24 "type_info": "Text"
25 },
26 {
27 "ordinal": 4,
28 "name": "email",
29 "type_info": "Text"
30 },
31 {
32 "ordinal": 5,
33 "name": "password",
34 "type_info": "Text"
35 },
36 {
37 "ordinal": 6,
38 "name": "public_key",
39 "type_info": "Text"
40 },
41 {
42 "ordinal": 7,
43 "name": "enc_private_key",
44 "type_info": "Text"
45 }
46 ],
47 "parameters": {
48 "Left": [
49 "Text",
50 "Text",
51 "Text",
52 "Text",
53 "Text"
54 ]
55 },
56 "nullable": [
57 false,
58 true,
59 false,
60 true,
61 true,
62 false,
63 false,
64 false
65 ]
66 },
67 "hash": "80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f"
68 }

Cargo.lock

View file
@@ -18,6 +18,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
18 18 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 19
20 20 [[package]]
21 name = "aead"
22 version = "0.5.2"
23 source = "registry+https://github.com/rust-lang/crates.io-index"
24 checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
25 dependencies = [
26 "crypto-common",
27 "generic-array",
28 ]
29
30 [[package]]
31 name = "aes"
32 version = "0.8.3"
33 source = "registry+https://github.com/rust-lang/crates.io-index"
34 checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
35 dependencies = [
36 "cfg-if",
37 "cipher",
38 "cpufeatures",
39 ]
40
41 [[package]]
42 name = "aes-gcm"
43 version = "0.10.2"
44 source = "registry+https://github.com/rust-lang/crates.io-index"
45 checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237"
46 dependencies = [
47 "aead",
48 "aes",
49 "cipher",
50 "ctr",
51 "ghash",
52 "subtle",
53 ]
54
55 [[package]]
21 56 name = "ahash"
22 57 version = "0.8.3"
23 58 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -211,6 +246,16 @@ dependencies = [
211 246 ]
212 247
213 248 [[package]]
249 name = "cipher"
250 version = "0.4.4"
251 source = "registry+https://github.com/rust-lang/crates.io-index"
252 checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
253 dependencies = [
254 "crypto-common",
255 "inout",
256 ]
257
258 [[package]]
214 259 name = "const-oid"
215 260 version = "0.9.5"
216 261 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -282,10 +327,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
282 327 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
283 328 dependencies = [
284 329 "generic-array",
330 "rand_core",
285 331 "typenum",
286 332 ]
287 333
288 334 [[package]]
335 name = "ctr"
336 version = "0.9.2"
337 source = "registry+https://github.com/rust-lang/crates.io-index"
338 checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
339 dependencies = [
340 "cipher",
341 ]
342
343 [[package]]
289 344 name = "data-encoding"
290 345 version = "2.4.0"
291 346 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -542,6 +597,16 @@ dependencies = [
542 597 ]
543 598
544 599 [[package]]
600 name = "ghash"
601 version = "0.5.0"
602 source = "registry+https://github.com/rust-lang/crates.io-index"
603 checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
604 dependencies = [
605 "opaque-debug",
606 "polyval",
607 ]
608
609 [[package]]
545 610 name = "gimli"
546 611 version = "0.28.0"
547 612 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -566,9 +631,11 @@ dependencies = [
566 631 name = "giterated-daemon"
567 632 version = "0.1.0"
568 633 dependencies = [
634 "aes-gcm",
569 635 "anyhow",
570 636 "argon2",
571 637 "async-trait",
638 "base64 0.21.3",
572 639 "chrono",
573 640 "futures-util",
574 641 "git2",
@@ -805,6 +872,15 @@ dependencies = [
805 872 ]
806 873
807 874 [[package]]
875 name = "inout"
876 version = "0.1.3"
877 source = "registry+https://github.com/rust-lang/crates.io-index"
878 checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
879 dependencies = [
880 "generic-array",
881 ]
882
883 [[package]]
808 884 name = "ipnet"
809 885 version = "2.8.0"
810 886 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1121,6 +1197,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1121 1197 checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
1122 1198
1123 1199 [[package]]
1200 name = "opaque-debug"
1201 version = "0.3.0"
1202 source = "registry+https://github.com/rust-lang/crates.io-index"
1203 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
1204
1205 [[package]]
1124 1206 name = "openssl"
1125 1207 version = "0.10.57"
1126 1208 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1294,6 +1376,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1294 1376 checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
1295 1377
1296 1378 [[package]]
1379 name = "polyval"
1380 version = "0.6.1"
1381 source = "registry+https://github.com/rust-lang/crates.io-index"
1382 checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
1383 dependencies = [
1384 "cfg-if",
1385 "cpufeatures",
1386 "opaque-debug",
1387 "universal-hash",
1388 ]
1389
1390 [[package]]
1297 1391 name = "ppv-lite86"
1298 1392 version = "0.2.17"
1299 1393 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2255,6 +2349,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2255 2349 checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
2256 2350
2257 2351 [[package]]
2352 name = "universal-hash"
2353 version = "0.5.1"
2354 source = "registry+https://github.com/rust-lang/crates.io-index"
2355 checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
2356 dependencies = [
2357 "crypto-common",
2358 "subtle",
2359 ]
2360
2361 [[package]]
2258 2362 name = "untrusted"
2259 2363 version = "0.7.1"
2260 2364 source = "registry+https://github.com/rust-lang/crates.io-index"

Cargo.toml

View file
@@ -13,12 +13,14 @@ futures-util = "*"
13 13 serde = { version = "1", features = [ "derive" ]}
14 14 serde_json = "1.0"
15 15 tracing-subscriber = "0.3"
16 base64 = "0.21.3"
16 17 jsonwebtoken = { version = "*", features = ["use_pem"]}
17 18 log = "*"
18 19 rand = "*"
19 20 rsa = {version = "0.9", features = ["sha2"]}
20 21 reqwest = "*"
21 22 argon2 = "*"
23 aes-gcm = "0.10.2"
22 24
23 25 toml = { version = "0.7" }
24 26

migrations/20230829104151_create_users.sql

View file
@@ -7,7 +7,9 @@ CREATE TABLE IF NOT EXISTS users
7 7 image_url TEXT NOT NULL,
8 8 bio TEXT,
9 9 email TEXT,
10 password TEXT NOT NULL
10 password TEXT NOT NULL,
11 public_key TEXT NOT NULL,
12 enc_private_key TEXT NOT NULL
11 13 );
12 14
13 15 CREATE UNIQUE INDEX unique_username ON users (username);
13 15 \ No newline at end of file

src/authentication.rs

View file
@@ -1,7 +1,7 @@
1 use std::{error::Error, time::SystemTime};
2
1 use anyhow::Error;
3 2 use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, TokenData, Validation};
4 3 use serde::{Deserialize, Serialize};
4 use std::time::SystemTime;
5 5 use tokio::{fs::File, io::AsyncReadExt};
6 6 use toml::Table;
7 7
@@ -70,7 +70,7 @@ impl AuthenticationTokenGranter {
70 70 pub async fn token_request(
71 71 &mut self,
72 72 raw_request: InstanceAuthenticated<AuthenticationTokenRequest>,
73 ) -> Result<AuthenticationTokenResponse, Box<dyn Error + Send>> {
73 ) -> Result<AuthenticationTokenResponse, Error> {
74 74 let request = raw_request.inner().await;
75 75
76 76 info!("Ensuring token request is from the same instance...");
@@ -131,7 +131,7 @@ impl AuthenticationTokenGranter {
131 131 pub async fn extension_request(
132 132 &mut self,
133 133 raw_request: InstanceAuthenticated<TokenExtensionRequest>,
134 ) -> Result<TokenExtensionResponse, Box<dyn Error + Send>> {
134 ) -> Result<TokenExtensionResponse, Error> {
135 135 let request = raw_request.inner().await;
136 136
137 137 // let server_public_key = {
@@ -213,7 +213,7 @@ impl AuthenticationTokenGranter {
213 213 }
214 214 }
215 215
216 async fn public_key(instance: &Instance) -> Result<String, Box<dyn Error>> {
216 async fn public_key(instance: &Instance) -> Result<String, Error> {
217 217 let key = reqwest::get(format!("https://{}/.giterated/pubkey.pem", instance.url))
218 218 .await?
219 219 .text()

src/backend/git.rs

View file
@@ -1,7 +1,7 @@
1 use anyhow::Error;
1 2 use async_trait::async_trait;
2 3 use git2::ObjectType;
3 4 use sqlx::PgPool;
4 use std::error::Error;
5 5 use std::path::{Path, PathBuf};
6 6 use thiserror::Error;
7 7
@@ -203,7 +203,7 @@ impl RepositoryBackend for GitBackend {
203 203 async fn create_repository(
204 204 &mut self,
205 205 raw_request: &ValidatedUserAuthenticated<CreateRepositoryRequest>,
206 ) -> Result<CreateRepositoryResponse, Box<dyn Error + Send>> {
206 ) -> Result<CreateRepositoryResponse, Error> {
207 207 let request = raw_request.inner().await;
208 208
209 209 // let public_key = public_key(&Instance {
@@ -275,7 +275,7 @@ impl RepositoryBackend for GitBackend {
275 275 .delete_by_owner_user_name(&request.owner, request.name.as_str())
276 276 .await
277 277 {
278 return Err(Box::new(err));
278 return Err(Box::new(err).into());
279 279 }
280 280
281 281 // ???
@@ -289,7 +289,7 @@ impl RepositoryBackend for GitBackend {
289 289 &mut self,
290 290 // TODO: Allow non-authenticated???
291 291 raw_request: &ValidatedUserAuthenticated<RepositoryInfoRequest>,
292 ) -> Result<RepositoryView, Box<dyn Error + Send>> {
292 ) -> Result<RepositoryView, Error> {
293 293 let request = raw_request.inner().await;
294 294
295 295 let repository = match self
@@ -301,19 +301,20 @@ impl RepositoryBackend for GitBackend {
301 301 .await
302 302 {
303 303 Ok(repository) => repository,
304 Err(err) => return Err(Box::new(err)),
304 Err(err) => return Err(Box::new(err).into()),
305 305 };
306 306
307 307 if !repository.can_user_view_repository(Some(&raw_request.user)) {
308 308 return Err(Box::new(GitBackendError::RepositoryNotFound {
309 309 owner_user: request.repository.owner.to_string(),
310 310 name: request.repository.name.clone(),
311 }));
311 })
312 .into());
312 313 }
313 314
314 315 let git = match repository.open_git2_repository(&self.repository_folder) {
315 316 Ok(git) => git,
316 Err(err) => return Err(Box::new(err)),
317 Err(err) => return Err(Box::new(err).into()),
317 318 };
318 319
319 320 let rev_name = match &request.rev {
@@ -341,7 +342,7 @@ impl RepositoryBackend for GitBackend {
341 342 .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string()))
342 343 {
343 344 Ok(reference) => reference.name().unwrap().to_string(),
344 Err(err) => return Err(Box::new(err)),
345 Err(err) => return Err(Box::new(err).into()),
345 346 }
346 347 }
347 348 };
@@ -352,7 +353,7 @@ impl RepositoryBackend for GitBackend {
352 353 .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string()))
353 354 {
354 355 Ok(rev) => rev,
355 Err(err) => return Err(Box::new(err)),
356 Err(err) => return Err(Box::new(err).into()),
356 357 };
357 358 let commit = rev.as_commit().unwrap();
358 359
@@ -371,7 +372,7 @@ impl RepositoryBackend for GitBackend {
371 372 .map_err(|_| GitBackendError::PathNotFound(path.to_string()))
372 373 {
373 374 Ok(entry) => entry,
374 Err(err) => return Err(Box::new(err)),
375 Err(err) => return Err(Box::new(err).into()),
375 376 };
376 377 // Turn the entry into a git tree
377 378 entry.to_object(&git).unwrap().as_tree().unwrap().clone()
@@ -438,7 +439,7 @@ impl RepositoryBackend for GitBackend {
438 439 fn repository_file_inspect(
439 440 &mut self,
440 441 _request: &ValidatedUserAuthenticated<RepositoryFileInspectRequest>,
441 ) -> Result<RepositoryFileInspectionResponse, Box<dyn Error + Send>> {
442 ) -> Result<RepositoryFileInspectionResponse, Error> {
442 443 todo!()
443 444 }
444 445 }
@@ -447,21 +448,21 @@ impl IssuesBackend for GitBackend {
447 448 fn issues_count(
448 449 &mut self,
449 450 _request: &ValidatedUserAuthenticated<RepositoryIssuesCountRequest>,
450 ) -> Result<RepositoryIssuesCountResponse, Box<dyn Error + Send>> {
451 ) -> Result<RepositoryIssuesCountResponse, Error> {
451 452 todo!()
452 453 }
453 454
454 455 fn issue_labels(
455 456 &mut self,
456 457 _request: &ValidatedUserAuthenticated<RepositoryIssueLabelsRequest>,
457 ) -> Result<RepositoryIssueLabelsResponse, Box<dyn Error + Send>> {
458 ) -> Result<RepositoryIssueLabelsResponse, Error> {
458 459 todo!()
459 460 }
460 461
461 462 fn issues(
462 463 &mut self,
463 464 _request: &ValidatedUserAuthenticated<RepositoryIssuesRequest>,
464 ) -> Result<RepositoryIssuesResponse, Box<dyn Error + Send>> {
465 ) -> Result<RepositoryIssuesResponse, Error> {
465 466 todo!()
466 467 }
467 468 }

src/backend/mod.rs

View file
@@ -2,8 +2,8 @@ pub mod git;
2 2 pub mod github;
3 3 pub mod user;
4 4
5 use anyhow::Error;
5 6 use async_trait::async_trait;
6 use std::error::Error;
7 7
8 8 use crate::{
9 9 messages::{
@@ -31,30 +31,30 @@ pub trait RepositoryBackend: IssuesBackend {
31 31 async fn create_repository(
32 32 &mut self,
33 33 request: &ValidatedUserAuthenticated<CreateRepositoryRequest>,
34 ) -> Result<CreateRepositoryResponse, Box<dyn Error + Send>>;
34 ) -> Result<CreateRepositoryResponse, Error>;
35 35 async fn repository_info(
36 36 &mut self,
37 37 request: &ValidatedUserAuthenticated<RepositoryInfoRequest>,
38 ) -> Result<RepositoryView, Box<dyn Error + Send>>;
38 ) -> Result<RepositoryView, Error>;
39 39 fn repository_file_inspect(
40 40 &mut self,
41 41 request: &ValidatedUserAuthenticated<RepositoryFileInspectRequest>,
42 ) -> Result<RepositoryFileInspectionResponse, Box<dyn Error + Send>>;
42 ) -> Result<RepositoryFileInspectionResponse, Error>;
43 43 }
44 44
45 45 pub trait IssuesBackend {
46 46 fn issues_count(
47 47 &mut self,
48 48 request: &ValidatedUserAuthenticated<RepositoryIssuesCountRequest>,
49 ) -> Result<RepositoryIssuesCountResponse, Box<dyn Error + Send>>;
49 ) -> Result<RepositoryIssuesCountResponse, Error>;
50 50 fn issue_labels(
51 51 &mut self,
52 52 request: &ValidatedUserAuthenticated<RepositoryIssueLabelsRequest>,
53 ) -> Result<RepositoryIssueLabelsResponse, Box<dyn Error + Send>>;
53 ) -> Result<RepositoryIssueLabelsResponse, Error>;
54 54 fn issues(
55 55 &mut self,
56 56 request: &ValidatedUserAuthenticated<RepositoryIssuesRequest>,
57 ) -> Result<RepositoryIssuesResponse, Box<dyn Error + Send>>;
57 ) -> Result<RepositoryIssuesResponse, Error>;
58 58 }
59 59
60 60 #[async_trait::async_trait]
@@ -62,12 +62,12 @@ pub trait AuthBackend {
62 62 async fn register(
63 63 &mut self,
64 64 request: RegisterAccountRequest,
65 ) -> Result<RegisterAccountResponse, Box<dyn Error + Send>>;
65 ) -> Result<RegisterAccountResponse, Error>;
66 66
67 67 async fn login(
68 68 &mut self,
69 69 request: AuthenticationTokenRequest,
70 ) -> Result<AuthenticationTokenResponse, Box<dyn Error + Send>>;
70 ) -> Result<AuthenticationTokenResponse, Error>;
71 71 }
72 72
73 73 #[async_trait::async_trait]
@@ -75,15 +75,12 @@ pub trait UserBackend: AuthBackend {
75 75 async fn display_name(
76 76 &mut self,
77 77 request: UserDisplayNameRequest,
78 ) -> Result<UserDisplayNameResponse, Box<dyn Error + Send>>;
78 ) -> Result<UserDisplayNameResponse, Error>;
79 79
80 80 async fn display_image(
81 81 &mut self,
82 82 request: UserDisplayImageRequest,
83 ) -> Result<UserDisplayImageResponse, Box<dyn Error + Send>>;
83 ) -> Result<UserDisplayImageResponse, Error>;
84 84
85 async fn bio(
86 &mut self,
87 request: UserBioRequest,
88 ) -> Result<UserBioResponse, Box<dyn Error + Send>>;
85 async fn bio(&mut self, request: UserBioRequest) -> Result<UserBioResponse, Error>;
89 86 }

src/backend/user.rs

View file
@@ -1,7 +1,15 @@
1 use std::{error::Error, sync::Arc};
1 use std::sync::Arc;
2 2
3 use anyhow::Error;
4
5 use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit};
3 6 use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
4 use rsa::rand_core::OsRng;
7 use base64::{engine::general_purpose::STANDARD, Engine as _};
8 use rsa::{
9 pkcs8::{EncodePrivateKey, EncodePublicKey},
10 rand_core::OsRng,
11 RsaPrivateKey, RsaPublicKey,
12 };
5 13 use sqlx::PgPool;
6 14 use tokio::sync::Mutex;
7 15
@@ -47,7 +55,7 @@ impl UserBackend for UserAuth {
47 55 async fn display_name(
48 56 &mut self,
49 57 request: UserDisplayNameRequest,
50 ) -> Result<UserDisplayNameResponse, Box<dyn std::error::Error + Send>> {
58 ) -> Result<UserDisplayNameResponse, Error> {
51 59 let db_row = sqlx::query_as!(
52 60 UserRow,
53 61 r#"SELECT * FROM users WHERE username = $1"#,
@@ -65,7 +73,7 @@ impl UserBackend for UserAuth {
65 73 async fn display_image(
66 74 &mut self,
67 75 request: UserDisplayImageRequest,
68 ) -> Result<UserDisplayImageResponse, Box<dyn std::error::Error + Send>> {
76 ) -> Result<UserDisplayImageResponse, anyhow::Error> {
69 77 let db_row = sqlx::query_as!(
70 78 UserRow,
71 79 r#"SELECT * FROM users WHERE username = $1"#,
@@ -80,10 +88,7 @@ impl UserBackend for UserAuth {
80 88 })
81 89 }
82 90
83 async fn bio(
84 &mut self,
85 request: UserBioRequest,
86 ) -> Result<UserBioResponse, Box<dyn std::error::Error + Send>> {
91 async fn bio(&mut self, request: UserBioRequest) -> Result<UserBioResponse, Error> {
87 92 let db_row = sqlx::query_as!(
88 93 UserRow,
89 94 r#"SELECT * FROM users WHERE username = $1"#,
@@ -102,7 +107,38 @@ impl AuthBackend for UserAuth {
102 107 async fn register(
103 108 &mut self,
104 109 request: RegisterAccountRequest,
105 ) -> Result<RegisterAccountResponse, Box<dyn Error + Send>> {
110 ) -> Result<RegisterAccountResponse, Error> {
111 const BITS: usize = 2048;
112
113 let private_key = RsaPrivateKey::new(&mut OsRng, BITS).unwrap();
114 let public_key = RsaPublicKey::from(&private_key);
115
116 let key = {
117 let mut target: [u8; 32] = [0; 32];
118
119 let mut index = 0;
120 let mut iterator = request.password.as_bytes().iter();
121 while index < 32 {
122 if let Some(next) = iterator.next() {
123 target[index] = *next;
124 index += 1;
125 } else {
126 iterator = request.password.as_bytes().iter();
127 }
128 }
129
130 target
131 };
132
133 let key: &Key<Aes256Gcm> = &key.into();
134 let cipher = Aes256Gcm::new(key);
135 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
136 let ciphertext = cipher
137 .encrypt(&nonce, private_key.to_pkcs8_der().unwrap().as_bytes())
138 .unwrap();
139
140 let private_key_enc = format!("{}#{}", STANDARD.encode(nonce), STANDARD.encode(ciphertext));
141
106 142 let salt = SaltString::generate(&mut OsRng);
107 143
108 144 let argon2 = Argon2::default();
@@ -114,10 +150,14 @@ impl AuthBackend for UserAuth {
114 150
115 151 let user = match sqlx::query_as!(
116 152 UserRow,
117 r#"INSERT INTO users VALUES ($1, null, $2, null, null, $3) returning *"#,
153 r#"INSERT INTO users VALUES ($1, null, $2, null, null, $3, $4, $5) returning *"#,
118 154 request.username,
119 155 "example.com",
120 156 password_hash,
157 public_key
158 .to_public_key_pem(rsa::pkcs8::LineEnding::LF)
159 .unwrap(),
160 private_key_enc
121 161 )
122 162 .fetch_one(&self.pg_pool)
123 163 .await
@@ -126,7 +166,7 @@ impl AuthBackend for UserAuth {
126 166 Err(err) => {
127 167 error!("Failed inserting into the database! {:?}", err);
128 168
129 panic!();
169 return Err(err.into());
130 170 }
131 171 };
132 172
@@ -147,7 +187,7 @@ impl AuthBackend for UserAuth {
147 187 async fn login(
148 188 &mut self,
149 189 _request: AuthenticationTokenRequest,
150 ) -> Result<AuthenticationTokenResponse, Box<dyn Error + Send>> {
190 ) -> Result<AuthenticationTokenResponse, Error> {
151 191 todo!()
152 192 }
153 193 }
@@ -160,4 +200,6 @@ struct UserRow {
160 200 pub bio: Option<String>,
161 201 pub email: Option<String>,
162 202 pub password: String,
203 pub public_key: String,
204 pub enc_private_key: Vec<u8>,
163 205 }

src/connection.rs

View file
@@ -1,6 +1,8 @@
1 1 use std::{collections::HashMap, net::SocketAddr, sync::Arc};
2 2
3 use anyhow::Error;
3 4 use futures_util::{stream::StreamExt, SinkExt};
5 use serde::Serialize;
4 6 use tokio::{
5 7 net::TcpStream,
6 8 sync::{
@@ -17,7 +19,10 @@ use crate::{
17 19 handshake::{HandshakeFinalize, HandshakeMessage, HandshakeResponse},
18 20 listener::Listeners,
19 21 messages::{
20 authentication::{AuthenticationMessage, AuthenticationRequest, TokenExtensionResponse},
22 authentication::{
23 AuthenticationMessage, AuthenticationRequest, AuthenticationResponse,
24 TokenExtensionResponse,
25 },
21 26 repository::{
22 27 RepositoryMessage, RepositoryMessageKind, RepositoryRequest, RepositoryResponse,
23 28 },
@@ -56,7 +61,7 @@ pub async fn connection_worker(
56 61 listeners: Arc<Mutex<Listeners>>,
57 62 connections: Arc<Mutex<Connections>>,
58 63 backend: Arc<Mutex<dyn RepositoryBackend + Send>>,
59 _user_backend: Arc<Mutex<dyn UserBackend + Send>>,
64 user_backend: Arc<Mutex<dyn UserBackend + Send>>,
60 65 auth_granter: Arc<Mutex<AuthenticationTokenGranter>>,
61 66 addr: SocketAddr,
62 67 ) {
@@ -108,15 +113,11 @@ pub async fn connection_worker(
108 113 version: String::from("0.1.0"),
109 114 };
110 115
111 socket
112 .send(Message::Binary(
113 serde_json::to_vec(&MessageKind::Handshake(
114 HandshakeMessage::Response(message),
115 ))
116 .unwrap(),
117 ))
118 .await
119 .unwrap();
116 let _result = send(
117 &mut socket,
118 MessageKind::Handshake(HandshakeMessage::Response(message)),
119 )
120 .await;
120 121
121 122 continue;
122 123 }
@@ -124,15 +125,11 @@ pub async fn connection_worker(
124 125 // Send HandshakeMessage::Finalize
125 126 let message = HandshakeFinalize { success: true };
126 127
127 socket
128 .send(Message::Binary(
129 serde_json::to_vec(&MessageKind::Handshake(
130 HandshakeMessage::Finalize(message),
131 ))
132 .unwrap(),
133 ))
134 .await
135 .unwrap();
128 let _result = send(
129 &mut socket,
130 MessageKind::Handshake(HandshakeMessage::Finalize(message)),
131 )
132 .await;
136 133
137 134 continue;
138 135 }
@@ -142,15 +139,11 @@ pub async fn connection_worker(
142 139 // Send HandshakeMessage::Finalize
143 140 let message = HandshakeFinalize { success: true };
144 141
145 socket
146 .send(Message::Binary(
147 serde_json::to_vec(&MessageKind::Handshake(
148 HandshakeMessage::Finalize(message),
149 ))
150 .unwrap(),
151 ))
152 .await
153 .unwrap();
142 let _result = send(
143 &mut socket,
144 MessageKind::Handshake(HandshakeMessage::Finalize(message)),
145 )
146 .await;
154 147
155 148 continue;
156 149 }
@@ -175,10 +168,7 @@ pub async fn connection_worker(
175 168 ..
176 169 }) = message
177 170 {
178 socket
179 .send(Message::Binary(serde_json::to_vec(&message).unwrap()))
180 .await
181 .unwrap();
171 let _result = send(&mut socket, message).await;
182 172 }
183 173 }
184 174 continue;
@@ -200,20 +190,16 @@ pub async fn connection_worker(
200 190 };
201 191 drop(backend);
202 192
203 socket
204 .send(Message::Binary(
205 serde_json::to_vec(&MessageKind::Repository(
206 RepositoryMessage {
207 target: repository.target.clone(),
208 command: RepositoryMessageKind::Response(
209 RepositoryResponse::CreateRepository(response),
210 ),
211 },
212 ))
213 .unwrap(),
214 ))
215 .await
216 .unwrap();
193 let _result = send(
194 &mut socket,
195 MessageKind::Repository(RepositoryMessage {
196 target: repository.target.clone(),
197 command: RepositoryMessageKind::Response(
198 RepositoryResponse::CreateRepository(response),
199 ),
200 }),
201 )
202 .await;
217 203
218 204 continue;
219 205 }
@@ -231,22 +217,17 @@ pub async fn connection_worker(
231 217 };
232 218 drop(backend);
233 219
234 socket
235 .send(Message::Binary(
236 serde_json::to_vec(&MessageKind::Repository(
237 RepositoryMessage {
238 target: repository.target.clone(),
239 command: RepositoryMessageKind::Response(
240 RepositoryResponse::RepositoryFileInspection(
241 response,
242 ),
243 ),
244 },
245 ))
246 .unwrap(),
247 ))
248 .await
249 .unwrap();
220 let _result = send(
221 &mut socket,
222 MessageKind::Repository(RepositoryMessage {
223 target: repository.target.clone(),
224 command: RepositoryMessageKind::Response(
225 RepositoryResponse::RepositoryFileInspection(response),
226 ),
227 }),
228 )
229 .await;
230
250 231 continue;
251 232 }
252 233 RepositoryRequest::RepositoryInfo(request) => {
@@ -263,20 +244,17 @@ pub async fn connection_worker(
263 244 };
264 245 drop(backend);
265 246
266 socket
267 .send(Message::Binary(
268 serde_json::to_vec(&MessageKind::Repository(
269 RepositoryMessage {
270 target: repository.target.clone(),
271 command: RepositoryMessageKind::Response(
272 RepositoryResponse::RepositoryInfo(response),
273 ),
274 },
275 ))
276 .unwrap(),
277 ))
278 .await
279 .unwrap();
247 let _result = send(
248 &mut socket,
249 MessageKind::Repository(RepositoryMessage {
250 target: repository.target.clone(),
251 command: RepositoryMessageKind::Response(
252 RepositoryResponse::RepositoryInfo(response),
253 ),
254 }),
255 )
256 .await;
257
280 258 continue;
281 259 }
282 260 RepositoryRequest::IssuesCount(request) => {
@@ -294,20 +272,17 @@ pub async fn connection_worker(
294 272 };
295 273 drop(backend);
296 274
297 socket
298 .send(Message::Binary(
299 serde_json::to_vec(&MessageKind::Repository(
300 RepositoryMessage {
301 target: repository.target.clone(),
302 command: RepositoryMessageKind::Response(
303 RepositoryResponse::IssuesCount(response),
304 ),
305 },
306 ))
307 .unwrap(),
308 ))
309 .await
310 .unwrap();
275 let _result = send(
276 &mut socket,
277 MessageKind::Repository(RepositoryMessage {
278 target: repository.target.clone(),
279 command: RepositoryMessageKind::Response(
280 RepositoryResponse::IssuesCount(response),
281 ),
282 }),
283 )
284 .await;
285
311 286 continue;
312 287 }
313 288 RepositoryRequest::IssueLabels(request) => {
@@ -324,20 +299,18 @@ pub async fn connection_worker(
324 299 }
325 300 };
326 301 drop(backend);
327 socket
328 .send(Message::Binary(
329 serde_json::to_vec(&MessageKind::Repository(
330 RepositoryMessage {
331 target: repository.target.clone(),
332 command: RepositoryMessageKind::Response(
333 RepositoryResponse::IssueLabels(response),
334 ),
335 },
336 ))
337 .unwrap(),
338 ))
339 .await
340 .unwrap();
302
303 let _result = send(
304 &mut socket,
305 MessageKind::Repository(RepositoryMessage {
306 target: repository.target.clone(),
307 command: RepositoryMessageKind::Response(
308 RepositoryResponse::IssueLabels(response),
309 ),
310 }),
311 )
312 .await;
313
341 314 continue;
342 315 }
343 316 RepositoryRequest::Issues(request) => {
@@ -355,20 +328,17 @@ pub async fn connection_worker(
355 328 };
356 329 drop(backend);
357 330
358 socket
359 .send(Message::Binary(
360 serde_json::to_vec(&MessageKind::Repository(
361 RepositoryMessage {
362 target: repository.target.clone(),
363 command: RepositoryMessageKind::Response(
364 RepositoryResponse::Issues(response),
365 ),
366 },
367 ))
368 .unwrap(),
369 ))
370 .await
371 .unwrap();
331 let _result = send(
332 &mut socket,
333 MessageKind::Repository(RepositoryMessage {
334 target: repository.target.clone(),
335 command: RepositoryMessageKind::Response(
336 RepositoryResponse::Issues(response),
337 ),
338 }),
339 )
340 .await;
341
372 342 continue;
373 343 }
374 344 },
@@ -388,15 +358,14 @@ pub async fn connection_worker(
388 358 let response = granter.token_request(token.clone()).await.unwrap();
389 359 drop(granter);
390 360
391 socket
392 .send(Message::Binary(
393 serde_json::to_vec(&MessageKind::Authentication(
394 AuthenticationMessage::Response(crate::messages::authentication::AuthenticationResponse::AuthenticationToken(response))
395 ))
396 .unwrap(),
397 ))
398 .await
399 .unwrap();
361 let _result = send(
362 &mut socket,
363 MessageKind::Authentication(AuthenticationMessage::Response(
364 AuthenticationResponse::AuthenticationToken(response),
365 )),
366 )
367 .await;
368
400 369 continue;
401 370 }
402 371 AuthenticationRequest::TokenExtension(request) => {
@@ -408,18 +377,34 @@ pub async fn connection_worker(
408 377 .unwrap_or(TokenExtensionResponse { new_token: None });
409 378 drop(granter);
410 379
411 socket
412 .send(Message::Binary(
413 serde_json::to_vec(&MessageKind::Authentication(
414 AuthenticationMessage::Response(crate::messages::authentication::AuthenticationResponse::TokenExtension(response))
415 ))
416 .unwrap(),
417 ))
418 .await
419 .unwrap();
380 let _result = send(
381 &mut socket,
382 MessageKind::Authentication(AuthenticationMessage::Response(
383 AuthenticationResponse::TokenExtension(response),
384 )),
385 )
386 .await;
387
388 continue;
389 }
390 AuthenticationRequest::RegisterAccount(request) => {
391 let request = request.inner().await.clone();
392
393 let mut user_backend = user_backend.lock().await;
394
395 let response = user_backend.register(request.clone()).await.unwrap();
396 drop(user_backend);
397
398 let _result = send(
399 &mut socket,
400 MessageKind::Authentication(AuthenticationMessage::Response(
401 AuthenticationResponse::RegisterAccount(response),
402 )),
403 )
404 .await;
405
420 406 continue;
421 407 }
422 AuthenticationRequest::RegisterAccount(_) => todo!(),
423 408 },
424 409 AuthenticationMessage::Response(_) => unreachable!(),
425 410 }
@@ -468,3 +453,14 @@ async fn send_and_get_listener(
468 453
469 454 listener
470 455 }
456
457 async fn send<T: Serialize>(
458 socket: &mut WebSocketStream<TcpStream>,
459 message: T,
460 ) -> Result<(), Error> {
461 socket
462 .send(Message::Binary(serde_json::to_vec(&message).unwrap()))
463 .await?;
464
465 Ok(())
466 }

src/main.rs

View file
@@ -1,5 +1,4 @@
1 use std::{error::Error, net::SocketAddr, str::FromStr, sync::Arc};
2
1 use anyhow::Error;
3 2 use connection::{connection_worker, Connections, RawConnection};
4 3 use giterated_daemon::{
5 4 authentication::AuthenticationTokenGranter,
@@ -9,6 +8,7 @@ use giterated_daemon::{
9 8 };
10 9 use listener::Listeners;
11 10 use sqlx::{postgres::PgConnectOptions, ConnectOptions, PgPool};
11 use std::{net::SocketAddr, str::FromStr, sync::Arc};
12 12 use tokio::{
13 13 fs::File,
14 14 io::{AsyncRead, AsyncReadExt, AsyncWrite},
@@ -22,7 +22,7 @@ use toml::Table;
22 22 extern crate tracing;
23 23
24 24 #[tokio::main]
25 async fn main() -> Result<(), Box<dyn Error>> {
25 async fn main() -> Result<(), Error> {
26 26 tracing_subscriber::fmt::init();
27 27 let mut listener = TcpListener::bind("0.0.0.0:7270").await?;
28 28 let connections: Arc<Mutex<Connections>> = Arc::default();
@@ -115,9 +115,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
115 115 }
116 116 }
117 117
118 async fn accept_stream(
119 listener: &mut TcpListener,
120 ) -> Result<(TcpStream, SocketAddr), Box<dyn Error>> {
118 async fn accept_stream(listener: &mut TcpListener) -> Result<(TcpStream, SocketAddr), Error> {
121 119 let stream = listener.accept().await?;
122 120
123 121 Ok(stream)
@@ -125,7 +123,7 @@ async fn accept_stream(
125 123
126 124 async fn accept_websocket_connection<S: AsyncRead + AsyncWrite + Unpin>(
127 125 stream: S,
128 ) -> Result<WebSocketStream<S>, Box<dyn Error>> {
126 ) -> Result<WebSocketStream<S>, Error> {
129 127 let connection = accept_async(stream).await?;
130 128
131 129 Ok(connection)

src/messages/authentication.rs

View file
@@ -2,6 +2,10 @@ use serde::{Deserialize, Serialize};
2 2
3 3 use super::InstanceAuthenticated;
4 4
5 /// An authentication message.
6 ///
7 /// View request documentation, authentication, and authorization
8 /// details in the associated type, [`AuthenticationRequest`].
5 9 #[derive(Clone, Serialize, Deserialize)]
6 10 pub enum AuthenticationMessage {
7 11 Request(AuthenticationRequest),
@@ -10,8 +14,36 @@ pub enum AuthenticationMessage {
10 14
11 15 #[derive(Clone, Serialize, Deserialize)]
12 16 pub enum AuthenticationRequest {
17 /// An account registration request.
18 ///
19 /// # Authentication
20 /// - Instance Authentication
21 /// - **ONLY ACCEPTED WHEN SAME-INSTANCE**
13 22 RegisterAccount(InstanceAuthenticated<RegisterAccountRequest>),
23
24 /// An authentication token request.
25 ///
26 /// AKA Login Request
27 ///
28 /// # Authentication
29 /// - Instance Authentication
30 /// - **ONLY ACCEPTED WHEN SAME-INSTANCE**
31 /// - Identifies the Instance to issue the token for
32 /// # Authorization
33 /// - Credentials ([`crate::backend::AuthBackend`]-based)
34 /// - Identifies the User account to issue a token for
35 /// - Decrypts user private key to issue to
14 36 AuthenticationToken(InstanceAuthenticated<AuthenticationTokenRequest>),
37
38 /// An authentication token extension request.
39 ///
40 /// # Authentication
41 /// - Instance Authentication
42 /// - **ONLY ACCEPTED WHEN SAME-INSTANCE**
43 /// - Identifies the Instance to issue the token for
44 /// # Authorization
45 /// - Token-based
46 /// - Validates authorization using token's authenticity
15 47 TokenExtension(InstanceAuthenticated<TokenExtensionRequest>),
16 48 }
17 49
@@ -22,6 +54,7 @@ pub enum AuthenticationResponse {
22 54 TokenExtension(TokenExtensionResponse),
23 55 }
24 56
57 /// See [`AuthenticationRequest::RegisterAccount`]'s documentation.
25 58 #[derive(Clone, Serialize, Deserialize)]
26 59 pub struct RegisterAccountRequest {
27 60 pub username: String,
@@ -34,6 +67,7 @@ pub struct RegisterAccountResponse {
34 67 pub token: String,
35 68 }
36 69
70 /// See [`AuthenticationRequest::AuthenticationToken`]'s documentation.
37 71 #[derive(Clone, Serialize, Deserialize)]
38 72 pub struct AuthenticationTokenRequest {
39 73 pub secret_key: String,
@@ -46,6 +80,7 @@ pub struct AuthenticationTokenResponse {
46 80 pub token: String,
47 81 }
48 82
83 /// See [`AuthenticationRequest::TokenExtension`]'s documentation.
49 84 #[derive(Clone, Serialize, Deserialize)]
50 85 pub struct TokenExtensionRequest {
51 86 pub secret_key: String,

src/messages/mod.rs

View file
@@ -1,5 +1,4 @@
1 use std::{error::Error, fmt::Debug};
2
1 use anyhow::Error;
3 2 use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation};
4 3 use rsa::{
5 4 pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey},
@@ -9,6 +8,7 @@ use rsa::{
9 8 RsaPrivateKey, RsaPublicKey,
10 9 };
11 10 use serde::{Deserialize, Serialize};
11 use std::fmt::Debug;
12 12
13 13 use crate::{
14 14 authentication::UserTokenMetadata,
@@ -66,11 +66,7 @@ where
66 66 }
67 67
68 68 impl<T: Serialize> InstanceAuthenticated<T> {
69 pub fn new(
70 message: T,
71 instance: Instance,
72 private_key: String,
73 ) -> Result<Self, Box<dyn Error>> {
69 pub fn new(message: T, instance: Instance, private_key: String) -> Result<Self, Error> {
74 70 let mut rng = rand::thread_rng();
75 71
76 72 let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key)?;
@@ -91,7 +87,7 @@ impl<T: Serialize> InstanceAuthenticated<T> {
91 87 &self.message
92 88 }
93 89
94 pub async fn validate(&self, instance: &Instance) -> Result<(), Box<dyn Error>> {
90 pub async fn validate(&self, instance: &Instance) -> Result<(), Error> {
95 91 let public_key = public_key(instance).await?;
96 92 let public_key = RsaPublicKey::from_pkcs1_pem(&public_key).unwrap();
97 93
@@ -194,7 +190,7 @@ where
194 190 }
195 191
196 192 impl<T: Serialize> UnvalidatedUserAuthenticated<T> {
197 pub fn new(message: T, token: String, private_key: String) -> Result<Self, Box<dyn Error>> {
193 pub fn new(message: T, token: String, private_key: String) -> Result<Self, Error> {
198 194 let mut rng = rand::thread_rng();
199 195
200 196 let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key)?;
@@ -215,7 +211,7 @@ impl<T: Serialize> UnvalidatedUserAuthenticated<T> {
215 211 &self.message
216 212 }
217 213
218 pub async fn validate(self) -> Result<ValidatedUserAuthenticated<T>, Box<dyn Error>> {
214 pub async fn validate(self) -> Result<ValidatedUserAuthenticated<T>, Error> {
219 215 let instance = {
220 216 let mut validation = Validation::new(Algorithm::RS256);
221 217 validation.insecure_disable_signature_validation();
@@ -258,7 +254,7 @@ impl<T: Serialize> UnvalidatedUserAuthenticated<T> {
258 254 }
259 255 }
260 256
261 async fn public_key(instance: &Instance) -> Result<String, Box<dyn Error>> {
257 async fn public_key(instance: &Instance) -> Result<String, Error> {
262 258 let key = reqwest::get(format!("https://{}/.giterated/pubkey.pem", instance.url))
263 259 .await?
264 260 .text()

src/messages/repository.rs

View file
@@ -8,6 +8,10 @@ use crate::model::{
8 8
9 9 use super::UnvalidatedUserAuthenticated;
10 10
11 /// A repository message.
12 ///
13 /// View request documentation, authentication, and authorization
14 /// details in the associated type, [`RepositoryRequest`].
11 15 #[derive(Clone, Serialize, Deserialize)]
12 16 pub struct RepositoryMessage {
13 17 pub target: Repository,
@@ -22,11 +26,74 @@ pub enum RepositoryMessageKind {
22 26
23 27 #[derive(Clone, Serialize, Deserialize)]
24 28 pub enum RepositoryRequest {
29 /// A request to create a repository.
30 ///
31 /// # Authentication
32 /// - Instance Authentication
33 /// - Used to validate User token `issued_for`
34 /// - User Authentication
35 /// - Used to source owning user
36 /// - Used to authorize user token against user's instance
37 /// # Authorization
38 /// - Instance Authorization
39 /// - Used to authorize action using User token requiring a correct `issued_for` and valid issuance from user's instance
40 /// - User Authorization
41 /// - Potential User permissions checks
25 42 CreateRepository(UnvalidatedUserAuthenticated<CreateRepositoryRequest>),
43
44 /// A request to inspect the tree of a repository.
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
26 53 RepositoryFileInspect(UnvalidatedUserAuthenticated<RepositoryFileInspectRequest>),
54
55 /// A request to get a repository's information.
56 ///
57 /// # Authentication
58 /// - Instance Authentication
59 /// - Validate request against the `issued_for` public key
60 /// - Validate User token against the user's instance's public key
61 /// # Authorization
62 /// - User Authorization
63 /// - Potential User permissions checks
27 64 RepositoryInfo(UnvalidatedUserAuthenticated<RepositoryInfoRequest>),
65
66 /// A request to get a repository's issues count.
67 ///
68 /// # Authentication
69 /// - Instance Authentication
70 /// - Validate request against the `issued_for` public key
71 /// - Validate User token against the user's instance's public key
72 /// # Authorization
73 /// - User Authorization
74 /// - Potential User permissions checks
28 75 IssuesCount(UnvalidatedUserAuthenticated<RepositoryIssuesCountRequest>),
76
77 /// A request to get a repository's issue labels.
78 ///
79 /// # Authentication
80 /// - Instance Authentication
81 /// - Validate request against the `issued_for` public key
82 /// - Validate User token against the user's instance's public key
83 /// # Authorization
84 /// - User Authorization
85 /// - Potential User permissions checks
29 86 IssueLabels(UnvalidatedUserAuthenticated<RepositoryIssueLabelsRequest>),
87
88 /// A request to get a repository's issues.
89 ///
90 /// # Authentication
91 /// - Instance Authentication
92 /// - Validate request against the `issued_for` public key
93 /// - Validate User token against the user's instance's public key
94 /// # Authorization
95 /// - User Authorization
96 /// - Potential User permissions checks
30 97 Issues(UnvalidatedUserAuthenticated<RepositoryIssuesRequest>),
31 98 }
32 99
@@ -40,6 +107,7 @@ pub enum RepositoryResponse {
40 107 Issues(RepositoryIssuesResponse),
41 108 }
42 109
110 /// See [`RepositoryRequest::CreateRepository`]'s documentation.
43 111 #[derive(Clone, Serialize, Deserialize)]
44 112 pub struct CreateRepositoryRequest {
45 113 pub name: String,
@@ -55,6 +123,7 @@ pub enum CreateRepositoryResponse {
55 123 Failed,
56 124 }
57 125
126 /// See [`RepositoryRequest::RepositoryFileInspect`]'s documentation.
58 127 #[derive(Clone, Serialize, Deserialize)]
59 128 pub struct RepositoryFileInspectRequest {
60 129 pub path: RepositoryTreeEntry,
@@ -74,6 +143,7 @@ pub enum RepositoryFileInspectionResponse {
74 143 },
75 144 }
76 145
146 /// See [`RepositoryRequest::IssuesCount`]'s documentation.
77 147 #[derive(Clone, Serialize, Deserialize)]
78 148 pub struct RepositoryIssuesCountRequest;
79 149
@@ -82,6 +152,7 @@ pub struct RepositoryIssuesCountResponse {
82 152 pub count: u64,
83 153 }
84 154
155 /// See [`RepositoryRequest::IssueLabels`]'s documentation.
85 156 #[derive(Clone, Serialize, Deserialize)]
86 157 pub struct RepositoryIssueLabelsRequest;
87 158
@@ -96,6 +167,7 @@ pub struct IssueLabel {
96 167 pub color: String,
97 168 }
98 169
170 /// See [`RepositoryRequest::Issues`]'s documentation.
99 171 #[derive(Clone, Serialize, Deserialize)]
100 172 pub struct RepositoryIssuesRequest;
101 173

src/model/instance.rs

View file
@@ -7,6 +7,23 @@ pub struct InstanceMeta {
7 7 pub public_key: String,
8 8 }
9 9
10 /// An instance, defined by the URL it can be reached at.
11 ///
12 /// # Textual Format
13 /// An instance's textual format is its URL.
14 ///
15 /// ## Examples
16 /// For the instance `giterated.dev`, the following [`Instance`] initialization
17 /// would be valid:
18 ///
19 /// ```
20 /// let instance = Instance {
21 /// url: String::from("giterated.dev")
22 /// };
23 ///
24 /// // This is correct
25 /// assert_eq!(Instance::from_str("giterated.dev"), instance);
26 /// ```
10 27 #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
11 28 pub struct Instance {
12 29 pub url: String,

src/model/mod.rs

View file
@@ -1,3 +1,8 @@
1 //! # Giterated Network Data Model Types
2 //!
3 //! All network data model types that are not directly associated with
4 //! individual requests or responses.
5
1 6 pub mod instance;
2 7 pub mod repository;
3 8 pub mod user;

src/model/repository.rs

View file
@@ -4,6 +4,29 @@ use serde::{Deserialize, Serialize};
4 4
5 5 use super::{instance::Instance, user::User};
6 6
7 /// A repository, defined by the instance it exists on along with
8 /// its owner and name.
9 ///
10 /// # Textual Format
11 /// A repository's textual reference is defined as:
12 ///
13 /// `{owner: User}/{name: String}@{instance: Instance}`
14 ///
15 /// # Examples
16 /// For the repository named `foo` owned by `barson:giterated.dev` on the instance
17 /// `giterated.dev`, the following [`Repository`] initialization would
18 /// be valid:
19 ///
20 /// ```
21 /// let repository = Repository {
22 /// owner: User::from_str("barson:giterated.dev").unwrap(),
23 /// name: String::from("foo"),
24 /// instance: Instance::from_str("giterated.dev").unwrap()
25 /// };
26 ///
27 /// // This is correct
28 /// assert_eq!(Repository::from_str("barson:giterated.dev/[email protected]").unwrap(), repository);
29 /// ```
7 30 #[derive(Hash, Clone, Serialize, Deserialize)]
8 31 pub struct Repository {
9 32 pub owner: User,

src/model/user.rs

View file
@@ -5,6 +5,26 @@ use serde::{Deserialize, Serialize};
5 5
6 6 use super::instance::Instance;
7 7
8 /// A user, defined by its username and instance.
9 ///
10 /// # Textual Format
11 /// A user's textual reference is defined as:
12 ///
13 /// `{username: String}:{instance: Instance}`
14 ///
15 /// # Examples
16 /// For the user with the username `barson` and the instance `giterated.dev`,
17 /// the following [`User`] initialization would be valid:
18 ///
19 /// ```
20 /// let user = User {
21 /// username: String::from("barson"),
22 /// instance: Instance::from_str("giterated.dev").unwrap()
23 /// };
24 ///
25 /// // This is correct
26 /// assert_eq!(User::from_str("barson:giterated.dev").unwrap(), user);
27 /// ```
8 28 #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
9 29 pub struct User {
10 30 pub username: String,