]> Untitled Git - lemmy.git/blob - server/src/db/user.rs
Merge branch 'admin_settings' into dev
[lemmy.git] / server / src / db / user.rs
1 use super::*;
2 use crate::schema::user_;
3 use crate::schema::user_::dsl::*;
4 use crate::{is_email_regex, Settings};
5 use bcrypt::{hash, DEFAULT_COST};
6 use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
7
8 #[derive(Queryable, Identifiable, PartialEq, Debug)]
9 #[table_name = "user_"]
10 pub struct User_ {
11   pub id: i32,
12   pub name: String,
13   pub fedi_name: String,
14   pub preferred_username: Option<String>,
15   pub password_encrypted: String,
16   pub email: Option<String>,
17   pub avatar: Option<String>,
18   pub admin: bool,
19   pub banned: bool,
20   pub published: chrono::NaiveDateTime,
21   pub updated: Option<chrono::NaiveDateTime>,
22   pub show_nsfw: bool,
23   pub theme: String,
24   pub default_sort_type: i16,
25   pub default_listing_type: i16,
26   pub lang: String,
27   pub show_avatars: bool,
28   pub send_notifications_to_email: bool,
29   pub matrix_user_id: Option<String>,
30 }
31
32 #[derive(Insertable, AsChangeset, Clone)]
33 #[table_name = "user_"]
34 pub struct UserForm {
35   pub name: String,
36   pub fedi_name: String,
37   pub preferred_username: Option<String>,
38   pub password_encrypted: String,
39   pub admin: bool,
40   pub banned: bool,
41   pub email: Option<String>,
42   pub avatar: Option<String>,
43   pub updated: Option<chrono::NaiveDateTime>,
44   pub show_nsfw: bool,
45   pub theme: String,
46   pub default_sort_type: i16,
47   pub default_listing_type: i16,
48   pub lang: String,
49   pub show_avatars: bool,
50   pub send_notifications_to_email: bool,
51   pub matrix_user_id: Option<String>,
52 }
53
54 impl Crud<UserForm> for User_ {
55   fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
56     user_.find(user_id).first::<Self>(conn)
57   }
58   fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
59     diesel::delete(user_.find(user_id)).execute(conn)
60   }
61   fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
62     insert_into(user_).values(form).get_result::<Self>(conn)
63   }
64   fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
65     diesel::update(user_.find(user_id))
66       .set(form)
67       .get_result::<Self>(conn)
68   }
69 }
70
71 impl User_ {
72   pub fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
73     let mut edited_user = form.clone();
74     let password_hash =
75       hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
76     edited_user.password_encrypted = password_hash;
77
78     Self::create(&conn, &edited_user)
79   }
80
81   pub fn update_password(
82     conn: &PgConnection,
83     user_id: i32,
84     new_password: &str,
85   ) -> Result<Self, Error> {
86     let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
87
88     diesel::update(user_.find(user_id))
89       .set(password_encrypted.eq(password_hash))
90       .get_result::<Self>(conn)
91   }
92
93   pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result<Self, Error> {
94     user_.filter(name.eq(from_user_name)).first::<Self>(conn)
95   }
96 }
97
98 #[derive(Debug, Serialize, Deserialize)]
99 pub struct Claims {
100   pub id: i32,
101   pub username: String,
102   pub iss: String,
103   pub show_nsfw: bool,
104   pub theme: String,
105   pub default_sort_type: i16,
106   pub default_listing_type: i16,
107   pub lang: String,
108   pub avatar: Option<String>,
109   pub show_avatars: bool,
110 }
111
112 impl Claims {
113   pub fn decode(jwt: &str) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> {
114     let v = Validation {
115       validate_exp: false,
116       ..Validation::default()
117     };
118     decode::<Claims>(
119       &jwt,
120       &DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
121       &v,
122     )
123   }
124 }
125
126 type Jwt = String;
127 impl User_ {
128   pub fn jwt(&self) -> Jwt {
129     let my_claims = Claims {
130       id: self.id,
131       username: self.name.to_owned(),
132       iss: self.fedi_name.to_owned(),
133       show_nsfw: self.show_nsfw,
134       theme: self.theme.to_owned(),
135       default_sort_type: self.default_sort_type,
136       default_listing_type: self.default_listing_type,
137       lang: self.lang.to_owned(),
138       avatar: self.avatar.to_owned(),
139       show_avatars: self.show_avatars.to_owned(),
140     };
141     encode(
142       &Header::default(),
143       &my_claims,
144       &EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
145     )
146     .unwrap()
147   }
148
149   pub fn find_by_username(conn: &PgConnection, username: &str) -> Result<Self, Error> {
150     user_.filter(name.eq(username)).first::<User_>(conn)
151   }
152
153   pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<Self, Error> {
154     user_.filter(email.eq(from_email)).first::<User_>(conn)
155   }
156
157   pub fn find_by_email_or_username(
158     conn: &PgConnection,
159     username_or_email: &str,
160   ) -> Result<Self, Error> {
161     if is_email_regex(username_or_email) {
162       User_::find_by_email(conn, username_or_email)
163     } else {
164       User_::find_by_username(conn, username_or_email)
165     }
166   }
167
168   pub fn get_profile_url(&self) -> String {
169     format!("https://{}/u/{}", Settings::get().hostname, self.name)
170   }
171
172   pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result<Self, Error> {
173     let claims: Claims = Claims::decode(&jwt).expect("Invalid token").claims;
174     Self::read(&conn, claims.id)
175   }
176 }
177
178 #[cfg(test)]
179 mod tests {
180   use super::User_;
181   use super::*;
182
183   #[test]
184   fn test_crud() {
185     let conn = establish_unpooled_connection();
186
187     let new_user = UserForm {
188       name: "thommy".into(),
189       fedi_name: "rrf".into(),
190       preferred_username: None,
191       password_encrypted: "nope".into(),
192       email: None,
193       matrix_user_id: None,
194       avatar: None,
195       admin: false,
196       banned: false,
197       updated: None,
198       show_nsfw: false,
199       theme: "darkly".into(),
200       default_sort_type: SortType::Hot as i16,
201       default_listing_type: ListingType::Subscribed as i16,
202       lang: "browser".into(),
203       show_avatars: true,
204       send_notifications_to_email: false,
205     };
206
207     let inserted_user = User_::create(&conn, &new_user).unwrap();
208
209     let expected_user = User_ {
210       id: inserted_user.id,
211       name: "thommy".into(),
212       fedi_name: "rrf".into(),
213       preferred_username: None,
214       password_encrypted: "nope".into(),
215       email: None,
216       matrix_user_id: None,
217       avatar: None,
218       admin: false,
219       banned: false,
220       published: inserted_user.published,
221       updated: None,
222       show_nsfw: false,
223       theme: "darkly".into(),
224       default_sort_type: SortType::Hot as i16,
225       default_listing_type: ListingType::Subscribed as i16,
226       lang: "browser".into(),
227       show_avatars: true,
228       send_notifications_to_email: false,
229     };
230
231     let read_user = User_::read(&conn, inserted_user.id).unwrap();
232     let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
233     let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
234
235     assert_eq!(expected_user, read_user);
236     assert_eq!(expected_user, inserted_user);
237     assert_eq!(expected_user, updated_user);
238     assert_eq!(1, num_deleted);
239   }
240 }