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

ambee/giterated

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

Major refactor to handler traits

Added `IntoGiteratedHandler`, which is the new trait for handler functions. This allows us to finally get rid of the Object and ObjectOperation bounds that resulted in hacks around the newer features of the unified stack. Squashed commit of the following: commit 62e1ecf76ee31cda0bab4602d9d00fa0dc2f9158 Author: Amber <[email protected]> Date: Wed Oct 11 09:31:11 2023 -0500 Update commit dfd2d1b0b5d81ee3bc48f0321c6aceaa677e3b8b Author: Amber <[email protected]> Date: Wed Oct 11 09:31:07 2023 -0500 Major refactor to handler traits Added `IntoGiteratedHandler`, which is the new trait for handler functions. This allows us to finally get rid of the Object and ObjectOperation bounds that resulted in hacks around the newer features of the unified stack. Removed dead and legacy code. I think... commit 57b4b398eff32e69f2f4b9700e42a1277a4d1055 Author: Amber <[email protected]> Date: Sun Oct 1 23:05:10 2023 -0500 New handler trait for giterated stack Refactor the old handler trait so it is more generic and can be used for specific kinds of handlers

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨90c4780

⁨giterated-daemon/src/backend/user.rs⁩ - ⁨7128⁩ bytes
Raw
1 use anyhow::Error;
2 use futures_util::StreamExt;
3 use giterated_models::authenticated::UserAuthenticationToken;
4
5 use giterated_models::instance::{AuthenticationTokenRequest, Instance, RegisterAccountRequest};
6
7 use giterated_models::repository::{Repository, RepositorySummary};
8 use giterated_models::user::User;
9
10 use giterated_stack::AuthenticatedUser;
11 use std::sync::Arc;
12
13 use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit};
14 use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
15 use base64::{engine::general_purpose::STANDARD, Engine as _};
16
17 use rsa::{
18 pkcs8::{EncodePrivateKey, EncodePublicKey},
19 rand_core::OsRng,
20 RsaPrivateKey, RsaPublicKey,
21 };
22
23 use secrecy::ExposeSecret;
24
25 use sqlx::{Either, PgPool};
26 use tokio::sync::Mutex;
27
28 use crate::authentication::AuthenticationTokenGranter;
29 use crate::backend::git::GitRepository;
30
31 use super::{AuthBackend, MetadataBackend, UserBackend};
32
33 pub struct UserAuth {
34 pub pg_pool: PgPool,
35 pub this_instance: Instance,
36 pub auth_granter: Arc<Mutex<AuthenticationTokenGranter>>,
37 pub settings_provider: Arc<Mutex<dyn MetadataBackend + Send>>,
38 }
39
40 impl UserAuth {
41 pub fn new(
42 pool: PgPool,
43 this_instance: &Instance,
44 granter: Arc<Mutex<AuthenticationTokenGranter>>,
45 settings_provider: Arc<Mutex<dyn MetadataBackend + Send>>,
46 ) -> Self {
47 Self {
48 pg_pool: pool,
49 this_instance: this_instance.clone(),
50 auth_granter: granter,
51 settings_provider,
52 }
53 }
54 }
55
56 #[async_trait::async_trait]
57 impl UserBackend for UserAuth {
58 async fn exists(&mut self, user: &User) -> Result<bool, Error> {
59 Ok(sqlx::query_as!(
60 UserRow,
61 r#"SELECT * FROM users WHERE username = $1"#,
62 user.username
63 )
64 .fetch_one(&self.pg_pool.clone())
65 .await
66 .is_ok())
67 }
68
69 async fn repositories_for_user(
70 &mut self,
71 _requester: &Option<AuthenticatedUser>,
72 user: &User,
73 ) -> Result<Vec<RepositorySummary>, Error> {
74 let mut repositories = sqlx::query_as!(
75 GitRepository,
76 r#"SELECT owner_user, name, description, visibility as "visibility: _", default_branch FROM repositories WHERE owner_user = $1"#,
77 user.to_string()
78 )
79 .fetch_many(&self.pg_pool);
80
81 let mut return_repositories = vec![];
82
83 while let Some(Ok(Either::Right(repository_row))) = repositories.next().await {
84 return_repositories.push(RepositorySummary {
85 repository: Repository {
86 owner: repository_row.owner_user.clone(),
87 name: repository_row.name,
88 instance: self.this_instance.clone(),
89 },
90 owner: repository_row.owner_user,
91 visibility: repository_row.visibility,
92 description: repository_row.description,
93 last_commit: None,
94 })
95 }
96
97 Ok(return_repositories)
98 }
99 }
100
101 #[async_trait::async_trait]
102 impl AuthBackend for UserAuth {
103 async fn register(
104 &mut self,
105 request: RegisterAccountRequest,
106 ) -> Result<UserAuthenticationToken, Error> {
107 const BITS: usize = 2048;
108
109 let private_key = RsaPrivateKey::new(&mut OsRng, BITS).unwrap();
110 let public_key = RsaPublicKey::from(&private_key);
111
112 let key = {
113 let mut target: [u8; 32] = [0; 32];
114
115 let mut index = 0;
116 let mut iterator = request.password.expose_secret().0.as_bytes().iter();
117 while index < 32 {
118 if let Some(next) = iterator.next() {
119 target[index] = *next;
120 index += 1;
121 } else {
122 iterator = request.password.expose_secret().0.as_bytes().iter();
123 }
124 }
125
126 target
127 };
128
129 let key: &Key<Aes256Gcm> = &key.into();
130 let cipher = Aes256Gcm::new(key);
131 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
132 let ciphertext = cipher
133 .encrypt(&nonce, private_key.to_pkcs8_der().unwrap().as_bytes())
134 .unwrap();
135
136 let private_key_enc = format!("{}#{}", STANDARD.encode(nonce), STANDARD.encode(ciphertext));
137
138 let salt = SaltString::generate(&mut OsRng);
139
140 let argon2 = Argon2::default();
141
142 let password_hash = argon2
143 .hash_password(request.password.expose_secret().0.as_bytes(), &salt)
144 .unwrap()
145 .to_string();
146
147 let user = match sqlx::query_as!(
148 UserRow,
149 r#"INSERT INTO users VALUES ($1, $2, $3, $4, $5) returning *"#,
150 request.username,
151 "example.com",
152 password_hash,
153 public_key
154 .to_public_key_pem(rsa::pkcs8::LineEnding::LF)
155 .unwrap(),
156 private_key_enc
157 )
158 .fetch_one(&self.pg_pool)
159 .await
160 {
161 Ok(user) => user,
162 Err(err) => {
163 error!("Failed inserting into the database! {:?}", err);
164
165 return Err(err.into());
166 }
167 };
168
169 let mut granter = self.auth_granter.lock().await;
170 let token = granter
171 .create_token_for(
172 &User {
173 username: user.username,
174 instance: self.this_instance.clone(),
175 },
176 &self.this_instance,
177 )
178 .await;
179
180 Ok(UserAuthenticationToken::from(token))
181 }
182
183 async fn login(
184 &mut self,
185 source: &Instance,
186 request: AuthenticationTokenRequest,
187 ) -> Result<UserAuthenticationToken, Error> {
188 let user = sqlx::query_as!(
189 UserRow,
190 r#"SELECT * FROM users WHERE username = $1"#,
191 request.username
192 )
193 .fetch_one(&self.pg_pool)
194 .await?;
195
196 let hash = PasswordHash::new(&user.password).unwrap();
197
198 if Argon2::default()
199 .verify_password(request.password.expose_secret().0.as_bytes(), &hash)
200 .is_err()
201 {
202 return Err(Error::from(AuthenticationError::InvalidPassword));
203 }
204
205 let mut granter = self.auth_granter.lock().await;
206 let token = granter
207 .create_token_for(
208 &User {
209 username: user.username,
210 instance: self.this_instance.clone(),
211 },
212 source,
213 )
214 .await;
215
216 Ok(UserAuthenticationToken::from(token))
217 }
218 }
219
220 #[allow(unused)]
221 #[derive(Debug, sqlx::FromRow)]
222 struct UserRow {
223 pub username: String,
224 pub email: Option<String>,
225 pub password: String,
226 pub public_key: String,
227 pub enc_private_key: Vec<u8>,
228 }
229
230 #[allow(unused)]
231 #[derive(Debug, sqlx::FromRow)]
232 struct UserValue {
233 pub username: String,
234 pub name: String,
235 pub value: String,
236 }
237
238 #[derive(Debug, thiserror::Error)]
239 pub enum AuthenticationError {
240 #[error("invalid password")]
241 InvalidPassword,
242 }
243