AccountController.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Auth;
  4. use Cache;
  5. use Mail;
  6. use Illuminate\Support\Facades\Redis;
  7. use Illuminate\Support\Str;
  8. use Carbon\Carbon;
  9. use App\Mail\ConfirmEmail;
  10. use Illuminate\Http\Request;
  11. use PragmaRX\Google2FA\Google2FA;
  12. use App\Jobs\FollowPipeline\FollowPipeline;
  13. use App\{
  14. DirectMessage,
  15. EmailVerification,
  16. Follower,
  17. FollowRequest,
  18. Media,
  19. Notification,
  20. Profile,
  21. User,
  22. UserDevice,
  23. UserFilter,
  24. UserSetting
  25. };
  26. use League\Fractal;
  27. use League\Fractal\Serializer\ArraySerializer;
  28. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  29. use App\Transformer\Api\Mastodon\v1\AccountTransformer;
  30. use App\Services\AccountService;
  31. use App\Services\FollowerService;
  32. use App\Services\NotificationService;
  33. use App\Services\UserFilterService;
  34. use App\Services\RelationshipService;
  35. use App\Jobs\FollowPipeline\FollowAcceptPipeline;
  36. use App\Jobs\FollowPipeline\FollowRejectPipeline;
  37. class AccountController extends Controller
  38. {
  39. protected $filters = [
  40. 'user.mute',
  41. 'user.block',
  42. ];
  43. const FILTER_LIMIT_MUTE_TEXT = 'You cannot mute more than ';
  44. const FILTER_LIMIT_BLOCK_TEXT = 'You cannot block more than ';
  45. public function __construct()
  46. {
  47. $this->middleware('auth');
  48. }
  49. public function notifications(Request $request)
  50. {
  51. return view('account.activity');
  52. }
  53. public function followingActivity(Request $request)
  54. {
  55. $this->validate($request, [
  56. 'page' => 'nullable|min:1|max:3',
  57. 'a' => 'nullable|alpha_dash',
  58. ]);
  59. $action = $request->input('a');
  60. $allowed = ['like', 'follow'];
  61. $timeago = Carbon::now()->subMonths(3);
  62. $profile = Auth::user()->profile;
  63. $following = $profile->following->pluck('id');
  64. $notifications = Notification::whereIn('actor_id', $following)
  65. ->whereIn('action', $allowed)
  66. ->where('actor_id', '<>', $profile->id)
  67. ->where('profile_id', '<>', $profile->id)
  68. ->whereDate('created_at', '>', $timeago)
  69. ->orderBy('notifications.created_at', 'desc')
  70. ->simplePaginate(30);
  71. return view('account.following', compact('profile', 'notifications'));
  72. }
  73. public function verifyEmail(Request $request)
  74. {
  75. $recentSent = EmailVerification::whereUserId(Auth::id())
  76. ->whereDate('created_at', '>', now()->subHours(12))->count();
  77. return view('account.verify_email', compact('recentSent'));
  78. }
  79. public function sendVerifyEmail(Request $request)
  80. {
  81. $recentAttempt = EmailVerification::whereUserId(Auth::id())
  82. ->whereDate('created_at', '>', now()->subHours(12))->count();
  83. if ($recentAttempt > 0) {
  84. return redirect()->back()->with('error', 'A verification email has already been sent recently. Please check your email, or try again later.');
  85. }
  86. EmailVerification::whereUserId(Auth::id())->delete();
  87. $user = User::whereNull('email_verified_at')->find(Auth::id());
  88. $utoken = Str::uuid() . Str::random(mt_rand(5,9));
  89. $rtoken = Str::random(mt_rand(64, 70));
  90. $verify = new EmailVerification();
  91. $verify->user_id = $user->id;
  92. $verify->email = $user->email;
  93. $verify->user_token = $utoken;
  94. $verify->random_token = $rtoken;
  95. $verify->save();
  96. Mail::to($user->email)->send(new ConfirmEmail($verify));
  97. return redirect()->back()->with('status', 'Verification email sent!');
  98. }
  99. public function confirmVerifyEmail(Request $request, $userToken, $randomToken)
  100. {
  101. $verify = EmailVerification::where('user_token', $userToken)
  102. ->where('created_at', '>', now()->subHours(24))
  103. ->where('random_token', $randomToken)
  104. ->firstOrFail();
  105. if (Auth::id() === $verify->user_id && $verify->user_token === $userToken && $verify->random_token === $randomToken) {
  106. $user = User::find(Auth::id());
  107. $user->email_verified_at = Carbon::now();
  108. $user->save();
  109. return redirect('/');
  110. } else {
  111. abort(403);
  112. }
  113. }
  114. public function direct()
  115. {
  116. return view('account.direct');
  117. }
  118. public function directMessage(Request $request, $id)
  119. {
  120. $profile = Profile::where('id', '!=', $request->user()->profile_id)
  121. // ->whereNull('domain')
  122. ->findOrFail($id);
  123. return view('account.directmessage', compact('id'));
  124. }
  125. public function mute(Request $request)
  126. {
  127. $this->validate($request, [
  128. 'type' => 'required|string|in:user',
  129. 'item' => 'required|integer|min:1',
  130. ]);
  131. $pid = $request->user()->profile_id;
  132. $count = UserFilterService::muteCount($pid);
  133. $maxLimit = (int) config_cache('instance.user_filters.max_user_mutes');
  134. abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts');
  135. if($count == 0) {
  136. $filterCount = UserFilter::whereUserId($pid)->count();
  137. abort_if($filterCount >= $maxLimit, 422, self::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts');
  138. }
  139. $type = $request->input('type');
  140. $item = $request->input('item');
  141. $action = $type . '.mute';
  142. if (!in_array($action, $this->filters)) {
  143. return abort(406);
  144. }
  145. $filterable = [];
  146. switch ($type) {
  147. case 'user':
  148. $profile = Profile::findOrFail($item);
  149. if ($profile->id == $pid) {
  150. return abort(403);
  151. }
  152. $class = get_class($profile);
  153. $filterable['id'] = $profile->id;
  154. $filterable['type'] = $class;
  155. break;
  156. }
  157. $filter = UserFilter::firstOrCreate([
  158. 'user_id' => $pid,
  159. 'filterable_id' => $filterable['id'],
  160. 'filterable_type' => $filterable['type'],
  161. 'filter_type' => 'mute',
  162. ]);
  163. UserFilterService::mute($pid, $filterable['id']);
  164. $res = RelationshipService::refresh($pid, $profile->id);
  165. if($request->wantsJson()) {
  166. return response()->json($res);
  167. } else {
  168. return redirect()->back();
  169. }
  170. }
  171. public function unmute(Request $request)
  172. {
  173. $this->validate($request, [
  174. 'type' => 'required|string|in:user',
  175. 'item' => 'required|integer|min:1',
  176. ]);
  177. $pid = $request->user()->profile_id;
  178. $type = $request->input('type');
  179. $item = $request->input('item');
  180. $action = $type . '.mute';
  181. if (!in_array($action, $this->filters)) {
  182. return abort(406);
  183. }
  184. $filterable = [];
  185. switch ($type) {
  186. case 'user':
  187. $profile = Profile::findOrFail($item);
  188. if ($profile->id == $pid) {
  189. return abort(403);
  190. }
  191. $class = get_class($profile);
  192. $filterable['id'] = $profile->id;
  193. $filterable['type'] = $class;
  194. break;
  195. default:
  196. abort(400);
  197. break;
  198. }
  199. $filter = UserFilter::whereUserId($pid)
  200. ->whereFilterableId($filterable['id'])
  201. ->whereFilterableType($filterable['type'])
  202. ->whereFilterType('mute')
  203. ->first();
  204. if($filter) {
  205. UserFilterService::unmute($pid, $filterable['id']);
  206. $filter->delete();
  207. }
  208. $res = RelationshipService::refresh($pid, $profile->id);
  209. if($request->wantsJson()) {
  210. return response()->json($res);
  211. } else {
  212. return redirect()->back();
  213. }
  214. }
  215. public function block(Request $request)
  216. {
  217. $this->validate($request, [
  218. 'type' => 'required|string|in:user',
  219. 'item' => 'required|integer|min:1',
  220. ]);
  221. $pid = $request->user()->profile_id;
  222. $count = UserFilterService::blockCount($pid);
  223. $maxLimit = (int) config_cache('instance.user_filters.max_user_blocks');
  224. abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts');
  225. if($count == 0) {
  226. $filterCount = UserFilter::whereUserId($pid)->whereFilterType('block')->count();
  227. abort_if($filterCount >= $maxLimit, 422, self::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts');
  228. }
  229. $type = $request->input('type');
  230. $item = $request->input('item');
  231. $action = $type.'.block';
  232. if (!in_array($action, $this->filters)) {
  233. return abort(406);
  234. }
  235. $filterable = [];
  236. switch ($type) {
  237. case 'user':
  238. $profile = Profile::findOrFail($item);
  239. if ($profile->id == $pid || ($profile->user && $profile->user->is_admin == true)) {
  240. return abort(403);
  241. }
  242. $class = get_class($profile);
  243. $filterable['id'] = $profile->id;
  244. $filterable['type'] = $class;
  245. $followed = Follower::whereProfileId($profile->id)->whereFollowingId($pid)->first();
  246. if($followed) {
  247. $followed->delete();
  248. $profile->following_count = Follower::whereProfileId($profile->id)->count();
  249. $profile->save();
  250. $selfProfile = $request->user()->profile;
  251. $selfProfile->followers_count = Follower::whereFollowingId($pid)->count();
  252. $selfProfile->save();
  253. FollowerService::remove($profile->id, $pid);
  254. AccountService::del($pid);
  255. AccountService::del($profile->id);
  256. }
  257. $following = Follower::whereProfileId($pid)->whereFollowingId($profile->id)->first();
  258. if($following) {
  259. $following->delete();
  260. $profile->followers_count = Follower::whereFollowingId($profile->id)->count();
  261. $profile->save();
  262. $selfProfile = $request->user()->profile;
  263. $selfProfile->following_count = Follower::whereProfileId($pid)->count();
  264. $selfProfile->save();
  265. FollowerService::remove($pid, $profile->pid);
  266. AccountService::del($pid);
  267. AccountService::del($profile->id);
  268. }
  269. Notification::whereProfileId($pid)
  270. ->whereActorId($profile->id)
  271. ->get()
  272. ->map(function($n) use($pid) {
  273. NotificationService::del($pid, $n['id']);
  274. $n->forceDelete();
  275. });
  276. break;
  277. }
  278. $filter = UserFilter::firstOrCreate([
  279. 'user_id' => $pid,
  280. 'filterable_id' => $filterable['id'],
  281. 'filterable_type' => $filterable['type'],
  282. 'filter_type' => 'block',
  283. ]);
  284. UserFilterService::block($pid, $filterable['id']);
  285. $res = RelationshipService::refresh($pid, $profile->id);
  286. if($request->wantsJson()) {
  287. return response()->json($res);
  288. } else {
  289. return redirect()->back();
  290. }
  291. }
  292. public function unblock(Request $request)
  293. {
  294. $this->validate($request, [
  295. 'type' => 'required|string|in:user',
  296. 'item' => 'required|integer|min:1',
  297. ]);
  298. $pid = $request->user()->profile_id;
  299. $type = $request->input('type');
  300. $item = $request->input('item');
  301. $action = $type . '.block';
  302. if (!in_array($action, $this->filters)) {
  303. return abort(406);
  304. }
  305. $filterable = [];
  306. switch ($type) {
  307. case 'user':
  308. $profile = Profile::findOrFail($item);
  309. if ($profile->id == $pid) {
  310. return abort(403);
  311. }
  312. $class = get_class($profile);
  313. $filterable['id'] = $profile->id;
  314. $filterable['type'] = $class;
  315. break;
  316. default:
  317. abort(400);
  318. break;
  319. }
  320. $filter = UserFilter::whereUserId($pid)
  321. ->whereFilterableId($filterable['id'])
  322. ->whereFilterableType($filterable['type'])
  323. ->whereFilterType('block')
  324. ->first();
  325. if($filter) {
  326. $filter->delete();
  327. UserFilterService::unblock($pid, $filterable['id']);
  328. }
  329. $res = RelationshipService::refresh($pid, $profile->id);
  330. if($request->wantsJson()) {
  331. return response()->json($res);
  332. } else {
  333. return redirect()->back();
  334. }
  335. }
  336. public function followRequests(Request $request)
  337. {
  338. $pid = Auth::user()->profile->id;
  339. $followers = FollowRequest::whereFollowingId($pid)->orderBy('id','desc')->whereIsRejected(0)->simplePaginate(10);
  340. return view('account.follow-requests', compact('followers'));
  341. }
  342. public function followRequestsJson(Request $request)
  343. {
  344. $pid = Auth::user()->profile_id;
  345. $followers = FollowRequest::whereFollowingId($pid)->orderBy('id','desc')->whereIsRejected(0)->get();
  346. $res = [
  347. 'count' => $followers->count(),
  348. 'accounts' => $followers->take(10)->map(function($a) {
  349. $actor = $a->actor;
  350. return [
  351. 'rid' => (string) $a->id,
  352. 'id' => (string) $actor->id,
  353. 'username' => $actor->username,
  354. 'avatar' => $actor->avatarUrl(),
  355. 'url' => $actor->url(),
  356. 'local' => $actor->domain == null,
  357. 'account' => AccountService::get($actor->id)
  358. ];
  359. })
  360. ];
  361. return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
  362. }
  363. public function followRequestHandle(Request $request)
  364. {
  365. $this->validate($request, [
  366. 'action' => 'required|string|max:10',
  367. 'id' => 'required|integer|min:1'
  368. ]);
  369. $pid = Auth::user()->profile->id;
  370. $action = $request->input('action') === 'accept' ? 'accept' : 'reject';
  371. $id = $request->input('id');
  372. $followRequest = FollowRequest::whereFollowingId($pid)->findOrFail($id);
  373. $follower = $followRequest->follower;
  374. switch ($action) {
  375. case 'accept':
  376. $follow = new Follower();
  377. $follow->profile_id = $follower->id;
  378. $follow->following_id = $pid;
  379. $follow->save();
  380. $profile = Profile::findOrFail($pid);
  381. $profile->followers_count++;
  382. $profile->save();
  383. AccountService::del($profile->id);
  384. $profile = Profile::findOrFail($follower->id);
  385. $profile->following_count++;
  386. $profile->save();
  387. AccountService::del($profile->id);
  388. if($follower->domain != null && $follower->private_key === null) {
  389. FollowAcceptPipeline::dispatch($followRequest)->onQueue('follow');
  390. } else {
  391. FollowPipeline::dispatch($follow);
  392. $followRequest->delete();
  393. }
  394. break;
  395. case 'reject':
  396. if($follower->domain != null && $follower->private_key === null) {
  397. FollowRejectPipeline::dispatch($followRequest)->onQueue('follow');
  398. } else {
  399. $followRequest->delete();
  400. }
  401. break;
  402. }
  403. Cache::forget('profile:follower_count:'.$pid);
  404. Cache::forget('profile:following_count:'.$pid);
  405. RelationshipService::refresh($pid, $follower->id);
  406. return response()->json(['msg' => 'success'], 200);
  407. }
  408. public function sudoMode(Request $request)
  409. {
  410. if($request->session()->has('sudoModeAttempts') && $request->session()->get('sudoModeAttempts') >= 3) {
  411. $request->session()->pull('2fa.session.active');
  412. $request->session()->pull('redirectNext');
  413. $request->session()->pull('sudoModeAttempts');
  414. Auth::logout();
  415. return redirect(route('login'));
  416. }
  417. return view('auth.sudo');
  418. }
  419. public function sudoModeVerify(Request $request)
  420. {
  421. $this->validate($request, [
  422. 'password' => 'required|string|max:500',
  423. 'trustDevice' => 'nullable'
  424. ]);
  425. $user = Auth::user();
  426. $password = $request->input('password');
  427. $trustDevice = $request->input('trustDevice') == 'on';
  428. $next = $request->session()->get('redirectNext', '/');
  429. if($request->session()->has('sudoModeAttempts')) {
  430. $count = (int) $request->session()->get('sudoModeAttempts');
  431. $request->session()->put('sudoModeAttempts', $count + 1);
  432. } else {
  433. $request->session()->put('sudoModeAttempts', 1);
  434. }
  435. if(password_verify($password, $user->password) === true) {
  436. $request->session()->put('sudoMode', time());
  437. if($trustDevice == true) {
  438. $request->session()->put('sudoTrustDevice', 1);
  439. }
  440. //Fix wrong scheme when using reverse proxy
  441. if(!str_contains($next, 'https') && config('instance.force_https_urls', true)) {
  442. $next = Str::of($next)->replace('http', 'https')->toString();
  443. }
  444. return redirect($next);
  445. } else {
  446. return redirect()
  447. ->back()
  448. ->withErrors(['password' => __('auth.failed')]);
  449. }
  450. }
  451. public function twoFactorCheckpoint(Request $request)
  452. {
  453. return view('auth.checkpoint');
  454. }
  455. public function twoFactorVerify(Request $request)
  456. {
  457. $this->validate($request, [
  458. 'code' => 'required|string|max:32'
  459. ]);
  460. $user = Auth::user();
  461. $code = $request->input('code');
  462. $google2fa = new Google2FA();
  463. $verify = $google2fa->verifyKey($user->{'2fa_secret'}, $code);
  464. if($verify) {
  465. $request->session()->push('2fa.session.active', true);
  466. return redirect('/');
  467. } else {
  468. if($this->twoFactorBackupCheck($request, $code, $user)) {
  469. return redirect('/');
  470. }
  471. if($request->session()->has('2fa.attempts')) {
  472. $count = (int) $request->session()->get('2fa.attempts');
  473. if($count == 3) {
  474. Auth::logout();
  475. return redirect('/');
  476. }
  477. $request->session()->put('2fa.attempts', $count + 1);
  478. } else {
  479. $request->session()->put('2fa.attempts', 1);
  480. }
  481. return redirect('/i/auth/checkpoint')->withErrors([
  482. 'code' => 'Invalid code'
  483. ]);
  484. }
  485. }
  486. protected function twoFactorBackupCheck($request, $code, User $user)
  487. {
  488. $backupCodes = $user->{'2fa_backup_codes'};
  489. if($backupCodes) {
  490. $codes = json_decode($backupCodes, true);
  491. foreach ($codes as $c) {
  492. if(hash_equals($c, $code)) {
  493. $codes = array_flatten(array_diff($codes, [$code]));
  494. $user->{'2fa_backup_codes'} = json_encode($codes);
  495. $user->save();
  496. $request->session()->push('2fa.session.active', true);
  497. return true;
  498. }
  499. }
  500. return false;
  501. } else {
  502. return false;
  503. }
  504. }
  505. public function accountRestored(Request $request)
  506. {
  507. }
  508. public function accountMutes(Request $request)
  509. {
  510. abort_if(!$request->user(), 403);
  511. $this->validate($request, [
  512. 'limit' => 'nullable|integer|min:1|max:40'
  513. ]);
  514. $user = $request->user();
  515. $limit = $request->input('limit') ?? 40;
  516. $mutes = UserFilter::whereUserId($user->profile_id)
  517. ->whereFilterableType('App\Profile')
  518. ->whereFilterType('mute')
  519. ->simplePaginate($limit)
  520. ->pluck('filterable_id');
  521. $accounts = Profile::find($mutes);
  522. $fractal = new Fractal\Manager();
  523. $fractal->setSerializer(new ArraySerializer());
  524. $resource = new Fractal\Resource\Collection($accounts, new AccountTransformer());
  525. $res = $fractal->createData($resource)->toArray();
  526. $url = $request->url();
  527. $page = $request->input('page', 1);
  528. $next = $page < 40 ? $page + 1 : 40;
  529. $prev = $page > 1 ? $page - 1 : 1;
  530. $links = '<'.$url.'?page='.$next.'&limit='.$limit.'>; rel="next", <'.$url.'?page='.$prev.'&limit='.$limit.'>; rel="prev"';
  531. return response()->json($res, 200, ['Link' => $links]);
  532. }
  533. public function accountBlocks(Request $request)
  534. {
  535. abort_if(!$request->user(), 403);
  536. $this->validate($request, [
  537. 'limit' => 'nullable|integer|min:1|max:40',
  538. 'page' => 'nullable|integer|min:1|max:10'
  539. ]);
  540. $user = $request->user();
  541. $limit = $request->input('limit') ?? 40;
  542. $blocked = UserFilter::select('filterable_id','filterable_type','filter_type','user_id')
  543. ->whereUserId($user->profile_id)
  544. ->whereFilterableType('App\Profile')
  545. ->whereFilterType('block')
  546. ->simplePaginate($limit)
  547. ->pluck('filterable_id');
  548. $profiles = Profile::findOrFail($blocked);
  549. $fractal = new Fractal\Manager();
  550. $fractal->setSerializer(new ArraySerializer());
  551. $resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
  552. $res = $fractal->createData($resource)->toArray();
  553. $url = $request->url();
  554. $page = $request->input('page', 1);
  555. $next = $page < 40 ? $page + 1 : 40;
  556. $prev = $page > 1 ? $page - 1 : 1;
  557. $links = '<'.$url.'?page='.$next.'&limit='.$limit.'>; rel="next", <'.$url.'?page='.$prev.'&limit='.$limit.'>; rel="prev"';
  558. return response()->json($res, 200, ['Link' => $links]);
  559. }
  560. public function accountBlocksV2(Request $request)
  561. {
  562. return response()->json(UserFilterService::blocks($request->user()->profile_id), 200, [], JSON_UNESCAPED_SLASHES);
  563. }
  564. public function accountMutesV2(Request $request)
  565. {
  566. return response()->json(UserFilterService::mutes($request->user()->profile_id), 200, [], JSON_UNESCAPED_SLASHES);
  567. }
  568. public function accountFiltersV2(Request $request)
  569. {
  570. return response()->json(UserFilterService::filters($request->user()->profile_id), 200, [], JSON_UNESCAPED_SLASHES);
  571. }
  572. }