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

ambee/giterated

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

Fix handling stack

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨c53b026

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