AppRegisterController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Mail\InAppRegisterEmailVerify;
  4. use App\Models\AppRegister;
  5. use App\Services\AccountService;
  6. use App\User;
  7. use App\Util\Lexer\RestrictedNames;
  8. use Illuminate\Http\Request;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Hash;
  11. use Illuminate\Support\Facades\Mail;
  12. use Illuminate\Support\Str;
  13. use Laravel\Passport\RefreshTokenRepository;
  14. use Purify;
  15. class AppRegisterController extends Controller
  16. {
  17. public function index(Request $request)
  18. {
  19. abort_unless(config('auth.in_app_registration'), 404);
  20. $open = (bool) config_cache('pixelfed.open_registration');
  21. if (! $open || $request->user()) {
  22. return redirect('/');
  23. }
  24. return view('auth.iar');
  25. }
  26. public function store(Request $request)
  27. {
  28. abort_unless(config('auth.in_app_registration'), 404);
  29. $open = (bool) config_cache('pixelfed.open_registration');
  30. if (! $open || $request->user()) {
  31. return redirect('/');
  32. }
  33. $rules = [
  34. 'email' => 'required|email:rfc,dns,spoof,strict|unique:users,email|unique:app_registers,email',
  35. ];
  36. if ((bool) config_cache('captcha.enabled') && (bool) config_cache('captcha.active.register')) {
  37. $rules['h-captcha-response'] = 'required|captcha';
  38. }
  39. $this->validate($request, $rules);
  40. $email = strtolower($request->input('email'));
  41. $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
  42. DB::beginTransaction();
  43. $exists = AppRegister::whereEmail($email)->where('created_at', '>', now()->subHours(24))->count();
  44. if ($exists && $exists > 3) {
  45. $errorParams = http_build_query([
  46. 'status' => 'error',
  47. 'message' => 'Too many attempts, please try again later.',
  48. ]);
  49. DB::rollBack();
  50. return redirect()->away("pixelfed://verifyEmail?{$errorParams}");
  51. }
  52. $registration = AppRegister::create([
  53. 'email' => $email,
  54. 'verify_code' => $code,
  55. 'uses' => 1,
  56. 'email_delivered_at' => now(),
  57. ]);
  58. try {
  59. Mail::to($email)->send(new InAppRegisterEmailVerify($code));
  60. } catch (\Exception $e) {
  61. DB::rollBack();
  62. $errorParams = http_build_query([
  63. 'status' => 'error',
  64. 'message' => 'Failed to send verification code',
  65. ]);
  66. return redirect()->away("pixelfed://verifyEmail?{$errorParams}");
  67. }
  68. DB::commit();
  69. $queryParams = http_build_query([
  70. 'email' => $request->email,
  71. 'expires_in' => 3600,
  72. 'status' => 'success',
  73. ]);
  74. return redirect()->away("pixelfed://verifyEmail?{$queryParams}");
  75. }
  76. public function verifyCode(Request $request)
  77. {
  78. abort_unless(config('auth.in_app_registration'), 404);
  79. $open = (bool) config_cache('pixelfed.open_registration');
  80. if (! $open || $request->user()) {
  81. return redirect('/');
  82. }
  83. $this->validate($request, [
  84. 'email' => 'required|email:rfc,dns,spoof,strict|unique:users,email',
  85. 'verify_code' => ['required', 'digits:6', 'numeric'],
  86. ]);
  87. $email = strtolower($request->input('email'));
  88. $code = $request->input('verify_code');
  89. $exists = AppRegister::whereEmail($email)
  90. ->whereVerifyCode($code)
  91. ->where('created_at', '>', now()->subMinutes(60))
  92. ->exists();
  93. return response()->json([
  94. 'status' => $exists ? 'success' : 'error',
  95. ]);
  96. }
  97. public function resendVerification(Request $request)
  98. {
  99. abort_unless(config('auth.in_app_registration'), 404);
  100. $open = (bool) config_cache('pixelfed.open_registration');
  101. if (! $open || $request->user()) {
  102. return redirect('/');
  103. }
  104. return view('auth.iar-resend');
  105. }
  106. public function resendVerificationStore(Request $request)
  107. {
  108. abort_unless(config('auth.in_app_registration'), 404);
  109. $open = (bool) config_cache('pixelfed.open_registration');
  110. if (! $open || $request->user()) {
  111. return redirect('/');
  112. }
  113. $rules = [
  114. 'email' => 'required|email:rfc,dns,spoof,strict|unique:users,email|exists:app_registers,email',
  115. ];
  116. if ((bool) config_cache('captcha.enabled') && (bool) config_cache('captcha.active.register')) {
  117. $rules['h-captcha-response'] = 'required|captcha';
  118. }
  119. $this->validate($request, $rules);
  120. $email = strtolower($request->input('email'));
  121. $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
  122. DB::beginTransaction();
  123. $exists = AppRegister::whereEmail($email)->first();
  124. if (! $exists || $exists->uses > 5) {
  125. $errorMessage = $exists->uses > 5 ? 'Too many attempts have been made, please contact the admins.' : 'Email not found';
  126. $errorParams = http_build_query([
  127. 'status' => 'error',
  128. 'message' => $errorMessage,
  129. ]);
  130. DB::rollBack();
  131. return redirect()->away("pixelfed://verifyEmail?{$errorParams}");
  132. }
  133. $registration = $exists->update([
  134. 'verify_code' => $code,
  135. 'uses' => ($exists->uses + 1),
  136. 'email_delivered_at' => now(),
  137. ]);
  138. try {
  139. Mail::to($email)->send(new InAppRegisterEmailVerify($code));
  140. } catch (\Exception $e) {
  141. DB::rollBack();
  142. $errorParams = http_build_query([
  143. 'status' => 'error',
  144. 'message' => 'Failed to send verification code',
  145. ]);
  146. return redirect()->away("pixelfed://verifyEmail?{$errorParams}");
  147. }
  148. DB::commit();
  149. $queryParams = http_build_query([
  150. 'email' => $request->email,
  151. 'expires_in' => 3600,
  152. 'status' => 'success',
  153. ]);
  154. return redirect()->away("pixelfed://verifyEmail?{$queryParams}");
  155. }
  156. public function onboarding(Request $request)
  157. {
  158. abort_unless(config('auth.in_app_registration'), 404);
  159. $open = (bool) config_cache('pixelfed.open_registration');
  160. if (! $open || $request->user()) {
  161. return redirect('/');
  162. }
  163. $this->validate($request, [
  164. 'email' => 'required|email:rfc,dns,spoof,strict|unique:users,email',
  165. 'verify_code' => ['required', 'digits:6', 'numeric'],
  166. 'username' => $this->validateUsernameRule(),
  167. 'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
  168. 'password' => 'required|string|min:'.config('pixelfed.min_password_length'),
  169. ]);
  170. $email = strtolower($request->input('email'));
  171. $code = $request->input('verify_code');
  172. $username = $request->input('username');
  173. $name = $request->input('name');
  174. $password = $request->input('password');
  175. $exists = AppRegister::whereEmail($email)
  176. ->whereVerifyCode($code)
  177. ->where('created_at', '>', now()->subMinutes(60))
  178. ->exists();
  179. if (! $exists) {
  180. return response()->json([
  181. 'status' => 'error',
  182. 'message' => 'Invalid verification code, please try again later.',
  183. ]);
  184. }
  185. $user = User::create([
  186. 'name' => Purify::clean($name),
  187. 'username' => $username,
  188. 'email' => $email,
  189. 'password' => Hash::make($password),
  190. 'app_register_ip' => request()->ip(),
  191. 'register_source' => 'app',
  192. 'email_verified_at' => now(),
  193. ]);
  194. sleep(random_int(8, 10));
  195. $user = User::findOrFail($user->id);
  196. $token = $user->createToken('Pixelfed App', ['read', 'write', 'follow', 'push']);
  197. $tokenModel = $token->token;
  198. $clientId = $tokenModel->client_id;
  199. $clientSecret = DB::table('oauth_clients')->where('id', $clientId)->value('secret');
  200. $refreshTokenRepo = app(RefreshTokenRepository::class);
  201. $refreshToken = $refreshTokenRepo->create([
  202. 'id' => Str::random(80),
  203. 'access_token_id' => $tokenModel->id,
  204. 'revoked' => false,
  205. 'expires_at' => now()->addDays(config('instance.oauth.refresh_expiration', 400)),
  206. ]);
  207. $expiresAt = $tokenModel->expires_at ?? now()->addDays(config('instance.oauth.token_expiration', 356));
  208. $expiresIn = now()->diffInSeconds($expiresAt);
  209. return response()->json([
  210. 'status' => 'success',
  211. 'token_type' => 'Bearer',
  212. 'domain' => config('pixelfed.domain.app'),
  213. 'expires_in' => $expiresIn,
  214. 'access_token' => $token->accessToken,
  215. 'refresh_token' => $refreshToken->id,
  216. 'client_id' => $clientId,
  217. 'client_secret' => $clientSecret,
  218. 'scope' => ['read', 'write', 'follow', 'push'],
  219. 'user' => [
  220. 'pid' => (string) $user->profile_id,
  221. 'username' => $user->username,
  222. ],
  223. 'account' => AccountService::get($user->profile_id, true),
  224. ]);
  225. }
  226. protected function validateUsernameRule()
  227. {
  228. return [
  229. 'required',
  230. 'min:2',
  231. 'max:30',
  232. 'unique:users',
  233. function ($attribute, $value, $fail) {
  234. $dash = substr_count($value, '-');
  235. $underscore = substr_count($value, '_');
  236. $period = substr_count($value, '.');
  237. if (ends_with($value, ['.php', '.js', '.css'])) {
  238. return $fail('Username is invalid.');
  239. }
  240. if (($dash + $underscore + $period) > 1) {
  241. return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
  242. }
  243. if (! ctype_alnum($value[0])) {
  244. return $fail('Username is invalid. Must start with a letter or number.');
  245. }
  246. if (! ctype_alnum($value[strlen($value) - 1])) {
  247. return $fail('Username is invalid. Must end with a letter or number.');
  248. }
  249. $val = str_replace(['_', '.', '-'], '', $value);
  250. if (! ctype_alnum($val)) {
  251. return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
  252. }
  253. if (! preg_match('/[a-zA-Z]/', $value)) {
  254. return $fail('Username is invalid. Must contain at least one alphabetical character.');
  255. }
  256. $restricted = RestrictedNames::get();
  257. if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
  258. return $fail('Username cannot be used.');
  259. }
  260. },
  261. ];
  262. }
  263. }