diff --git a/.sqlx/query-52f8fb6168788228527ff20a70de2a4f2ad2e848852895a005db063bd0f60807.json b/.sqlx/query-52f8fb6168788228527ff20a70de2a4f2ad2e848852895a005db063bd0f60807.json deleted file mode 100644 index 16d2b53..0000000 --- a/.sqlx/query-52f8fb6168788228527ff20a70de2a4f2ad2e848852895a005db063bd0f60807.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users VALUES ($1, null, $2, null, null, $3) returning *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "display_name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "image_url", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "bio", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "password", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [ - false, - true, - false, - true, - true, - false - ] - }, - "hash": "52f8fb6168788228527ff20a70de2a4f2ad2e848852895a005db063bd0f60807" -} diff --git a/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json b/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json index 054209e..5bcf2df 100644 --- a/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json +++ b/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json @@ -32,6 +32,16 @@ "ordinal": 5, "name": "password", "type_info": "Text" + }, + { + "ordinal": 6, + "name": "public_key", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "enc_private_key", + "type_info": "Text" } ], "parameters": { @@ -45,6 +55,8 @@ false, true, true, + false, + false, false ] }, diff --git a/.sqlx/query-80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f.json b/.sqlx/query-80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f.json new file mode 100644 index 0000000..7e4538d --- /dev/null +++ b/.sqlx/query-80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f.json @@ -0,0 +1,68 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO users VALUES ($1, null, $2, null, null, $3, $4, $5) returning *", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "image_url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "bio", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "password", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "public_key", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "enc_private_key", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + false, + true, + false, + true, + true, + false, + false, + false + ] + }, + "hash": "80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f" +} diff --git a/Cargo.lock b/Cargo.lock index dcd04d8..3c1c86f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "ahash" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -211,6 +246,16 @@ dependencies = [ ] [[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] name = "const-oid" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -282,10 +327,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] [[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] name = "data-encoding" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -542,6 +597,16 @@ dependencies = [ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -566,9 +631,11 @@ dependencies = [ name = "giterated-daemon" version = "0.1.0" dependencies = [ + "aes-gcm", "anyhow", "argon2", "async-trait", + "base64 0.21.3", "chrono", "futures-util", "git2", @@ -805,6 +872,15 @@ dependencies = [ ] [[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] name = "ipnet" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1121,6 +1197,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] name = "openssl" version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1294,6 +1376,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2255,6 +2349,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index e9acb69..ac1acc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,14 @@ futures-util = "*" serde = { version = "1", features = [ "derive" ]} serde_json = "1.0" tracing-subscriber = "0.3" +base64 = "0.21.3" jsonwebtoken = { version = "*", features = ["use_pem"]} log = "*" rand = "*" rsa = {version = "0.9", features = ["sha2"]} reqwest = "*" argon2 = "*" +aes-gcm = "0.10.2" toml = { version = "0.7" } diff --git a/migrations/20230829104151_create_users.sql b/migrations/20230829104151_create_users.sql index 86224aa..31eecf8 100644 --- a/migrations/20230829104151_create_users.sql +++ b/migrations/20230829104151_create_users.sql @@ -7,7 +7,9 @@ CREATE TABLE IF NOT EXISTS users image_url TEXT NOT NULL, bio TEXT, email TEXT, - password TEXT NOT NULL + password TEXT NOT NULL, + public_key TEXT NOT NULL, + enc_private_key TEXT NOT NULL ); CREATE UNIQUE INDEX unique_username ON users (username); \ No newline at end of file diff --git a/src/authentication.rs b/src/authentication.rs index 17f6f46..beaa3ef 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -1,7 +1,7 @@ -use std::{error::Error, time::SystemTime}; - +use anyhow::Error; use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, TokenData, Validation}; use serde::{Deserialize, Serialize}; +use std::time::SystemTime; use tokio::{fs::File, io::AsyncReadExt}; use toml::Table; @@ -70,7 +70,7 @@ impl AuthenticationTokenGranter { pub async fn token_request( &mut self, raw_request: InstanceAuthenticated, - ) -> Result> { + ) -> Result { let request = raw_request.inner().await; info!("Ensuring token request is from the same instance..."); @@ -131,7 +131,7 @@ impl AuthenticationTokenGranter { pub async fn extension_request( &mut self, raw_request: InstanceAuthenticated, - ) -> Result> { + ) -> Result { let request = raw_request.inner().await; // let server_public_key = { @@ -213,7 +213,7 @@ impl AuthenticationTokenGranter { } } -async fn public_key(instance: &Instance) -> Result> { +async fn public_key(instance: &Instance) -> Result { let key = reqwest::get(format!("https://{}/.giterated/pubkey.pem", instance.url)) .await? .text() diff --git a/src/backend/git.rs b/src/backend/git.rs index a4d4c9a..901afcb 100644 --- a/src/backend/git.rs +++ b/src/backend/git.rs @@ -1,7 +1,7 @@ +use anyhow::Error; use async_trait::async_trait; use git2::ObjectType; use sqlx::PgPool; -use std::error::Error; use std::path::{Path, PathBuf}; use thiserror::Error; @@ -203,7 +203,7 @@ impl RepositoryBackend for GitBackend { async fn create_repository( &mut self, raw_request: &ValidatedUserAuthenticated, - ) -> Result> { + ) -> Result { let request = raw_request.inner().await; // let public_key = public_key(&Instance { @@ -275,7 +275,7 @@ impl RepositoryBackend for GitBackend { .delete_by_owner_user_name(&request.owner, request.name.as_str()) .await { - return Err(Box::new(err)); + return Err(Box::new(err).into()); } // ??? @@ -289,7 +289,7 @@ impl RepositoryBackend for GitBackend { &mut self, // TODO: Allow non-authenticated??? raw_request: &ValidatedUserAuthenticated, - ) -> Result> { + ) -> Result { let request = raw_request.inner().await; let repository = match self @@ -301,19 +301,20 @@ impl RepositoryBackend for GitBackend { .await { Ok(repository) => repository, - Err(err) => return Err(Box::new(err)), + Err(err) => return Err(Box::new(err).into()), }; if !repository.can_user_view_repository(Some(&raw_request.user)) { return Err(Box::new(GitBackendError::RepositoryNotFound { owner_user: request.repository.owner.to_string(), name: request.repository.name.clone(), - })); + }) + .into()); } let git = match repository.open_git2_repository(&self.repository_folder) { Ok(git) => git, - Err(err) => return Err(Box::new(err)), + Err(err) => return Err(Box::new(err).into()), }; let rev_name = match &request.rev { @@ -341,7 +342,7 @@ impl RepositoryBackend for GitBackend { .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) { Ok(reference) => reference.name().unwrap().to_string(), - Err(err) => return Err(Box::new(err)), + Err(err) => return Err(Box::new(err).into()), } } }; @@ -352,7 +353,7 @@ impl RepositoryBackend for GitBackend { .map_err(|_| GitBackendError::RefNotFound(rev_name.to_string())) { Ok(rev) => rev, - Err(err) => return Err(Box::new(err)), + Err(err) => return Err(Box::new(err).into()), }; let commit = rev.as_commit().unwrap(); @@ -371,7 +372,7 @@ impl RepositoryBackend for GitBackend { .map_err(|_| GitBackendError::PathNotFound(path.to_string())) { Ok(entry) => entry, - Err(err) => return Err(Box::new(err)), + Err(err) => return Err(Box::new(err).into()), }; // Turn the entry into a git tree entry.to_object(&git).unwrap().as_tree().unwrap().clone() @@ -438,7 +439,7 @@ impl RepositoryBackend for GitBackend { fn repository_file_inspect( &mut self, _request: &ValidatedUserAuthenticated, - ) -> Result> { + ) -> Result { todo!() } } @@ -447,21 +448,21 @@ impl IssuesBackend for GitBackend { fn issues_count( &mut self, _request: &ValidatedUserAuthenticated, - ) -> Result> { + ) -> Result { todo!() } fn issue_labels( &mut self, _request: &ValidatedUserAuthenticated, - ) -> Result> { + ) -> Result { todo!() } fn issues( &mut self, _request: &ValidatedUserAuthenticated, - ) -> Result> { + ) -> Result { todo!() } } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index f3fc640..0e13756 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -2,8 +2,8 @@ pub mod git; pub mod github; pub mod user; +use anyhow::Error; use async_trait::async_trait; -use std::error::Error; use crate::{ messages::{ @@ -31,30 +31,30 @@ pub trait RepositoryBackend: IssuesBackend { async fn create_repository( &mut self, request: &ValidatedUserAuthenticated, - ) -> Result>; + ) -> Result; async fn repository_info( &mut self, request: &ValidatedUserAuthenticated, - ) -> Result>; + ) -> Result; fn repository_file_inspect( &mut self, request: &ValidatedUserAuthenticated, - ) -> Result>; + ) -> Result; } pub trait IssuesBackend { fn issues_count( &mut self, request: &ValidatedUserAuthenticated, - ) -> Result>; + ) -> Result; fn issue_labels( &mut self, request: &ValidatedUserAuthenticated, - ) -> Result>; + ) -> Result; fn issues( &mut self, request: &ValidatedUserAuthenticated, - ) -> Result>; + ) -> Result; } #[async_trait::async_trait] @@ -62,12 +62,12 @@ pub trait AuthBackend { async fn register( &mut self, request: RegisterAccountRequest, - ) -> Result>; + ) -> Result; async fn login( &mut self, request: AuthenticationTokenRequest, - ) -> Result>; + ) -> Result; } #[async_trait::async_trait] @@ -75,15 +75,12 @@ pub trait UserBackend: AuthBackend { async fn display_name( &mut self, request: UserDisplayNameRequest, - ) -> Result>; + ) -> Result; async fn display_image( &mut self, request: UserDisplayImageRequest, - ) -> Result>; + ) -> Result; - async fn bio( - &mut self, - request: UserBioRequest, - ) -> Result>; + async fn bio(&mut self, request: UserBioRequest) -> Result; } diff --git a/src/backend/user.rs b/src/backend/user.rs index de655c9..912c7b4 100644 --- a/src/backend/user.rs +++ b/src/backend/user.rs @@ -1,7 +1,15 @@ -use std::{error::Error, sync::Arc}; +use std::sync::Arc; +use anyhow::Error; + +use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit}; use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; -use rsa::rand_core::OsRng; +use base64::{engine::general_purpose::STANDARD, Engine as _}; +use rsa::{ + pkcs8::{EncodePrivateKey, EncodePublicKey}, + rand_core::OsRng, + RsaPrivateKey, RsaPublicKey, +}; use sqlx::PgPool; use tokio::sync::Mutex; @@ -47,7 +55,7 @@ impl UserBackend for UserAuth { async fn display_name( &mut self, request: UserDisplayNameRequest, - ) -> Result> { + ) -> Result { let db_row = sqlx::query_as!( UserRow, r#"SELECT * FROM users WHERE username = $1"#, @@ -65,7 +73,7 @@ impl UserBackend for UserAuth { async fn display_image( &mut self, request: UserDisplayImageRequest, - ) -> Result> { + ) -> Result { let db_row = sqlx::query_as!( UserRow, r#"SELECT * FROM users WHERE username = $1"#, @@ -80,10 +88,7 @@ impl UserBackend for UserAuth { }) } - async fn bio( - &mut self, - request: UserBioRequest, - ) -> Result> { + async fn bio(&mut self, request: UserBioRequest) -> Result { let db_row = sqlx::query_as!( UserRow, r#"SELECT * FROM users WHERE username = $1"#, @@ -102,7 +107,38 @@ impl AuthBackend for UserAuth { async fn register( &mut self, request: RegisterAccountRequest, - ) -> Result> { + ) -> Result { + const BITS: usize = 2048; + + let private_key = RsaPrivateKey::new(&mut OsRng, BITS).unwrap(); + let public_key = RsaPublicKey::from(&private_key); + + let key = { + let mut target: [u8; 32] = [0; 32]; + + let mut index = 0; + let mut iterator = request.password.as_bytes().iter(); + while index < 32 { + if let Some(next) = iterator.next() { + target[index] = *next; + index += 1; + } else { + iterator = request.password.as_bytes().iter(); + } + } + + target + }; + + let key: &Key = &key.into(); + let cipher = Aes256Gcm::new(key); + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + let ciphertext = cipher + .encrypt(&nonce, private_key.to_pkcs8_der().unwrap().as_bytes()) + .unwrap(); + + let private_key_enc = format!("{}#{}", STANDARD.encode(nonce), STANDARD.encode(ciphertext)); + let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); @@ -114,10 +150,14 @@ impl AuthBackend for UserAuth { let user = match sqlx::query_as!( UserRow, - r#"INSERT INTO users VALUES ($1, null, $2, null, null, $3) returning *"#, + r#"INSERT INTO users VALUES ($1, null, $2, null, null, $3, $4, $5) returning *"#, request.username, "example.com", password_hash, + public_key + .to_public_key_pem(rsa::pkcs8::LineEnding::LF) + .unwrap(), + private_key_enc ) .fetch_one(&self.pg_pool) .await @@ -126,7 +166,7 @@ impl AuthBackend for UserAuth { Err(err) => { error!("Failed inserting into the database! {:?}", err); - panic!(); + return Err(err.into()); } }; @@ -147,7 +187,7 @@ impl AuthBackend for UserAuth { async fn login( &mut self, _request: AuthenticationTokenRequest, - ) -> Result> { + ) -> Result { todo!() } } @@ -160,4 +200,6 @@ struct UserRow { pub bio: Option, pub email: Option, pub password: String, + pub public_key: String, + pub enc_private_key: Vec, } diff --git a/src/connection.rs b/src/connection.rs index 5fee48a..ce63554 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,6 +1,8 @@ use std::{collections::HashMap, net::SocketAddr, sync::Arc}; +use anyhow::Error; use futures_util::{stream::StreamExt, SinkExt}; +use serde::Serialize; use tokio::{ net::TcpStream, sync::{ @@ -17,7 +19,10 @@ use crate::{ handshake::{HandshakeFinalize, HandshakeMessage, HandshakeResponse}, listener::Listeners, messages::{ - authentication::{AuthenticationMessage, AuthenticationRequest, TokenExtensionResponse}, + authentication::{ + AuthenticationMessage, AuthenticationRequest, AuthenticationResponse, + TokenExtensionResponse, + }, repository::{ RepositoryMessage, RepositoryMessageKind, RepositoryRequest, RepositoryResponse, }, @@ -56,7 +61,7 @@ pub async fn connection_worker( listeners: Arc>, connections: Arc>, backend: Arc>, - _user_backend: Arc>, + user_backend: Arc>, auth_granter: Arc>, addr: SocketAddr, ) { @@ -108,15 +113,11 @@ pub async fn connection_worker( version: String::from("0.1.0"), }; - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Handshake( - HandshakeMessage::Response(message), - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Handshake(HandshakeMessage::Response(message)), + ) + .await; continue; } @@ -124,15 +125,11 @@ pub async fn connection_worker( // Send HandshakeMessage::Finalize let message = HandshakeFinalize { success: true }; - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Handshake( - HandshakeMessage::Finalize(message), - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Handshake(HandshakeMessage::Finalize(message)), + ) + .await; continue; } @@ -142,15 +139,11 @@ pub async fn connection_worker( // Send HandshakeMessage::Finalize let message = HandshakeFinalize { success: true }; - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Handshake( - HandshakeMessage::Finalize(message), - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Handshake(HandshakeMessage::Finalize(message)), + ) + .await; continue; } @@ -175,10 +168,7 @@ pub async fn connection_worker( .. }) = message { - socket - .send(Message::Binary(serde_json::to_vec(&message).unwrap())) - .await - .unwrap(); + let _result = send(&mut socket, message).await; } } continue; @@ -200,20 +190,16 @@ pub async fn connection_worker( }; drop(backend); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Repository( - RepositoryMessage { - target: repository.target.clone(), - command: RepositoryMessageKind::Response( - RepositoryResponse::CreateRepository(response), - ), - }, - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Repository(RepositoryMessage { + target: repository.target.clone(), + command: RepositoryMessageKind::Response( + RepositoryResponse::CreateRepository(response), + ), + }), + ) + .await; continue; } @@ -231,22 +217,17 @@ pub async fn connection_worker( }; drop(backend); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Repository( - RepositoryMessage { - target: repository.target.clone(), - command: RepositoryMessageKind::Response( - RepositoryResponse::RepositoryFileInspection( - response, - ), - ), - }, - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Repository(RepositoryMessage { + target: repository.target.clone(), + command: RepositoryMessageKind::Response( + RepositoryResponse::RepositoryFileInspection(response), + ), + }), + ) + .await; + continue; } RepositoryRequest::RepositoryInfo(request) => { @@ -263,20 +244,17 @@ pub async fn connection_worker( }; drop(backend); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Repository( - RepositoryMessage { - target: repository.target.clone(), - command: RepositoryMessageKind::Response( - RepositoryResponse::RepositoryInfo(response), - ), - }, - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Repository(RepositoryMessage { + target: repository.target.clone(), + command: RepositoryMessageKind::Response( + RepositoryResponse::RepositoryInfo(response), + ), + }), + ) + .await; + continue; } RepositoryRequest::IssuesCount(request) => { @@ -294,20 +272,17 @@ pub async fn connection_worker( }; drop(backend); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Repository( - RepositoryMessage { - target: repository.target.clone(), - command: RepositoryMessageKind::Response( - RepositoryResponse::IssuesCount(response), - ), - }, - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Repository(RepositoryMessage { + target: repository.target.clone(), + command: RepositoryMessageKind::Response( + RepositoryResponse::IssuesCount(response), + ), + }), + ) + .await; + continue; } RepositoryRequest::IssueLabels(request) => { @@ -324,20 +299,18 @@ pub async fn connection_worker( } }; drop(backend); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Repository( - RepositoryMessage { - target: repository.target.clone(), - command: RepositoryMessageKind::Response( - RepositoryResponse::IssueLabels(response), - ), - }, - )) - .unwrap(), - )) - .await - .unwrap(); + + let _result = send( + &mut socket, + MessageKind::Repository(RepositoryMessage { + target: repository.target.clone(), + command: RepositoryMessageKind::Response( + RepositoryResponse::IssueLabels(response), + ), + }), + ) + .await; + continue; } RepositoryRequest::Issues(request) => { @@ -355,20 +328,17 @@ pub async fn connection_worker( }; drop(backend); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Repository( - RepositoryMessage { - target: repository.target.clone(), - command: RepositoryMessageKind::Response( - RepositoryResponse::Issues(response), - ), - }, - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Repository(RepositoryMessage { + target: repository.target.clone(), + command: RepositoryMessageKind::Response( + RepositoryResponse::Issues(response), + ), + }), + ) + .await; + continue; } }, @@ -388,15 +358,14 @@ pub async fn connection_worker( let response = granter.token_request(token.clone()).await.unwrap(); drop(granter); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Authentication( - AuthenticationMessage::Response(crate::messages::authentication::AuthenticationResponse::AuthenticationToken(response)) - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Authentication(AuthenticationMessage::Response( + AuthenticationResponse::AuthenticationToken(response), + )), + ) + .await; + continue; } AuthenticationRequest::TokenExtension(request) => { @@ -408,18 +377,34 @@ pub async fn connection_worker( .unwrap_or(TokenExtensionResponse { new_token: None }); drop(granter); - socket - .send(Message::Binary( - serde_json::to_vec(&MessageKind::Authentication( - AuthenticationMessage::Response(crate::messages::authentication::AuthenticationResponse::TokenExtension(response)) - )) - .unwrap(), - )) - .await - .unwrap(); + let _result = send( + &mut socket, + MessageKind::Authentication(AuthenticationMessage::Response( + AuthenticationResponse::TokenExtension(response), + )), + ) + .await; + + continue; + } + AuthenticationRequest::RegisterAccount(request) => { + let request = request.inner().await.clone(); + + let mut user_backend = user_backend.lock().await; + + let response = user_backend.register(request.clone()).await.unwrap(); + drop(user_backend); + + let _result = send( + &mut socket, + MessageKind::Authentication(AuthenticationMessage::Response( + AuthenticationResponse::RegisterAccount(response), + )), + ) + .await; + continue; } - AuthenticationRequest::RegisterAccount(_) => todo!(), }, AuthenticationMessage::Response(_) => unreachable!(), } @@ -468,3 +453,14 @@ async fn send_and_get_listener( listener } + +async fn send( + socket: &mut WebSocketStream, + message: T, +) -> Result<(), Error> { + socket + .send(Message::Binary(serde_json::to_vec(&message).unwrap())) + .await?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 2c9b288..5963c41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ -use std::{error::Error, net::SocketAddr, str::FromStr, sync::Arc}; - +use anyhow::Error; use connection::{connection_worker, Connections, RawConnection}; use giterated_daemon::{ authentication::AuthenticationTokenGranter, @@ -9,6 +8,7 @@ use giterated_daemon::{ }; use listener::Listeners; use sqlx::{postgres::PgConnectOptions, ConnectOptions, PgPool}; +use std::{net::SocketAddr, str::FromStr, sync::Arc}; use tokio::{ fs::File, io::{AsyncRead, AsyncReadExt, AsyncWrite}, @@ -22,7 +22,7 @@ use toml::Table; extern crate tracing; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Error> { tracing_subscriber::fmt::init(); let mut listener = TcpListener::bind("0.0.0.0:7270").await?; let connections: Arc> = Arc::default(); @@ -115,9 +115,7 @@ async fn main() -> Result<(), Box> { } } -async fn accept_stream( - listener: &mut TcpListener, -) -> Result<(TcpStream, SocketAddr), Box> { +async fn accept_stream(listener: &mut TcpListener) -> Result<(TcpStream, SocketAddr), Error> { let stream = listener.accept().await?; Ok(stream) @@ -125,7 +123,7 @@ async fn accept_stream( async fn accept_websocket_connection( stream: S, -) -> Result, Box> { +) -> Result, Error> { let connection = accept_async(stream).await?; Ok(connection) diff --git a/src/messages/authentication.rs b/src/messages/authentication.rs index 8311861..a1b4c58 100644 --- a/src/messages/authentication.rs +++ b/src/messages/authentication.rs @@ -2,6 +2,10 @@ use serde::{Deserialize, Serialize}; use super::InstanceAuthenticated; +/// An authentication message. +/// +/// View request documentation, authentication, and authorization +/// details in the associated type, [`AuthenticationRequest`]. #[derive(Clone, Serialize, Deserialize)] pub enum AuthenticationMessage { Request(AuthenticationRequest), @@ -10,8 +14,36 @@ pub enum AuthenticationMessage { #[derive(Clone, Serialize, Deserialize)] pub enum AuthenticationRequest { + /// An account registration request. + /// + /// # Authentication + /// - Instance Authentication + /// - **ONLY ACCEPTED WHEN SAME-INSTANCE** RegisterAccount(InstanceAuthenticated), + + /// An authentication token request. + /// + /// AKA Login Request + /// + /// # Authentication + /// - Instance Authentication + /// - **ONLY ACCEPTED WHEN SAME-INSTANCE** + /// - Identifies the Instance to issue the token for + /// # Authorization + /// - Credentials ([`crate::backend::AuthBackend`]-based) + /// - Identifies the User account to issue a token for + /// - Decrypts user private key to issue to AuthenticationToken(InstanceAuthenticated), + + /// An authentication token extension request. + /// + /// # Authentication + /// - Instance Authentication + /// - **ONLY ACCEPTED WHEN SAME-INSTANCE** + /// - Identifies the Instance to issue the token for + /// # Authorization + /// - Token-based + /// - Validates authorization using token's authenticity TokenExtension(InstanceAuthenticated), } @@ -22,6 +54,7 @@ pub enum AuthenticationResponse { TokenExtension(TokenExtensionResponse), } +/// See [`AuthenticationRequest::RegisterAccount`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct RegisterAccountRequest { pub username: String, @@ -34,6 +67,7 @@ pub struct RegisterAccountResponse { pub token: String, } +/// See [`AuthenticationRequest::AuthenticationToken`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct AuthenticationTokenRequest { pub secret_key: String, @@ -46,6 +80,7 @@ pub struct AuthenticationTokenResponse { pub token: String, } +/// See [`AuthenticationRequest::TokenExtension`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct TokenExtensionRequest { pub secret_key: String, diff --git a/src/messages/mod.rs b/src/messages/mod.rs index 551371d..a4032cc 100644 --- a/src/messages/mod.rs +++ b/src/messages/mod.rs @@ -1,5 +1,4 @@ -use std::{error::Error, fmt::Debug}; - +use anyhow::Error; use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; use rsa::{ pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey}, @@ -9,6 +8,7 @@ use rsa::{ RsaPrivateKey, RsaPublicKey, }; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; use crate::{ authentication::UserTokenMetadata, @@ -66,11 +66,7 @@ where } impl InstanceAuthenticated { - pub fn new( - message: T, - instance: Instance, - private_key: String, - ) -> Result> { + pub fn new(message: T, instance: Instance, private_key: String) -> Result { let mut rng = rand::thread_rng(); let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key)?; @@ -91,7 +87,7 @@ impl InstanceAuthenticated { &self.message } - pub async fn validate(&self, instance: &Instance) -> Result<(), Box> { + pub async fn validate(&self, instance: &Instance) -> Result<(), Error> { let public_key = public_key(instance).await?; let public_key = RsaPublicKey::from_pkcs1_pem(&public_key).unwrap(); @@ -194,7 +190,7 @@ where } impl UnvalidatedUserAuthenticated { - pub fn new(message: T, token: String, private_key: String) -> Result> { + pub fn new(message: T, token: String, private_key: String) -> Result { let mut rng = rand::thread_rng(); let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key)?; @@ -215,7 +211,7 @@ impl UnvalidatedUserAuthenticated { &self.message } - pub async fn validate(self) -> Result, Box> { + pub async fn validate(self) -> Result, Error> { let instance = { let mut validation = Validation::new(Algorithm::RS256); validation.insecure_disable_signature_validation(); @@ -258,7 +254,7 @@ impl UnvalidatedUserAuthenticated { } } -async fn public_key(instance: &Instance) -> Result> { +async fn public_key(instance: &Instance) -> Result { let key = reqwest::get(format!("https://{}/.giterated/pubkey.pem", instance.url)) .await? .text() diff --git a/src/messages/repository.rs b/src/messages/repository.rs index cc29744..da80adb 100644 --- a/src/messages/repository.rs +++ b/src/messages/repository.rs @@ -8,6 +8,10 @@ use crate::model::{ use super::UnvalidatedUserAuthenticated; +/// A repository message. +/// +/// View request documentation, authentication, and authorization +/// details in the associated type, [`RepositoryRequest`]. #[derive(Clone, Serialize, Deserialize)] pub struct RepositoryMessage { pub target: Repository, @@ -22,11 +26,74 @@ pub enum RepositoryMessageKind { #[derive(Clone, Serialize, Deserialize)] pub enum RepositoryRequest { + /// A request to create a repository. + /// + /// # Authentication + /// - Instance Authentication + /// - Used to validate User token `issued_for` + /// - User Authentication + /// - Used to source owning user + /// - Used to authorize user token against user's instance + /// # Authorization + /// - Instance Authorization + /// - Used to authorize action using User token requiring a correct `issued_for` and valid issuance from user's instance + /// - User Authorization + /// - Potential User permissions checks CreateRepository(UnvalidatedUserAuthenticated), + + /// A request to inspect the tree of a repository. + /// + /// # Authentication + /// - Instance Authentication + /// - Validate request against the `issued_for` public key + /// - Validate User token against the user's instance's public key + /// # Authorization + /// - User Authorization + /// - Potential User permissions checks RepositoryFileInspect(UnvalidatedUserAuthenticated), + + /// A request to get a repository's information. + /// + /// # Authentication + /// - Instance Authentication + /// - Validate request against the `issued_for` public key + /// - Validate User token against the user's instance's public key + /// # Authorization + /// - User Authorization + /// - Potential User permissions checks RepositoryInfo(UnvalidatedUserAuthenticated), + + /// A request to get a repository's issues count. + /// + /// # Authentication + /// - Instance Authentication + /// - Validate request against the `issued_for` public key + /// - Validate User token against the user's instance's public key + /// # Authorization + /// - User Authorization + /// - Potential User permissions checks IssuesCount(UnvalidatedUserAuthenticated), + + /// A request to get a repository's issue labels. + /// + /// # Authentication + /// - Instance Authentication + /// - Validate request against the `issued_for` public key + /// - Validate User token against the user's instance's public key + /// # Authorization + /// - User Authorization + /// - Potential User permissions checks IssueLabels(UnvalidatedUserAuthenticated), + + /// A request to get a repository's issues. + /// + /// # Authentication + /// - Instance Authentication + /// - Validate request against the `issued_for` public key + /// - Validate User token against the user's instance's public key + /// # Authorization + /// - User Authorization + /// - Potential User permissions checks Issues(UnvalidatedUserAuthenticated), } @@ -40,6 +107,7 @@ pub enum RepositoryResponse { Issues(RepositoryIssuesResponse), } +/// See [`RepositoryRequest::CreateRepository`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct CreateRepositoryRequest { pub name: String, @@ -55,6 +123,7 @@ pub enum CreateRepositoryResponse { Failed, } +/// See [`RepositoryRequest::RepositoryFileInspect`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct RepositoryFileInspectRequest { pub path: RepositoryTreeEntry, @@ -74,6 +143,7 @@ pub enum RepositoryFileInspectionResponse { }, } +/// See [`RepositoryRequest::IssuesCount`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct RepositoryIssuesCountRequest; @@ -82,6 +152,7 @@ pub struct RepositoryIssuesCountResponse { pub count: u64, } +/// See [`RepositoryRequest::IssueLabels`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct RepositoryIssueLabelsRequest; @@ -96,6 +167,7 @@ pub struct IssueLabel { pub color: String, } +/// See [`RepositoryRequest::Issues`]'s documentation. #[derive(Clone, Serialize, Deserialize)] pub struct RepositoryIssuesRequest; diff --git a/src/model/instance.rs b/src/model/instance.rs index 571ce11..26587bd 100644 --- a/src/model/instance.rs +++ b/src/model/instance.rs @@ -7,6 +7,23 @@ pub struct InstanceMeta { pub public_key: String, } +/// An instance, defined by the URL it can be reached at. +/// +/// # Textual Format +/// An instance's textual format is its URL. +/// +/// ## Examples +/// For the instance `giterated.dev`, the following [`Instance`] initialization +/// would be valid: +/// +/// ``` +/// let instance = Instance { +/// url: String::from("giterated.dev") +/// }; +/// +/// // This is correct +/// assert_eq!(Instance::from_str("giterated.dev"), instance); +/// ``` #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct Instance { pub url: String, diff --git a/src/model/mod.rs b/src/model/mod.rs index e741f77..9c3fa48 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,8 @@ +//! # Giterated Network Data Model Types +//! +//! All network data model types that are not directly associated with +//! individual requests or responses. + pub mod instance; pub mod repository; pub mod user; diff --git a/src/model/repository.rs b/src/model/repository.rs index d942c6c..4b9e66e 100644 --- a/src/model/repository.rs +++ b/src/model/repository.rs @@ -4,6 +4,29 @@ use serde::{Deserialize, Serialize}; use super::{instance::Instance, user::User}; +/// A repository, defined by the instance it exists on along with +/// its owner and name. +/// +/// # Textual Format +/// A repository's textual reference is defined as: +/// +/// `{owner: User}/{name: String}@{instance: Instance}` +/// +/// # Examples +/// For the repository named `foo` owned by `barson:giterated.dev` on the instance +/// `giterated.dev`, the following [`Repository`] initialization would +/// be valid: +/// +/// ``` +/// let repository = Repository { +/// owner: User::from_str("barson:giterated.dev").unwrap(), +/// name: String::from("foo"), +/// instance: Instance::from_str("giterated.dev").unwrap() +/// }; +/// +/// // This is correct +/// assert_eq!(Repository::from_str("barson:giterated.dev/foo@giterated.dev").unwrap(), repository); +/// ``` #[derive(Hash, Clone, Serialize, Deserialize)] pub struct Repository { pub owner: User, diff --git a/src/model/user.rs b/src/model/user.rs index 59a5dd2..5fa8ead 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -5,6 +5,26 @@ use serde::{Deserialize, Serialize}; use super::instance::Instance; +/// A user, defined by its username and instance. +/// +/// # Textual Format +/// A user's textual reference is defined as: +/// +/// `{username: String}:{instance: Instance}` +/// +/// # Examples +/// For the user with the username `barson` and the instance `giterated.dev`, +/// the following [`User`] initialization would be valid: +/// +/// ``` +/// let user = User { +/// username: String::from("barson"), +/// instance: Instance::from_str("giterated.dev").unwrap() +/// }; +/// +/// // This is correct +/// assert_eq!(User::from_str("barson:giterated.dev").unwrap(), user); +/// ``` #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct User { pub username: String,