]> Untitled Git - lemmy.git/blob - crates/api_crud/src/user/create.rs
Making public key required. Fixes #1934
[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   ApiError,
36   ConnectionId,
37   LemmyError,
38 };
39 use lemmy_websocket::{messages::CheckCaptcha, LemmyContext};
40
41 #[async_trait::async_trait(?Send)]
42 impl PerformCrud for Register {
43   type Response = LoginResponse;
44
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(ApiError::err_plain("registration_closed").into());
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(ApiError::err_plain("passwords_dont_match").into());
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(ApiError::err_plain("captcha_incorrect").into());
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(ApiError::err_plain("invalid_username").into());
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(|e| ApiError::err("user_already_exists", e))?;
125
126     // Create the local user
127     // TODO some of these could probably use the DB defaults
128     let local_user_form = LocalUserForm {
129       person_id: inserted_person.id,
130       email: Some(data.email.to_owned()),
131       password_encrypted: data.password.to_owned(),
132       show_nsfw: Some(data.show_nsfw),
133       show_bot_accounts: Some(true),
134       theme: Some("browser".into()),
135       default_sort_type: Some(SortType::Active as i16),
136       default_listing_type: Some(ListingType::Subscribed as i16),
137       lang: Some("browser".into()),
138       show_avatars: Some(true),
139       show_scores: Some(true),
140       show_read_posts: Some(true),
141       show_new_post_notifs: Some(false),
142       send_notifications_to_email: Some(false),
143     };
144
145     let inserted_local_user = match blocking(context.pool(), move |conn| {
146       LocalUser::register(conn, &local_user_form)
147     })
148     .await?
149     {
150       Ok(lu) => lu,
151       Err(e) => {
152         let err_type = if e.to_string()
153           == "duplicate key value violates unique constraint \"local_user_email_key\""
154         {
155           "email_already_exists"
156         } else {
157           "user_already_exists"
158         };
159
160         // If the local user creation errored, then delete that person
161         blocking(context.pool(), move |conn| {
162           Person::delete(conn, inserted_person.id)
163         })
164         .await??;
165
166         return Err(ApiError::err(err_type, e).into());
167       }
168     };
169
170     let main_community_keypair = generate_actor_keypair()?;
171
172     // Create the main community if it doesn't exist
173     let protocol_and_hostname = context.settings().get_protocol_and_hostname();
174     let main_community = match blocking(context.pool(), move |conn| {
175       Community::read(conn, CommunityId(2))
176     })
177     .await?
178     {
179       Ok(c) => c,
180       Err(_e) => {
181         let default_community_name = "main";
182         let actor_id = generate_local_apub_endpoint(
183           EndpointType::Community,
184           default_community_name,
185           &protocol_and_hostname,
186         )?;
187         let community_form = CommunityForm {
188           name: default_community_name.to_string(),
189           title: "The Default Community".to_string(),
190           description: Some("The Default Community".to_string()),
191           actor_id: Some(actor_id.to_owned()),
192           private_key: Some(Some(main_community_keypair.private_key)),
193           public_key: main_community_keypair.public_key,
194           followers_url: Some(generate_followers_url(&actor_id)?),
195           inbox_url: Some(generate_inbox_url(&actor_id)?),
196           shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
197           ..CommunityForm::default()
198         };
199         blocking(context.pool(), move |conn| {
200           Community::create(conn, &community_form)
201         })
202         .await??
203       }
204     };
205
206     // Sign them up for main community no matter what
207     let community_follower_form = CommunityFollowerForm {
208       community_id: main_community.id,
209       person_id: inserted_person.id,
210       pending: false,
211     };
212
213     let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
214     blocking(context.pool(), follow)
215       .await?
216       .map_err(|e| ApiError::err("community_follower_already_exists", e))?;
217
218     // If its an admin, add them as a mod and follower to main
219     if no_admins {
220       let community_moderator_form = CommunityModeratorForm {
221         community_id: main_community.id,
222         person_id: inserted_person.id,
223       };
224
225       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
226       blocking(context.pool(), join)
227         .await?
228         .map_err(|e| ApiError::err("community_moderator_already_exists", e))?;
229     }
230
231     // Return the jwt
232     Ok(LoginResponse {
233       jwt: Claims::jwt(
234         inserted_local_user.id.0,
235         &context.secret().jwt_secret,
236         &context.settings().hostname,
237       )?,
238     })
239   }
240 }