diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ace1fde..bb3c238a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.1...dev) +### Added +- Manual email verification requests. ([bc659387](https://github.com/pixelfed/pixelfed/commit/bc659387)) + ### Updated - Updated NotificationService, fix 500 bug. ([4a609dc3](https://github.com/pixelfed/pixelfed/commit/4a609dc3)) - Updated HttpSignatures, update instance actor headers. Fixes #2935. ([a900de21](https://github.com/pixelfed/pixelfed/commit/a900de21)) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 1103900fa..edbf6a4cc 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -75,7 +75,10 @@ class AccountController extends Controller public function verifyEmail(Request $request) { - return view('account.verify_email'); + $recentSent = EmailVerification::whereUserId(Auth::id()) + ->whereDate('created_at', '>', now()->subHours(12))->count(); + + return view('account.verify_email', compact('recentSent')); } public function sendVerifyEmail(Request $request) diff --git a/app/Http/Controllers/Admin/AdminReportController.php b/app/Http/Controllers/Admin/AdminReportController.php index 2af1b2bee..d2625acff 100644 --- a/app/Http/Controllers/Admin/AdminReportController.php +++ b/app/Http/Controllers/Admin/AdminReportController.php @@ -4,8 +4,12 @@ namespace App\Http\Controllers\Admin; use Cache; use App\Report; +use App\User; use Carbon\Carbon; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redis; +use App\Services\AccountService; +use App\Services\StatusService; trait AdminReportController { @@ -33,6 +37,7 @@ trait AdminReportController $report = Report::findOrFail($id); $this->handleReportAction($report, $action); + Cache::forget('admin-dash:reports:list-cache'); return response()->json(['msg'=> 'Success']); } @@ -52,17 +57,20 @@ trait AdminReportController $item->is_nsfw = true; $item->save(); $report->nsfw = true; + StatusService::del($item->id); break; case 'unlist': $item->visibility = 'unlisted'; $item->save(); Cache::forget('profiles:private'); + StatusService::del($item->id); break; case 'delete': // Todo: fire delete job $report->admin_seen = null; + StatusService::del($item->id); break; case 'shadowban': @@ -115,4 +123,55 @@ trait AdminReportController ]; return response()->json($res); } + + public function reportMailVerifications(Request $request) + { + $ids = Redis::smembers('email:manual'); + $ignored = Redis::smembers('email:manual-ignored'); + $reports = []; + if($ids) { + $reports = collect($ids) + ->filter(function($id) use($ignored) { + return !in_array($id, $ignored); + }) + ->map(function($id) { + $account = AccountService::get($id); + $user = User::whereProfileId($id)->first(); + if(!$user) { + return []; + } + $account['email'] = $user->email; + return $account; + }) + ->filter(function($res) { + return isset($res['id']); + }) + ->values(); + } + return view('admin.reports.mail_verification', compact('reports', 'ignored')); + } + + public function reportMailVerifyIgnore(Request $request) + { + $id = $request->input('id'); + Redis::sadd('email:manual-ignored', $id); + return redirect('/i/admin/reports'); + } + + public function reportMailVerifyApprove(Request $request) + { + $id = $request->input('id'); + $user = User::whereProfileId($id)->firstOrFail(); + Redis::srem('email:manual', $id); + Redis::srem('email:manual-ignored', $id); + $user->email_verified_at = now(); + $user->save(); + return redirect('/i/admin/reports'); + } + + public function reportMailVerifyClearIgnored(Request $request) + { + Redis::del('email:manual-ignored'); + return [200]; + } } diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 8c9d7e218..f31bc823a 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -17,6 +17,7 @@ use App\{ use DB, Cache; use Carbon\Carbon; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redis; use App\Http\Controllers\Admin\{ AdminDiscoverController, AdminInstanceController, @@ -28,12 +29,13 @@ use App\Http\Controllers\Admin\{ }; use Illuminate\Validation\Rule; use App\Services\AdminStatsService; +use App\Services\StatusService; use App\Services\StoryService; class AdminController extends Controller { use AdminReportController, - AdminDiscoverController, + AdminDiscoverController, AdminMediaController, AdminSettingsController, AdminInstanceController, @@ -54,9 +56,15 @@ class AdminController extends Controller public function statuses(Request $request) { - $statuses = Status::orderBy('id', 'desc')->simplePaginate(10); - - return view('admin.statuses.home', compact('statuses')); + $statuses = Status::orderBy('id', 'desc')->cursorPaginate(10); + $data = $statuses->map(function($status) { + return StatusService::get($status->id, false); + }) + ->filter(function($s) { + return $s; + }) + ->toArray(); + return view('admin.statuses.home', compact('statuses', 'data')); } public function showStatus(Request $request, $id) @@ -69,17 +77,45 @@ class AdminController extends Controller public function reports(Request $request) { $filter = $request->input('filter') == 'closed' ? 'closed' : 'open'; - $reports = Report::whereHas('status') - ->whereHas('reportedUser') - ->whereHas('reporter') - ->orderBy('created_at','desc') - ->when($filter, function($q, $filter) { - return $filter == 'open' ? - $q->whereNull('admin_seen') : - $q->whereNotNull('admin_seen'); - }) - ->paginate(6); - return view('admin.reports.home', compact('reports')); + $page = $request->input('page') ?? 1; + + $ai = Cache::remember('admin-dash:reports:ai-count', 3600, function() { + return AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count(); + }); + + $spam = Cache::remember('admin-dash:reports:spam-count', 3600, function() { + return AccountInterstitial::whereType('post.autospam')->whereNull('appeal_handled_at')->count(); + }); + + $mailVerifications = Redis::scard('email:manual'); + + if($filter == 'open' && $page == 1) { + $reports = Cache::remember('admin-dash:reports:list-cache', 300, function() use($page, $filter) { + return Report::whereHas('status') + ->whereHas('reportedUser') + ->whereHas('reporter') + ->orderBy('created_at','desc') + ->when($filter, function($q, $filter) { + return $filter == 'open' ? + $q->whereNull('admin_seen') : + $q->whereNotNull('admin_seen'); + }) + ->paginate(6); + }); + } else { + $reports = Report::whereHas('status') + ->whereHas('reportedUser') + ->whereHas('reporter') + ->orderBy('created_at','desc') + ->when($filter, function($q, $filter) { + return $filter == 'open' ? + $q->whereNull('admin_seen') : + $q->whereNotNull('admin_seen'); + }) + ->paginate(6); + } + + return view('admin.reports.home', compact('reports', 'ai', 'spam', 'mailVerifications')); } public function showReport(Request $request, $id) @@ -143,7 +179,7 @@ class AdminController extends Controller Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id); Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id); - + Cache::forget('admin-dash:reports:spam-count'); return redirect('/i/admin/reports/autospam'); } @@ -156,8 +192,11 @@ class AdminController extends Controller $appeal->appeal_handled_at = now(); $appeal->save(); + StatusService::del($status->id); + Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id); Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id); + Cache::forget('admin-dash:reports:spam-count'); return redirect('/i/admin/reports/autospam'); } @@ -176,7 +215,7 @@ class AdminController extends Controller if($action == 'dismiss') { $appeal->appeal_handled_at = now(); $appeal->save(); - + Cache::forget('admin-dash:reports:ai-count'); return redirect('/i/admin/reports/appeals'); } @@ -201,6 +240,8 @@ class AdminController extends Controller $appeal->appeal_handled_at = now(); $appeal->save(); + StatusService::del($status->id); + Cache::forget('admin-dash:reports:ai-count'); return redirect('/i/admin/reports/appeals'); } diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 23bb687ba..b9f86a639 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\{ AccountInterstitial, + Bookmark, DirectMessage, DiscoverCategory, Hashtag, @@ -19,6 +20,7 @@ use App\{ UserFilter, }; use Auth,Cache; +use Illuminate\Support\Facades\Redis; use Carbon\Carbon; use League\Fractal; use App\Transformer\Api\{ @@ -345,14 +347,18 @@ class InternalApiController extends Controller public function bookmarks(Request $request) { - $statuses = Auth::user()->profile - ->bookmarks() - ->withCount(['likes','comments']) - ->orderBy('created_at', 'desc') - ->simplePaginate(10); - - $resource = new Fractal\Resource\Collection($statuses, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + $res = Bookmark::whereProfileId($request->user()->profile_id) + ->orderByDesc('created_at') + ->simplePaginate(10) + ->map(function($bookmark) { + $status = StatusService::get($bookmark->status_id); + $status['bookmarked_at'] = $bookmark->created_at->format('c'); + return $status; + }) + ->filter(function($bookmark) { + return isset($bookmark['id']); + }) + ->values(); return response()->json($res); } @@ -456,4 +462,18 @@ class InternalApiController extends Controller $template = $status->in_reply_to_id ? 'status.reply' : 'status.remote'; return view($template, compact('user', 'status')); } + + public function requestEmailVerification(Request $request) + { + $pid = $request->user()->profile_id; + $exists = Redis::sismember('email:manual', $pid); + return view('account.email.request_verification', compact('exists')); + } + + public function requestEmailVerificationStore(Request $request) + { + $pid = $request->user()->profile_id; + Redis::sadd('email:manual', $pid); + return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']); + } } diff --git a/app/Http/Middleware/EmailVerificationCheck.php b/app/Http/Middleware/EmailVerificationCheck.php index 1dbb17e90..e3d278c34 100644 --- a/app/Http/Middleware/EmailVerificationCheck.php +++ b/app/Http/Middleware/EmailVerificationCheck.php @@ -21,7 +21,7 @@ class EmailVerificationCheck is_null($request->user()->email_verified_at) && !$request->is( 'i/auth/*', - 'i/verify-email', + 'i/verify-email*', 'log*', 'site*', 'i/confirm-email/*', diff --git a/resources/views/account/email/request_verification.blade.php b/resources/views/account/email/request_verification.blade.php new file mode 100644 index 000000000..a4d641597 --- /dev/null +++ b/resources/views/account/email/request_verification.blade.php @@ -0,0 +1,37 @@ +@extends('layouts.app') + +@section('content') +
{{ session('status') }}
+{{ session('error') }}
+If you are experiencing issues receiving your email address confirmation code to the email address you registered with, you can request manual verification as a last resort. An administrator will review your request.
+ +If you request manual email verification, you still may experience issues recieving emails from our service, including password reset requests.
+ +In the event you need to reset your password and do not receive the password reset email, please contact the administrators.
+ + @if(!$exists) + + @else + + @endif +{{ session('error') }}
@endif + + @if(Auth::user()->email_verified_at) +Your email is already verified. Click here to go home.
+ @elseYou need to confirm your email address ({{Auth::user()->email}}) before you can proceed.
-You can change your email address here.
-If you don't recieve an email within 30 minutes, you can contact the administrator.
-You need to confirm your email address {{Auth::user()->email}} before you can proceed.
+ @if(!$recentSent) + @else + + @endif +Click here to change your email address.
If you are experiencing issues receiving your email confirmation, you can request manual verification.
+{{$mailVerifications}}
+ Email Verify {{$mailVerifications == 1 ? 'Request' : 'Requests'}} +{{$ai}}
Appeal {{$ai == 1 ? 'Request' : 'Requests'}} diff --git a/resources/views/admin/reports/mail_verification.blade.php b/resources/views/admin/reports/mail_verification.blade.php new file mode 100644 index 000000000..35ac8ccba --- /dev/null +++ b/resources/views/admin/reports/mail_verification.blade.php @@ -0,0 +1,75 @@ +@extends('admin.partial.template-full') + +@section('section') ++ You are ignoring {{ count($ignored) }} mail verification requests. Clear ignored requests +
+ @endif +{{ $report['username'] }}
+{{ $report['email'] }}
+No email verification requests found!
+{{ $error }} + @endforeach +
- @if(Auth::user()->email_verified_at) - Verified {{Auth::user()->email_verified_at->diffForHumans()}} - @else - Unverified You need to verify your email. - @endif -
-{{ $error }} + @endforeach +
+ To register a new device, you have to remove any active devices. +
+ ++ Added {{$user->{'2fa_setup_at'}->diffForHumans()}} +
+- To register a new device, you have to remove any active devices. -
- -- Added {{$user->{'2fa_setup_at'}->diffForHumans()}} -
-- Each code can only be used once. -
-{{$code}}
Generate more recovery codes and store them in a safe place.
--
- ++ Each code can only be used once. +
+{{$code}}
Generate more recovery codes and store them in a safe place.
++
+ +You will need to install a compatible mobile app, we recommend the following apps:
+Please scan the QR code and then enter the 6 digit code in the form below. Keep in mind the code changes every 30 seconds, and is only good for 1 minute.
+QR Code
+ +OTP Secret
+ +Please store the following codes in a safe place, each backup code can be used only once if you do not have access to your 2FA mobile app.
+ +
+ @foreach($backups as $code)
+ {{$code}}
+ @endforeach
+
+ You will need to install a compatible mobile app, we recommend the following apps:
-Please scan the QR code and then enter the 6 digit code in the form below. Keep in mind the code changes every 30 seconds, and is only good for 1 minute.
-QR Code
- -OTP Secret
- -Please store the following codes in a safe place, each backup code can be used only once if you do not have access to your 2FA mobile app.
- -
- @foreach($backups as $code)
- {{$code}}
- @endforeach
-
-