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

ambee/giterated

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

Implement `FromOperationState` for `AuthenticatedUser` and `AuthenticatedInstance`

Use `AuthenticatedUser` on repository requests so we can filter by privacy. Woohoo! Attempt to filter `UserRepositoriesRequest` responses by visibility to the requester.

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨75dcac3

⁨giterated-daemon/src/backend/user.rs⁩ - ⁨8268⁩ 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