use std::sync::Arc; use anyhow::Error; use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit}; use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; use base64::{engine::general_purpose::STANDARD, Engine as _}; use futures_util::StreamExt; use giterated_models::{ messages::{ authentication::{ AuthenticationTokenRequest, AuthenticationTokenResponse, RegisterAccountRequest, RegisterAccountResponse, }, user::{ UserBioRequest, UserBioResponse, UserDisplayImageRequest, UserDisplayImageResponse, UserDisplayNameRequest, UserDisplayNameResponse, }, }, model::{ instance::Instance, settings::{Setting, UserBio, UserDisplayImage, UserDisplayName}, user::User, }, }; use rsa::{ pkcs8::{EncodePrivateKey, EncodePublicKey}, rand_core::OsRng, RsaPrivateKey, RsaPublicKey, }; use sqlx::PgPool; use tokio::sync::Mutex; use crate::authentication::AuthenticationTokenGranter; use super::{AuthBackend, SettingsBackend, UserBackend}; pub struct UserAuth { pub pg_pool: PgPool, pub this_instance: Instance, pub auth_granter: Arc>, pub settings_provider: Arc>, } impl UserAuth { pub fn new( pool: PgPool, this_instance: &Instance, granter: Arc>, settings_provider: Arc>, ) -> Self { Self { pg_pool: pool, this_instance: this_instance.clone(), auth_granter: granter, settings_provider, } } } #[async_trait::async_trait] impl UserBackend for UserAuth { async fn display_name( &mut self, request: UserDisplayNameRequest, ) -> Result { let mut settings_backend = self.settings_provider.lock().await; let settings = settings_backend.user_get(&request.user).await?; drop(settings_backend); let name = settings .iter() .find(|setting| &setting.0 == UserDisplayName::name()); if let Some((_, name)) = name { let name: UserDisplayName = serde_json::from_value(name.clone()).unwrap(); Ok(UserDisplayNameResponse { display_name: Some(name.0), }) } else { Ok(UserDisplayNameResponse { display_name: None }) } } async fn display_image( &mut self, request: UserDisplayImageRequest, ) -> Result { let mut settings_backend: tokio::sync::MutexGuard<'_, dyn SettingsBackend> = self.settings_provider.lock().await; let settings = settings_backend.user_get(&request.user).await?; drop(settings_backend); let image = settings .iter() .find(|setting| &setting.0 == UserDisplayImage::name()); if let Some((_, image)) = image { let image: UserDisplayImage = serde_json::from_value(image.clone()).unwrap(); Ok(UserDisplayImageResponse { image_url: Some(image.0), }) } else { Ok(UserDisplayImageResponse { image_url: None }) } } async fn bio(&mut self, request: UserBioRequest) -> Result { let mut settings_backend = self.settings_provider.lock().await; let settings = settings_backend.user_get(&request.user).await?; drop(settings_backend); let bio = settings .iter() .find(|setting| &setting.0 == UserBio::name()); if let Some((_, bio)) = bio { let bio: UserBio = serde_json::from_value(bio.clone()).unwrap(); Ok(UserBioResponse { bio: Some(bio.0) }) } else { Ok(UserBioResponse { bio: None }) } } async fn exists(&mut self, user: &User) -> Result { Ok(sqlx::query_as!( UserRow, r#"SELECT * FROM users WHERE username = $1"#, user.username ) .fetch_one(&self.pg_pool.clone()) .await .is_err()) } } #[async_trait::async_trait] impl AuthBackend for UserAuth { async fn register( &mut self, request: RegisterAccountRequest, ) -> 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(); let password_hash = argon2 .hash_password(request.password.as_bytes(), &salt) .unwrap() .to_string(); let user = match sqlx::query_as!( UserRow, r#"INSERT INTO users VALUES ($1, $2, $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 { Ok(user) => user, Err(err) => { error!("Failed inserting into the database! {:?}", err); return Err(err.into()); } }; let mut granter = self.auth_granter.lock().await; let token = granter .create_token_for( &User { username: user.username, instance: self.this_instance.clone(), }, &self.this_instance, ) .await; Ok(RegisterAccountResponse { token }) } async fn login( &mut self, _request: AuthenticationTokenRequest, ) -> Result { todo!() } } #[allow(unused)] #[derive(Debug, sqlx::FromRow)] struct UserRow { pub username: String, pub email: Option, pub password: String, pub public_key: String, pub enc_private_key: Vec, }