CuratedRegisterController.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Support\Str;
  5. use App\User;
  6. use App\Models\CuratedRegister;
  7. use App\Models\CuratedRegisterActivity;
  8. use App\Services\EmailService;
  9. use App\Services\BouncerService;
  10. use App\Util\Lexer\RestrictedNames;
  11. use App\Mail\CuratedRegisterConfirmEmail;
  12. use App\Mail\CuratedRegisterNotifyAdmin;
  13. use Illuminate\Support\Facades\Mail;
  14. use App\Jobs\CuratedOnboarding\CuratedOnboardingNotifyAdminNewApplicationPipeline;
  15. class CuratedRegisterController extends Controller
  16. {
  17. public function __construct()
  18. {
  19. abort_unless((bool) config_cache('instance.curated_registration.enabled'), 404);
  20. if((bool) config_cache('pixelfed.open_registration')) {
  21. abort_if(config('instance.curated_registration.state.only_enabled_on_closed_reg'), 404);
  22. } else {
  23. abort_unless(config('instance.curated_registration.state.fallback_on_closed_reg'), 404);
  24. }
  25. }
  26. public function index(Request $request)
  27. {
  28. abort_if($request->user(), 404);
  29. return view('auth.curated-register.index', ['step' => 1]);
  30. }
  31. public function concierge(Request $request)
  32. {
  33. abort_if($request->user(), 404);
  34. $emailConfirmed = $request->session()->has('cur-reg-con.email-confirmed') &&
  35. $request->has('next') &&
  36. $request->session()->has('cur-reg-con.cr-id');
  37. return view('auth.curated-register.concierge', compact('emailConfirmed'));
  38. }
  39. public function conciergeResponseSent(Request $request)
  40. {
  41. return view('auth.curated-register.user_response_sent');
  42. }
  43. public function conciergeFormShow(Request $request)
  44. {
  45. abort_if($request->user(), 404);
  46. abort_unless(
  47. $request->session()->has('cur-reg-con.email-confirmed') &&
  48. $request->session()->has('cur-reg-con.cr-id') &&
  49. $request->session()->has('cur-reg-con.ac-id'), 404);
  50. $crid = $request->session()->get('cur-reg-con.cr-id');
  51. $arid = $request->session()->get('cur-reg-con.ac-id');
  52. $showCaptcha = config('instance.curated_registration.captcha_enabled');
  53. if($attempts = $request->session()->get('cur-reg-con-attempt')) {
  54. $showCaptcha = $attempts && $attempts >= 2;
  55. } else {
  56. $showCaptcha = false;
  57. }
  58. $activity = CuratedRegisterActivity::whereRegisterId($crid)->whereFromAdmin(true)->findOrFail($arid);
  59. return view('auth.curated-register.concierge_form', compact('activity', 'showCaptcha'));
  60. }
  61. public function conciergeFormStore(Request $request)
  62. {
  63. abort_if($request->user(), 404);
  64. $request->session()->increment('cur-reg-con-attempt');
  65. abort_unless(
  66. $request->session()->has('cur-reg-con.email-confirmed') &&
  67. $request->session()->has('cur-reg-con.cr-id') &&
  68. $request->session()->has('cur-reg-con.ac-id'), 404);
  69. $attempts = $request->session()->get('cur-reg-con-attempt');
  70. $messages = [];
  71. $rules = [
  72. 'response' => 'required|string|min:5|max:1000',
  73. 'crid' => 'required|integer|min:1',
  74. 'acid' => 'required|integer|min:1'
  75. ];
  76. if(config('instance.curated_registration.captcha_enabled') && $attempts >= 3) {
  77. $rules['h-captcha-response'] = 'required|captcha';
  78. $messages['h-captcha-response.required'] = 'The captcha must be filled';
  79. }
  80. $this->validate($request, $rules, $messages);
  81. $crid = $request->session()->get('cur-reg-con.cr-id');
  82. $acid = $request->session()->get('cur-reg-con.ac-id');
  83. abort_if((string) $crid !== $request->input('crid'), 404);
  84. abort_if((string) $acid !== $request->input('acid'), 404);
  85. if(CuratedRegisterActivity::whereRegisterId($crid)->whereReplyToId($acid)->exists()) {
  86. return redirect()->back()->withErrors(['code' => 'You already replied to this request.']);
  87. }
  88. $act = CuratedRegisterActivity::create([
  89. 'register_id' => $crid,
  90. 'reply_to_id' => $acid,
  91. 'type' => 'user_response',
  92. 'message' => $request->input('response'),
  93. 'from_user' => true,
  94. 'action_required' => true,
  95. ]);
  96. CuratedRegister::findOrFail($crid)->update(['user_has_responded' => true]);
  97. $request->session()->pull('cur-reg-con');
  98. $request->session()->pull('cur-reg-con-attempt');
  99. return view('auth.curated-register.user_response_sent');
  100. }
  101. public function conciergeStore(Request $request)
  102. {
  103. abort_if($request->user(), 404);
  104. $rules = [
  105. 'sid' => 'required_if:action,email|integer|min:1|max:20000000',
  106. 'id' => 'required_if:action,email|integer|min:1|max:20000000',
  107. 'code' => 'required_if:action,email',
  108. 'action' => 'required|string|in:email,message',
  109. 'email' => 'required_if:action,email|email',
  110. 'response' => 'required_if:action,message|string|min:20|max:1000',
  111. ];
  112. $messages = [];
  113. if(config('instance.curated_registration.captcha_enabled')) {
  114. $rules['h-captcha-response'] = 'required|captcha';
  115. $messages['h-captcha-response.required'] = 'The captcha must be filled';
  116. }
  117. $this->validate($request, $rules, $messages);
  118. $action = $request->input('action');
  119. $sid = $request->input('sid');
  120. $id = $request->input('id');
  121. $code = $request->input('code');
  122. $email = $request->input('email');
  123. $cr = CuratedRegister::whereIsClosed(false)->findOrFail($sid);
  124. $ac = CuratedRegisterActivity::whereRegisterId($cr->id)->whereFromAdmin(true)->findOrFail($id);
  125. if(!hash_equals($ac->secret_code, $code)) {
  126. return redirect()->back()->withErrors(['code' => 'Invalid code']);
  127. }
  128. if(!hash_equals($cr->email, $email)) {
  129. return redirect()->back()->withErrors(['email' => 'Invalid email']);
  130. }
  131. $request->session()->put('cur-reg-con.email-confirmed', true);
  132. $request->session()->put('cur-reg-con.cr-id', $cr->id);
  133. $request->session()->put('cur-reg-con.ac-id', $ac->id);
  134. $emailConfirmed = true;
  135. return redirect('/auth/sign_up/concierge/form');
  136. }
  137. public function confirmEmail(Request $request)
  138. {
  139. if($request->user()) {
  140. return redirect(route('help.email-confirmation-issues'));
  141. }
  142. return view('auth.curated-register.confirm_email');
  143. }
  144. public function emailConfirmed(Request $request)
  145. {
  146. if($request->user()) {
  147. return redirect(route('help.email-confirmation-issues'));
  148. }
  149. return view('auth.curated-register.email_confirmed');
  150. }
  151. public function resendConfirmation(Request $request)
  152. {
  153. return view('auth.curated-register.resend-confirmation');
  154. }
  155. public function resendConfirmationProcess(Request $request)
  156. {
  157. $rules = [
  158. 'email' => [
  159. 'required',
  160. 'string',
  161. app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
  162. 'exists:curated_registers',
  163. ]
  164. ];
  165. $messages = [];
  166. if(config('instance.curated_registration.captcha_enabled')) {
  167. $rules['h-captcha-response'] = 'required|captcha';
  168. $messages['h-captcha-response.required'] = 'The captcha must be filled';
  169. }
  170. $this->validate($request, $rules, $messages);
  171. $cur = CuratedRegister::whereEmail($request->input('email'))->whereIsClosed(false)->first();
  172. if(!$cur) {
  173. return redirect()->back()->withErrors(['email' => 'The selected email is invalid.']);
  174. }
  175. $totalCount = CuratedRegisterActivity::whereRegisterId($cur->id)
  176. ->whereType('user_resend_email_confirmation')
  177. ->count();
  178. if($totalCount && $totalCount >= config('instance.curated_registration.resend_confirmation_limit')) {
  179. return redirect()->back()->withErrors(['email' => 'You have re-attempted too many times. To proceed with your application, please <a href="/site/contact" class="text-white" style="text-decoration: underline;">contact the admin team</a>.']);
  180. }
  181. $count = CuratedRegisterActivity::whereRegisterId($cur->id)
  182. ->whereType('user_resend_email_confirmation')
  183. ->where('created_at', '>', now()->subHours(12))
  184. ->count();
  185. if($count) {
  186. return redirect()->back()->withErrors(['email' => 'You can only re-send the confirmation email once per 12 hours. Try again later.']);
  187. }
  188. CuratedRegisterActivity::create([
  189. 'register_id' => $cur->id,
  190. 'type' => 'user_resend_email_confirmation',
  191. 'admin_only_view' => true,
  192. 'from_admin' => false,
  193. 'from_user' => false,
  194. 'action_required' => false,
  195. ]);
  196. Mail::to($cur->email)->send(new CuratedRegisterConfirmEmail($cur));
  197. return view('auth.curated-register.resent-confirmation');
  198. return $request->all();
  199. }
  200. public function confirmEmailHandle(Request $request)
  201. {
  202. $rules = [
  203. 'sid' => 'required',
  204. 'code' => 'required'
  205. ];
  206. $messages = [];
  207. if(config('instance.curated_registration.captcha_enabled')) {
  208. $rules['h-captcha-response'] = 'required|captcha';
  209. $messages['h-captcha-response.required'] = 'The captcha must be filled';
  210. }
  211. $this->validate($request, $rules, $messages);
  212. $cr = CuratedRegister::whereNull('email_verified_at')
  213. ->where('created_at', '>', now()->subHours(24))
  214. ->find($request->input('sid'));
  215. if(!$cr) {
  216. return redirect(route('help.email-confirmation-issues'));
  217. }
  218. if(!hash_equals($cr->verify_code, $request->input('code'))) {
  219. return redirect(route('help.email-confirmation-issues'));
  220. }
  221. $cr->email_verified_at = now();
  222. $cr->save();
  223. if(config('instance.curated_registration.notify.admin.on_verify_email.enabled')) {
  224. CuratedOnboardingNotifyAdminNewApplicationPipeline::dispatch($cr);
  225. }
  226. return view('auth.curated-register.email_confirmed');
  227. }
  228. public function proceed(Request $request)
  229. {
  230. $this->validate($request, [
  231. 'step' => 'required|integer|in:1,2,3,4'
  232. ]);
  233. $step = $request->input('step');
  234. switch($step) {
  235. case 1:
  236. $step = 2;
  237. $request->session()->put('cur-step', 1);
  238. return view('auth.curated-register.index', compact('step'));
  239. break;
  240. case 2:
  241. $this->stepTwo($request);
  242. $step = 3;
  243. $request->session()->put('cur-step', 2);
  244. return view('auth.curated-register.index', compact('step'));
  245. break;
  246. case 3:
  247. $this->stepThree($request);
  248. $step = 3;
  249. $request->session()->put('cur-step', 3);
  250. $verifiedEmail = true;
  251. $request->session()->pull('cur-reg');
  252. return view('auth.curated-register.index', compact('step', 'verifiedEmail'));
  253. break;
  254. }
  255. }
  256. protected function stepTwo($request)
  257. {
  258. if($request->filled('reason')) {
  259. $request->session()->put('cur-reg.form-reason', $request->input('reason'));
  260. }
  261. if($request->filled('username')) {
  262. $request->session()->put('cur-reg.form-username', $request->input('username'));
  263. }
  264. if($request->filled('email')) {
  265. $request->session()->put('cur-reg.form-email', $request->input('email'));
  266. }
  267. $this->validate($request, [
  268. 'username' => [
  269. 'required',
  270. 'min:2',
  271. 'max:15',
  272. 'unique:curated_registers',
  273. 'unique:users',
  274. function ($attribute, $value, $fail) {
  275. $dash = substr_count($value, '-');
  276. $underscore = substr_count($value, '_');
  277. $period = substr_count($value, '.');
  278. if(ends_with($value, ['.php', '.js', '.css'])) {
  279. return $fail('Username is invalid.');
  280. }
  281. if(($dash + $underscore + $period) > 1) {
  282. return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
  283. }
  284. if (!ctype_alnum($value[0])) {
  285. return $fail('Username is invalid. Must start with a letter or number.');
  286. }
  287. if (!ctype_alnum($value[strlen($value) - 1])) {
  288. return $fail('Username is invalid. Must end with a letter or number.');
  289. }
  290. $val = str_replace(['_', '.', '-'], '', $value);
  291. if(!ctype_alnum($val)) {
  292. return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
  293. }
  294. $restricted = RestrictedNames::get();
  295. if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
  296. return $fail('Username cannot be used.');
  297. }
  298. },
  299. ],
  300. 'email' => [
  301. 'required',
  302. 'string',
  303. app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
  304. 'max:255',
  305. 'unique:users',
  306. 'unique:curated_registers',
  307. function ($attribute, $value, $fail) {
  308. $banned = EmailService::isBanned($value);
  309. if($banned) {
  310. return $fail('Email is invalid.');
  311. }
  312. },
  313. ],
  314. 'password' => 'required|min:8',
  315. 'password_confirmation' => 'required|same:password',
  316. 'reason' => 'required|min:20|max:1000',
  317. 'agree' => 'required|accepted'
  318. ]);
  319. $request->session()->put('cur-reg.form-email', $request->input('email'));
  320. $request->session()->put('cur-reg.form-password', $request->input('password'));
  321. }
  322. protected function stepThree($request)
  323. {
  324. $this->validate($request, [
  325. 'email' => [
  326. 'required',
  327. 'string',
  328. app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
  329. 'max:255',
  330. 'unique:users',
  331. 'unique:curated_registers',
  332. function ($attribute, $value, $fail) {
  333. $banned = EmailService::isBanned($value);
  334. if($banned) {
  335. return $fail('Email is invalid.');
  336. }
  337. },
  338. ]
  339. ]);
  340. $cr = new CuratedRegister;
  341. $cr->email = $request->email;
  342. $cr->username = $request->session()->get('cur-reg.form-username');
  343. $cr->password = bcrypt($request->session()->get('cur-reg.form-password'));
  344. $cr->ip_address = $request->ip();
  345. $cr->reason_to_join = $request->session()->get('cur-reg.form-reason');
  346. $cr->verify_code = Str::random(40);
  347. $cr->save();
  348. Mail::to($cr->email)->send(new CuratedRegisterConfirmEmail($cr));
  349. }
  350. }