]> Untitled Git - lemmy.git/blob - crates/api_crud/src/user/create.rs
a9ea986195978032f2c9bf38194a1417e8047e7c
[lemmy.git] / crates / api_crud / src / user / create.rs
1 use crate::PerformCrud;
2 use actix_web::web::Data;
3 use lemmy_api_common::{blocking, honeypot_check, password_length_check, person::*};
4 use lemmy_apub::{
5   generate_followers_url,
6   generate_inbox_url,
7   generate_local_apub_endpoint,
8   generate_shared_inbox_url,
9   EndpointType,
10 };
11 use lemmy_db_schema::{
12   newtypes::CommunityId,
13   source::{
14     community::{
15       Community,
16       CommunityFollower,
17       CommunityFollowerForm,
18       CommunityForm,
19       CommunityModerator,
20       CommunityModeratorForm,
21     },
22     local_user::{LocalUser, LocalUserForm},
23     person::{Person, PersonForm},
24     site::Site,
25   },
26   traits::{Crud, Followable, Joinable},
27   ListingType,
28   SortType,
29 };
30 use lemmy_db_views_actor::person_view::PersonViewSafe;
31 use lemmy_utils::{
32   apub::generate_actor_keypair,
33   claims::Claims,
34   utils::{check_slurs, is_valid_actor_name},
35   ConnectionId,
36   LemmyError,
37 };
38 use lemmy_websocket::{messages::CheckCaptcha, LemmyContext};
39
40 #[async_trait::async_trait(?Send)]
41 impl PerformCrud for Register {
42   type Response = LoginResponse;
43
44   #[tracing::instrument(skip(self, context, _websocket_id))]
45   async fn perform(
46     &self,
47     context: &Data<LemmyContext>,
48     _websocket_id: Option<ConnectionId>,
49   ) -> Result<LoginResponse, LemmyError> {
50     let data: &Register = self;
51
52     // Make sure site has open registration
53     if let Ok(site) = blocking(context.pool(), Site::read_simple).await? {
54       if !site.open_registration {
55         return Err(LemmyError::from_message("registration_closed"));
56       }
57     }
58
59     password_length_check(&data.password)?;
60     honeypot_check(&data.honeypot)?;
61
62     // Make sure passwords match
63     if data.password != data.password_verify {
64       return Err(LemmyError::from_message("passwords_dont_match"));
65     }
66
67     // Check if there are admins. False if admins exist
68     let no_admins = blocking(context.pool(), move |conn| {
69       PersonViewSafe::admins(conn).map(|a| a.is_empty())
70     })
71     .await??;
72
73     // If its not the admin, check the captcha
74     if !no_admins && context.settings().captcha.enabled {
75       let check = context
76         .chat_server()
77         .send(CheckCaptcha {
78           uuid: data
79             .captcha_uuid
80             .to_owned()
81             .unwrap_or_else(|| "".to_string()),
82           answer: data
83             .captcha_answer
84             .to_owned()
85             .unwrap_or_else(|| "".to_string()),
86         })
87         .await?;
88       if !check {
89         return Err(LemmyError::from_message("captcha_incorrect"));
90       }
91     }
92
93     check_slurs(&data.username, &context.settings().slur_regex())?;
94
95     let actor_keypair = generate_actor_keypair()?;
96     if !is_valid_actor_name(&data.username, context.settings().actor_name_max_length) {
97       return Err(LemmyError::from_message("invalid_username"));
98     }
99     let actor_id = generate_local_apub_endpoint(
100       EndpointType::Person,
101       &data.username,
102       &context.settings().get_protocol_and_hostname(),
103     )?;
104
105     // We have to create both a person, and local_user
106
107     // Register the new person
108     let person_form = PersonForm {
109       name: data.username.to_owned(),
110       actor_id: Some(actor_id.clone()),
111       private_key: Some(Some(actor_keypair.private_key)),
112       public_key: actor_keypair.public_key,
113       inbox_url: Some(generate_inbox_url(&actor_id)?),
114       shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
115       admin: Some(no_admins),
116       ..PersonForm::default()
117     };
118
119     // insert the person
120     let inserted_person = blocking(context.pool(), move |conn| {
121       Person::create(conn, &person_form)
122     })
123     .await?
124     .map_err(LemmyError::from)
125     .map_err(|e| e.with_message("user_already_exists"))?;
126
127     // Create the local user
128     // TODO some of these could probably use the DB defaults
129     let local_user_form = LocalUserForm {
130       person_id: inserted_person.id,
131       email: Some(data.email.as_deref().map(|s| s.to_owned())),
132       password_encrypted: data.password.to_string(),
133       show_nsfw: Some(data.show_nsfw),
134       show_bot_accounts: Some(true),
135       theme: Some("browser".into()),
136       default_sort_type: Some(SortType::Active as i16),
137       default_listing_type: Some(ListingType::Subscribed as i16),
138       lang: Some("browser".into()),
139       show_avatars: Some(true),
140       show_scores: Some(true),
141       show_read_posts: Some(true),
142       show_new_post_notifs: Some(false),
143       send_notifications_to_email: Some(false),
144     };
145
146     let inserted_local_user = match blocking(context.pool(), move |conn| {
147       LocalUser::register(conn, &local_user_form)
148     })
149     .await?
150     {
151       Ok(lu) => lu,
152       Err(e) => {
153         let err_type = if e.to_string()
154           == "duplicate key value violates unique constraint \"local_user_email_key\""
155         {
156           "email_already_exists"
157         } else {
158           "user_already_exists"
159         };
160
161         // If the local user creation errored, then delete that person
162         blocking(context.pool(), move |conn| {
163           Person::delete(conn, inserted_person.id)
164         })
165         .await??;
166
167         return Err(LemmyError::from(e).with_message(err_type));
168       }
169     };
170
171     let main_community_keypair = generate_actor_keypair()?;
172
173     // Create the main community if it doesn't exist
174     let protocol_and_hostname = context.settings().get_protocol_and_hostname();
175     let main_community = match blocking(context.pool(), move |conn| {
176       Community::read(conn, CommunityId(2))
177     })
178     .await?
179     {
180       Ok(c) => c,
181       Err(_e) => {
182         let default_community_name = "main";
183         let actor_id = generate_local_apub_endpoint(
184           EndpointType::Community,
185           default_community_name,
186           &protocol_and_hostname,
187         )?;
188         let community_form = CommunityForm {
189           name: default_community_name.to_string(),
190           title: "The Default Community".to_string(),
191           description: Some("The Default Community".to_string()),
192           actor_id: Some(actor_id.to_owned()),
193           private_key: Some(Some(main_community_keypair.private_key)),
194           public_key: main_community_keypair.public_key,
195           followers_url: Some(generate_followers_url(&actor_id)?),
196           inbox_url: Some(generate_inbox_url(&actor_id)?),
197           shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
198           ..CommunityForm::default()
199         };
200         blocking(context.pool(), move |conn| {
201           Community::create(conn, &community_form)
202         })
203         .await??
204       }
205     };
206
207     // Sign them up for main community no matter what
208     let community_follower_form = CommunityFollowerForm {
209       community_id: main_community.id,
210       person_id: inserted_person.id,
211       pending: false,
212     };
213
214     let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
215     blocking(context.pool(), follow)
216       .await?
217       .map_err(LemmyError::from)
218       .map_err(|e| e.with_message("community_follower_already_exists"))?;
219
220     // If its an admin, add them as a mod and follower to main
221     if no_admins {
222       let community_moderator_form = CommunityModeratorForm {
223         community_id: main_community.id,
224         person_id: inserted_person.id,
225       };
226
227       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
228       blocking(context.pool(), join)
229         .await?
230         .map_err(LemmyError::from)
231         .map_err(|e| e.with_message("community_moderator_already_exists"))?;
232     }
233
234     // Return the jwt
235     Ok(LoginResponse {
236       jwt: Claims::jwt(
237         inserted_local_user.id.0,
238         &context.secret().jwt_secret,
239         &context.settings().hostname,
240       )?
241       .into(),
242     })
243   }
244 }