diff --git a/.gitignore b/.gitignore index 4f96098..55afa48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target .idea .env +Giterated.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c9f198f..ca3dd57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,12 @@ dependencies = [ [[package]] name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" @@ -178,7 +184,7 @@ dependencies = [ "js-sys", "num-traits", "serde", - "time", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -276,6 +282,12 @@ dependencies = [ ] [[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -529,12 +541,15 @@ dependencies = [ "chrono", "futures-util", "git2", + "jsonwebtoken", + "log", "serde", "serde_json", "sqlx", "thiserror", "tokio", "tokio-tungstenite", + "toml", "tracing", "tracing-subscriber", ] @@ -700,6 +715,20 @@ dependencies = [ ] [[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.3", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -873,6 +902,17 @@ dependencies = [ ] [[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1025,6 +1065,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1162,6 +1211,21 @@ dependencies = [ ] [[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] name = "rsa" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1278,6 +1342,15 @@ dependencies = [ ] [[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] name = "sha1" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1328,6 +1401,18 @@ dependencies = [ ] [[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time 0.3.28", +] + +[[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1489,7 +1574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" dependencies = [ "atoi", - "base64", + "base64 0.21.3", "bitflags 2.4.0", "byteorder", "bytes", @@ -1532,7 +1617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" dependencies = [ "atoi", - "base64", + "base64 0.21.3", "bitflags 2.4.0", "byteorder", "chrono", @@ -1681,6 +1766,34 @@ dependencies = [ ] [[package]] +name = "time" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] + +[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1749,6 +1862,40 @@ dependencies = [ ] [[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1866,6 +2013,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] name = "url" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1967,6 +2120,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "whoami" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2070,6 +2233,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 40cbe69..0899af3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,10 @@ futures-util = "*" serde = { version = "1", features = [ "derive" ]} serde_json = "1.0" tracing-subscriber = "0.3" +jsonwebtoken = { version = "*", features = ["use_pem"]} +log = "*" + +toml = { version = "0.7" } chrono = { version = "0.4", features = [ "serde" ] } async-trait = "0.1" diff --git a/src/authentication.rs b/src/authentication.rs new file mode 100644 index 0000000..76f9f89 --- /dev/null +++ b/src/authentication.rs @@ -0,0 +1,76 @@ +use std::{error::Error, time::SystemTime}; + +use chrono::Duration; +use jsonwebtoken::{encode, Algorithm, EncodingKey}; +use serde::{Deserialize, Serialize}; +use tokio::{fs::File, io::AsyncReadExt}; +use toml::Table; + +use crate::{ + messages::authentication::{AuthenticationTokenRequest, AuthenticationTokenResponse}, + model::{instance::Instance, user::User}, +}; + +#[derive(Debug, Serialize, Deserialize)] +struct UserTokenMetadata { + user: User, + generated_for: Instance, + exp: u64, +} + +pub struct AuthenticationTokenGranter { + pub config: Table, +} + +impl AuthenticationTokenGranter { + pub async fn token_request( + &mut self, + request: AuthenticationTokenRequest, + ) -> Result> { + let secret_key = self.config["authentication"]["secret_key"] + .as_str() + .unwrap(); + let private_key = { + let mut file = File::open(self.config["keys"]["private"].as_str().unwrap()) + .await + .unwrap(); + + let mut key = vec![]; + file.read_to_end(&mut key).await.unwrap(); + + key + }; + + if request.secret_key != secret_key { + error!("Incorrect secret key!"); + + panic!() + } + + let encoding_key = EncodingKey::from_rsa_pem(&private_key).unwrap(); + + let claims = UserTokenMetadata { + user: User { + username: String::from("ambee"), + instance: Instance { + url: String::from("giterated.dev"), + }, + }, + generated_for: Instance { + url: String::from("giterated.dev"), + }, + exp: (SystemTime::UNIX_EPOCH.elapsed().unwrap() + + std::time::Duration::from_secs(24 * 60 * 60)) + .as_secs(), + }; + + let token = encode( + &jsonwebtoken::Header::new(Algorithm::RS256), + &claims, + &encoding_key, + ) + .unwrap(); + + Ok(AuthenticationTokenResponse { token }) + } +} diff --git a/src/backend/git.rs b/src/backend/git.rs index b2b47c7..f9a6a43 100644 --- a/src/backend/git.rs +++ b/src/backend/git.rs @@ -151,7 +151,7 @@ impl GitBackend { } // Delete the repository from the database - return match sqlx::query!( + match sqlx::query!( "DELETE FROM repositories WHERE instance_url = $1 AND username = $2 AND name = $3", instance_url, username, @@ -162,7 +162,7 @@ impl GitBackend { { Ok(deleted) => Ok(deleted.rows_affected()), Err(err) => Err(GitBackendError::FailedDeletingFromDatabase(err)), - }; + } } // TODO: Find where this fits @@ -197,7 +197,7 @@ impl GitBackend { let current_path = dd.new_file().path().unwrap(); // Path or directory - if current_path.eq(Path::new(&path)) || current_path.starts_with(&path) { + if current_path.eq(Path::new(&path)) || current_path.starts_with(path) { return Ok(commit.into()); } } @@ -215,7 +215,7 @@ impl RepositoryBackend for GitBackend { request: &CreateRepositoryRequest, ) -> Result> { // Check if repository already exists in the database - if let Ok(repository) = self + if let Ok(_repository) = self .find_by_instance_username_name( request.owner.instance.url.as_str(), request.owner.username.as_str(), @@ -304,7 +304,7 @@ impl RepositoryBackend for GitBackend { if !repository.can_user_view_repository( request.owner.instance.url.as_str(), - Some(&request.owner.username.as_str()), + Some(request.owner.username.as_str()), ) { return Err(Box::new(GitBackendError::RepositoryNotFound { instance_url: request.owner.instance.url.clone(), @@ -361,7 +361,7 @@ impl RepositoryBackend for GitBackend { let mut current_path = rev_name.replace("refs/heads/", ""); // Get the commit tree - let mut git_tree = if let Some(path) = &request.path { + let git_tree = if let Some(path) = &request.path { // Add it to our full path string current_path.push_str(format!("/{}", path).as_str()); // Get the specified path, return an error if it wasn't found. @@ -400,7 +400,7 @@ impl RepositoryBackend for GitBackend { } // Could possibly be done better - let path = if let Some(path) = current_path.split_once("/") { + let path = if let Some(path) = current_path.split_once('/') { format!("{}/{}", path.1, entry.name().unwrap()) } else { entry.name().unwrap().to_string() diff --git a/src/connection.rs b/src/connection.rs index a16735f..47297ef 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -12,6 +12,7 @@ use tokio::{ use tokio_tungstenite::{tungstenite::Message, WebSocketStream}; use crate::{ + authentication::AuthenticationTokenGranter, backend::{IssuesBackend, RepositoryBackend}, handshake::{HandshakeFinalize, HandshakeMessage, HandshakeResponse}, listener::Listeners, @@ -54,6 +55,7 @@ pub async fn connection_worker( listeners: Arc>, connections: Arc>, backend: Arc>, + auth_granter: Arc>, addr: SocketAddr, ) { let mut handshaked = false; @@ -183,7 +185,7 @@ pub async fn connection_worker( RepositoryMessageKind::Request(request) => match request { RepositoryRequest::CreateRepository(request) => { let mut backend = backend.lock().await; - let response = backend.create_repository(request); + let response = backend.create_repository(request).await; let response = match response { Ok(response) => response, @@ -241,7 +243,7 @@ pub async fn connection_worker( } RepositoryRequest::RepositoryInfo(request) => { let mut backend = backend.lock().await; - let response = backend.repository_info(request); + let response = backend.repository_info(request).await; let response = match response { Ok(response) => response, diff --git a/src/lib.rs b/src/lib.rs index cebef1a..39fb094 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod authentication; pub mod backend; pub mod connection; pub mod handshake; diff --git a/src/main.rs b/src/main.rs index b3cd9c8..e82e6fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,16 +2,20 @@ use std::{error::Error, net::SocketAddr, sync::Arc}; use connection::{connection_worker, Connections, RawConnection}; use giterated_daemon::{ + authentication::AuthenticationTokenGranter, backend::{git::GitBackend, RepositoryBackend}, connection, listener, }; use listener::Listeners; +use sqlx::{postgres::PgConnectOptions, ConnectOptions, PgPool}; use tokio::{ - io::{AsyncRead, AsyncWrite}, + fs::File, + io::{AsyncRead, AsyncReadExt, AsyncWrite}, net::{TcpListener, TcpStream}, sync::Mutex, }; use tokio_tungstenite::{accept_async, WebSocketStream}; +use toml::Table; #[macro_use] extern crate tracing; @@ -19,10 +23,36 @@ extern crate tracing; #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); - let mut listener = TcpListener::bind("127.0.0.1:8080").await?; + let mut listener = TcpListener::bind("0.0.0.0:7270").await?; let connections: Arc> = Arc::default(); let listeners: Arc> = Arc::default(); - let backend: Arc> = Arc::new(Mutex::new(GitBackend::new())); + let config: Table = { + let mut file = File::open("Giterated.toml").await?; + let mut text = String::new(); + file.read_to_string(&mut text).await?; + text.parse()? + }; + let db_conn_options = PgConnectOptions::new() + .host(config["postgres"]["host"].as_str().unwrap()) + .port(config["postgres"]["port"].as_integer().unwrap() as u16) + .database(config["postgres"]["database"].as_str().unwrap()) + .username(config["postgres"]["user"].as_str().unwrap()) + .password(config["postgres"]["password"].as_str().unwrap()) + .log_statements(log::LevelFilter::Off) + .to_owned(); + let db_pool = PgPool::connect_with(db_conn_options).await?; + + let repository_backend: Arc> = Arc::new(Mutex::new({ + let foo: GitBackend = GitBackend { + pg_pool: db_pool, + repository_folder: String::from("/tmp/foo"), + }; + foo + })); + + let token_granter = Arc::new(Mutex::new(AuthenticationTokenGranter { + config: config.clone(), + })); loop { let stream = accept_stream(&mut listener).await; @@ -57,7 +87,8 @@ async fn main() -> Result<(), Box> { connection, listeners.clone(), connections.clone(), - backend.clone(), + repository_backend.clone(), + token_granter.clone(), address, )), }; diff --git a/src/messages/authentication.rs b/src/messages/authentication.rs new file mode 100644 index 0000000..a095ba0 --- /dev/null +++ b/src/messages/authentication.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] +pub enum AuthenticationMessage { + Request(AuthenticationRequest), + Response(AuthenticationResponse), +} + +#[derive(Clone, Serialize, Deserialize)] +pub enum AuthenticationRequest { + AuthenticationToken(AuthenticationTokenRequest), +} + +#[derive(Clone, Serialize, Deserialize)] +pub enum AuthenticationResponse { + AuthenticationToken(AuthenticationTokenResponse), +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct AuthenticationTokenRequest { + pub secret_key: String, + pub username: String, + pub password: String, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct AuthenticationTokenResponse { + pub token: String, +} diff --git a/src/messages/mod.rs b/src/messages/mod.rs index cb91226..1e003fd 100644 --- a/src/messages/mod.rs +++ b/src/messages/mod.rs @@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::handshake::HandshakeMessage; -use self::repository::RepositoryMessage; +use self::{authentication::AuthenticationMessage, repository::RepositoryMessage}; +pub mod authentication; pub mod issues; pub mod repository; pub mod user; @@ -12,4 +13,5 @@ pub mod user; pub enum MessageKind { Handshake(HandshakeMessage), Repository(RepositoryMessage), + Authentication(AuthenticationMessage), } diff --git a/src/model/repository.rs b/src/model/repository.rs index fb4cf65..2badfad 100644 --- a/src/model/repository.rs +++ b/src/model/repository.rs @@ -101,9 +101,7 @@ impl From> for Commit { fn from(commit: git2::Commit<'_>) -> Self { Self { oid: commit.id().to_string(), - message: commit - .message() - .map_or(None, |message| Some(message.to_string())), + message: commit.message().map(|message| message.to_string()), author: commit.author().into(), committer: commit.committer().into(), time: chrono::NaiveDateTime::from_timestamp_opt(commit.time().seconds(), 0).unwrap(), @@ -123,10 +121,8 @@ pub struct CommitSignature { impl From> for CommitSignature { fn from(signature: git2::Signature<'_>) -> Self { Self { - name: signature.name().map_or(None, |name| Some(name.to_string())), - email: signature - .email() - .map_or(None, |email| Some(email.to_string())), + name: signature.name().map(|name| name.to_string()), + email: signature.email().map(|email| email.to_string()), time: chrono::NaiveDateTime::from_timestamp_opt(signature.when().seconds(), 0).unwrap(), } }