AppRegisterController.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. 'email_delivered_at' => now(),
  56. ]);
  57. try {
  58. Mail::to($email)->send(new InAppRegisterEmailVerify($code));
  59. } catch (\Exception $e) {
  60. DB::rollBack();
  61. $errorParams = http_build_query([
  62. 'status' => 'error',
  63. 'message' => 'Failed to send verification code',
  64. ]);
  65. return redirect()->away("pixelfed://verifyEmail?{$errorParams}");
  66. }
  67. DB::commit();
  68. $queryParams = http_build_query([
  69. 'email' => $request->email,
  70. 'expires_in' => 3600,
  71. 'status' => 'success',
  72. ]);
  73. return redirect()->away("pixelfed://verifyEmail?{$queryParams}");
  74. }
  75. public function verifyCode(Request $request)
  76. {
  77. abort_unless(config('auth.in_app_registration'), 404);
  78. $open = (bool) config_cache('pixelfed.open_registration');
  79. if (! $open || $request->user()) {
  80. return redirect('/');
  81. }
  82. $this->validate($request, [
  83. 'email' => 'required|email:rfc,dns,spoof,strict|unique:users,email',
  84. 'verify_code' => ['required', 'digits:6', 'numeric'],
  85. ]);
  86. $email = strtolower($request->input('email'));
  87. $code = $request->input('verify_code');
  88. $exists = AppRegister::whereEmail($email)
  89. ->whereVerifyCode($code)
  90. ->where('created_at', '>', now()->subMinutes(60))
  91. ->exists();
  92. return response()->json([
  93. 'status' => $exists ? 'success' : 'error',
  94. ]);
  95. }
  96. public function onboarding(Request $request)
  97. {
  98. abort_unless(config('auth.in_app_registration'), 404);
  99. $open = (bool) config_cache('pixelfed.open_registration');
  100. if (! $open || $request->user()) {
  101. return redirect('/');
  102. }
  103. $this->validate($request, [
  104. 'email' => 'required|email:rfc,dns,spoof,strict|unique:users,email',
  105. 'verify_code' => ['required', 'digits:6', 'numeric'],
  106. 'username' => $this->validateUsernameRule(),
  107. 'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
  108. 'password' => 'required|string|min:'.config('pixelfed.min_password_length'),
  109. ]);
  110. $email = strtolower($request->input('email'));
  111. $code = $request->input('verify_code');
  112. $username = $request->input('username');
  113. $name = $request->input('name');
  114. $password = $request->input('password');
  115. $exists = AppRegister::whereEmail($email)
  116. ->whereVerifyCode($code)
  117. ->where('created_at', '>', now()->subMinutes(60))
  118. ->exists();
  119. if (! $exists) {
  120. return response()->json([
  121. 'status' => 'error',
  122. 'message' => 'Invalid verification code, please try again later.',
  123. ]);
  124. }
  125. $user = User::create([
  126. 'name' => Purify::clean($name),
  127. 'username' => $username,
  128. 'email' => $email,
  129. 'password' => Hash::make($password),
  130. 'app_register_ip' => request()->ip(),
  131. 'register_source' => 'app',
  132. 'email_verified_at' => now(),
  133. ]);
  134. sleep(random_int(5,10));
  135. $user = User::findOrFail($user->id);
  136. $token = $user->createToken('Pixelfed App', ['read', 'write', 'follow', 'push']);
  137. $tokenModel = $token->token;
  138. $clientId = $tokenModel->client_id;
  139. $clientSecret = DB::table('oauth_clients')->where('id', $clientId)->value('secret');
  140. $refreshTokenRepo = app(RefreshTokenRepository::class);
  141. $refreshToken = $refreshTokenRepo->create([
  142. 'id' => Str::random(80),
  143. 'access_token_id' => $tokenModel->id,
  144. 'revoked' => false,
  145. 'expires_at' => now()->addDays(config('instance.oauth.refresh_expiration', 400)),
  146. ]);
  147. $expiresAt = $tokenModel->expires_at ?? now()->addDays(config('instance.oauth.token_expiration', 356));
  148. $expiresIn = now()->diffInSeconds($expiresAt);
  149. return response()->json([
  150. 'status' => 'success',
  151. 'token_type' => 'Bearer',
  152. 'domain' => config('pixelfed.domain.app'),
  153. 'expires_in' => $expiresIn,
  154. 'access_token' => $token->accessToken,
  155. 'refresh_token' => $refreshToken->id,
  156. 'client_id' => $clientId,
  157. 'client_secret' => $clientSecret,
  158. 'scope' => ['read', 'write', 'follow', 'push'],
  159. 'user' => [
  160. 'pid' => (string) $user->profile_id,
  161. 'username' => $user->username,
  162. ],
  163. 'account' => AccountService::get($user->profile_id, true),
  164. ]);
  165. }
  166. protected function validateUsernameRule()
  167. {
  168. return [
  169. 'required',
  170. 'min:2',
  171. 'max:30',
  172. 'unique:users',
  173. function ($attribute, $value, $fail) {
  174. $dash = substr_count($value, '-');
  175. $underscore = substr_count($value, '_');
  176. $period = substr_count($value, '.');
  177. if (ends_with($value, ['.php', '.js', '.css'])) {
  178. return $fail('Username is invalid.');
  179. }
  180. if (($dash + $underscore + $period) > 1) {
  181. return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
  182. }
  183. if (! ctype_alnum($value[0])) {
  184. return $fail('Username is invalid. Must start with a letter or number.');
  185. }
  186. if (! ctype_alnum($value[strlen($value) - 1])) {
  187. return $fail('Username is invalid. Must end with a letter or number.');
  188. }
  189. $val = str_replace(['_', '.', '-'], '', $value);
  190. if (! ctype_alnum($val)) {
  191. return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
  192. }
  193. if (! preg_match('/[a-zA-Z]/', $value)) {
  194. return $fail('Username is invalid. Must contain at least one alphabetical character.');
  195. }
  196. $restricted = RestrictedNames::get();
  197. if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
  198. return $fail('Username cannot be used.');
  199. }
  200. },
  201. ];
  202. }
  203. }