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

ambee/giterated

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

Giterated Stack `ObjectValue` and `Setting` refactor.

This refactor adds value and setting update events, as well as value getters. Additionally, the stack is now the owner of the ability to write settings into storage. This is accomplished with the `MetadataProvider` trait. This sets up the ground work for push federation, cache, and basically everything else. commit 7befc583cb3e0c6719506c550ed66ac76293413c Author: Amber <[email protected]> Date: Fri Sep 29 15:46:48 2023 -0500 Finish value and settings refactor in the stack. commit 3ac09994a0caafd1a0b95d9a781c7f202f20e75b Author: Amber <[email protected]> Date: Fri Sep 29 09:46:32 2023 -0500 Add set_setting handling back in commit 84fd31e3eae85d98fa68a28b333dbb32cde3bdb8 Author: Amber <[email protected]> Date: Wed Sep 27 06:36:31 2023 -0500 Remove some allocations from meta types commit 16c310ce3680c4a14ed35083b6a230aaecd43152 Author: Amber <[email protected]> Date: Wed Sep 27 05:35:03 2023 -0500 Add cargo metadata commit eb2520a20001bac7b21c6c3d34f62db32f0ada80 Author: Amber <[email protected]> Date: Wed Sep 27 05:26:27 2023 -0500 Refactor setting and value management to use the unified stack. Allows for tight management, inspection, and eventing of setting and value management. commit 901fe103da0fce4b40f33b0a8b64404049ae03cf Author: Amber <[email protected]> Date: Wed Sep 27 02:38:33 2023 -0500 Set up ground work for value / settings refactor

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨c377e4d

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