Add docs
parent: tbd commit: 51aad53
Showing 19 changed files with 593 insertions and 257 deletions
.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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, |