Merge pull request #5716 from pixelfed/staging

Update Curated Onboarding, add new app:curated-onboarding command, extend email verification window to 7 days and fix resend verification mails
pull/5729/head
daniel 5 months ago committed by GitHub
commit 97c43f2d75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -41,6 +41,7 @@
- Fix GroupController, move groups enabled check to each method to fix route:list ([f260572e](https://github.com/pixelfed/pixelfed/commit/f260572e)) - Fix GroupController, move groups enabled check to each method to fix route:list ([f260572e](https://github.com/pixelfed/pixelfed/commit/f260572e))
- Update MediaStorageService, handle local media deletes after successful S3 upload ([280f63dc](https://github.com/pixelfed/pixelfed/commit/280f63dc)) - Update MediaStorageService, handle local media deletes after successful S3 upload ([280f63dc](https://github.com/pixelfed/pixelfed/commit/280f63dc))
- Update status twitter:card to summary_large_image for images/albums ([9a5a9f55](https://github.com/pixelfed/pixelfed/commit/9a5a9f55)) - Update status twitter:card to summary_large_image for images/albums ([9a5a9f55](https://github.com/pixelfed/pixelfed/commit/9a5a9f55))
- Update CuratedOnboarding, add new app:curated-onboarding command, extend email verification window to 7 days and fix resend verification mails ([49604210](https://github.com/pixelfed/pixelfed/commit/49604210))
- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev)

@ -0,0 +1,170 @@
<?php
namespace App\Console\Commands;
use App\Mail\CuratedRegisterConfirmEmail;
use App\Models\CuratedRegister;
use App\Models\CuratedRegisterActivity;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\search;
use function Laravel\Prompts\select;
use function Laravel\Prompts\table;
class CuratedOnboardingCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:curated-onboarding';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Manage curated onboarding applications';
/**
* Execute the console command.
*/
public function handle()
{
$this->line(' ');
$this->info(' Welcome to the Curated Onboarding manager');
$this->line(' ');
$action = select(
label: 'Select an action:',
options: ['Stats', 'Edit'],
default: 'Stats',
hint: 'You can manage this via the admin dashboard.'
);
switch ($action) {
case 'Stats':
return $this->stats();
break;
case 'Edit':
return $this->edit();
break;
default:
exit;
break;
}
}
protected function stats()
{
$total = CuratedRegister::count();
$approved = CuratedRegister::whereIsApproved(true)->whereIsRejected(false)->whereNotNull('email_verified_at')->count();
$awaitingMoreInfo = CuratedRegister::whereIsAwaitingMoreInfo(true)->whereIsRejected(false)->whereIsClosed(false)->whereNotNull('email_verified_at')->count();
$open = CuratedRegister::whereIsApproved(false)->whereIsRejected(false)->whereIsClosed(false)->whereNotNull('email_verified_at')->whereIsAwaitingMoreInfo(false)->count();
$nonVerified = CuratedRegister::whereIsApproved(false)->whereIsRejected(false)->whereIsClosed(false)->whereNull('email_verified_at')->whereIsAwaitingMoreInfo(false)->count();
table(
['Total', 'Approved', 'Open', 'Awaiting More Info', 'Unverified Emails'],
[
[$total, $approved, $open, $awaitingMoreInfo, $nonVerified],
]
);
}
protected function edit()
{
$id = search(
label: 'Search for a username or email',
options: fn (string $value) => strlen($value) > 0
? CuratedRegister::where(function ($query) use ($value) {
$query->whereLike('username', "%{$value}%")
->orWhereLike('email', "%{$value}%");
})->get()
->mapWithKeys(fn ($user) => [
$user->id => "{$user->username} ({$user->email})",
])
->all()
: []
);
$register = CuratedRegister::findOrFail($id);
if ($register->is_approved) {
$status = 'Approved';
} elseif ($register->is_rejected) {
$status = 'Rejected';
} elseif ($register->is_closed) {
$status = 'Closed';
} elseif ($register->is_awaiting_more_info) {
$status = 'Awaiting more info';
} elseif ($register->user_has_responded) {
$status = 'Awaiting Admin Response';
} else {
$status = 'Unknown';
}
table(
['Field', 'Value'],
[
['ID', $register->id],
['Username', $register->username],
['Email', $register->email],
['Status', $status],
['Created At', $register->created_at->format('Y-m-d H:i')],
['Updated At', $register->updated_at->format('Y-m-d H:i')],
]
);
if (in_array($status, ['Approved', 'Rejected', 'Closed'])) {
return;
}
$options = ['Cancel', 'Delete'];
if ($register->email_verified_at == null) {
$options[] = 'Resend Email Verification';
}
$action = select(
label: 'Select an action:',
options: $options,
default: 'Cancel',
);
if ($action === 'Resend Email Verification') {
$confirmed = confirm('Are you sure you want to send another email to '.$register->email.' ?');
if (! $confirmed) {
$this->error('Aborting...');
exit;
}
DB::transaction(function () use ($register) {
$register->verify_code = Str::random(40);
$register->created_at = now();
$register->save();
Mail::to($register->email)->send(new CuratedRegisterConfirmEmail($register));
$this->info('Mail sent!');
});
} elseif ($action === 'Delete') {
$confirmed = confirm('Are you sure you want to delete the application from '.$register->email.' ?');
if (! $confirmed) {
$this->error('Aborting...');
exit;
}
DB::transaction(function () use ($register) {
CuratedRegisterActivity::whereRegisterId($register->id)->delete();
$register->delete();
$this->info('Successfully deleted!');
});
} else {
$this->info('Cancelled.');
exit;
}
}
}

@ -111,7 +111,7 @@ class RegisterController extends Controller
$emailRules = [ $emailRules = [
'required', 'required',
'string', 'string',
'email', 'email:rfc,dns,spoof',
'max:255', 'max:255',
'unique:users', 'unique:users',
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {

@ -2,23 +2,21 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\Jobs\CuratedOnboarding\CuratedOnboardingNotifyAdminNewApplicationPipeline;
use Illuminate\Support\Str; use App\Mail\CuratedRegisterConfirmEmail;
use App\User;
use App\Models\CuratedRegister; use App\Models\CuratedRegister;
use App\Models\CuratedRegisterActivity; use App\Models\CuratedRegisterActivity;
use App\Services\EmailService; use App\Services\EmailService;
use App\Services\BouncerService;
use App\Util\Lexer\RestrictedNames; use App\Util\Lexer\RestrictedNames;
use App\Mail\CuratedRegisterConfirmEmail; use Illuminate\Http\Request;
use App\Mail\CuratedRegisterNotifyAdmin;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use App\Jobs\CuratedOnboarding\CuratedOnboardingNotifyAdminNewApplicationPipeline; use Illuminate\Support\Str;
class CuratedRegisterController extends Controller class CuratedRegisterController extends Controller
{ {
public function __construct() public function preCheck($allowWhenDisabled = false)
{ {
if (! $allowWhenDisabled) {
abort_unless((bool) config_cache('instance.curated_registration.enabled'), 404); abort_unless((bool) config_cache('instance.curated_registration.enabled'), 404);
if ((bool) config_cache('pixelfed.open_registration')) { if ((bool) config_cache('pixelfed.open_registration')) {
@ -26,31 +24,40 @@ class CuratedRegisterController extends Controller
} else { } else {
abort_unless(config('instance.curated_registration.state.fallback_on_closed_reg'), 404); abort_unless(config('instance.curated_registration.state.fallback_on_closed_reg'), 404);
} }
} else {
abort_unless(config('instance.curated_registration.state.fallback_on_closed_reg'), 404);
}
} }
public function index(Request $request) public function index(Request $request)
{ {
abort_if($request->user(), 404); abort_if($request->user(), 404);
return view('auth.curated-register.index', ['step' => 1]); return view('auth.curated-register.index', ['step' => 1]);
} }
public function concierge(Request $request) public function concierge(Request $request)
{ {
abort_if($request->user(), 404); abort_if($request->user(), 404);
$this->preCheck(true);
$emailConfirmed = $request->session()->has('cur-reg-con.email-confirmed') && $emailConfirmed = $request->session()->has('cur-reg-con.email-confirmed') &&
$request->has('next') && $request->has('next') &&
$request->session()->has('cur-reg-con.cr-id'); $request->session()->has('cur-reg-con.cr-id');
return view('auth.curated-register.concierge', compact('emailConfirmed')); return view('auth.curated-register.concierge', compact('emailConfirmed'));
} }
public function conciergeResponseSent(Request $request) public function conciergeResponseSent(Request $request)
{ {
$this->preCheck(true);
return view('auth.curated-register.user_response_sent'); return view('auth.curated-register.user_response_sent');
} }
public function conciergeFormShow(Request $request) public function conciergeFormShow(Request $request)
{ {
abort_if($request->user(), 404); abort_if($request->user(), 404);
$this->preCheck(true);
abort_unless( abort_unless(
$request->session()->has('cur-reg-con.email-confirmed') && $request->session()->has('cur-reg-con.email-confirmed') &&
$request->session()->has('cur-reg-con.cr-id') && $request->session()->has('cur-reg-con.cr-id') &&
@ -64,12 +71,14 @@ class CuratedRegisterController extends Controller
$showCaptcha = false; $showCaptcha = false;
} }
$activity = CuratedRegisterActivity::whereRegisterId($crid)->whereFromAdmin(true)->findOrFail($arid); $activity = CuratedRegisterActivity::whereRegisterId($crid)->whereFromAdmin(true)->findOrFail($arid);
return view('auth.curated-register.concierge_form', compact('activity', 'showCaptcha')); return view('auth.curated-register.concierge_form', compact('activity', 'showCaptcha'));
} }
public function conciergeFormStore(Request $request) public function conciergeFormStore(Request $request)
{ {
abort_if($request->user(), 404); abort_if($request->user(), 404);
$this->preCheck(true);
$request->session()->increment('cur-reg-con-attempt'); $request->session()->increment('cur-reg-con-attempt');
abort_unless( abort_unless(
$request->session()->has('cur-reg-con.email-confirmed') && $request->session()->has('cur-reg-con.email-confirmed') &&
@ -80,7 +89,7 @@ class CuratedRegisterController extends Controller
$rules = [ $rules = [
'response' => 'required|string|min:5|max:1000', 'response' => 'required|string|min:5|max:1000',
'crid' => 'required|integer|min:1', 'crid' => 'required|integer|min:1',
'acid' => 'required|integer|min:1' 'acid' => 'required|integer|min:1',
]; ];
if (config('instance.curated_registration.captcha_enabled') && $attempts >= 3) { if (config('instance.curated_registration.captcha_enabled') && $attempts >= 3) {
$rules['h-captcha-response'] = 'required|captcha'; $rules['h-captcha-response'] = 'required|captcha';
@ -115,6 +124,7 @@ class CuratedRegisterController extends Controller
public function conciergeStore(Request $request) public function conciergeStore(Request $request)
{ {
abort_if($request->user(), 404); abort_if($request->user(), 404);
$this->preCheck(true);
$rules = [ $rules = [
'sid' => 'required_if:action,email|integer|min:1|max:20000000', 'sid' => 'required_if:action,email|integer|min:1|max:20000000',
'id' => 'required_if:action,email|integer|min:1|max:20000000', 'id' => 'required_if:action,email|integer|min:1|max:20000000',
@ -151,6 +161,7 @@ class CuratedRegisterController extends Controller
$request->session()->put('cur-reg-con.cr-id', $cr->id); $request->session()->put('cur-reg-con.cr-id', $cr->id);
$request->session()->put('cur-reg-con.ac-id', $ac->id); $request->session()->put('cur-reg-con.ac-id', $ac->id);
$emailConfirmed = true; $emailConfirmed = true;
return redirect('/auth/sign_up/concierge/form'); return redirect('/auth/sign_up/concierge/form');
} }
@ -159,6 +170,8 @@ class CuratedRegisterController extends Controller
if ($request->user()) { if ($request->user()) {
return redirect(route('help.email-confirmation-issues')); return redirect(route('help.email-confirmation-issues'));
} }
$this->preCheck(true);
return view('auth.curated-register.confirm_email'); return view('auth.curated-register.confirm_email');
} }
@ -167,23 +180,34 @@ class CuratedRegisterController extends Controller
if ($request->user()) { if ($request->user()) {
return redirect(route('help.email-confirmation-issues')); return redirect(route('help.email-confirmation-issues'));
} }
$this->preCheck(true);
return view('auth.curated-register.email_confirmed'); return view('auth.curated-register.email_confirmed');
} }
public function resendConfirmation(Request $request) public function resendConfirmation(Request $request)
{ {
if ($request->user()) {
return redirect(route('help.email-confirmation-issues'));
}
$this->preCheck(true);
return view('auth.curated-register.resend-confirmation'); return view('auth.curated-register.resend-confirmation');
} }
public function resendConfirmationProcess(Request $request) public function resendConfirmationProcess(Request $request)
{ {
if ($request->user()) {
return redirect(route('help.email-confirmation-issues'));
}
$this->preCheck(true);
$rules = [ $rules = [
'email' => [ 'email' => [
'required', 'required',
'string', 'string',
app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email', app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
'exists:curated_registers', 'exists:curated_registers',
] ],
]; ];
$messages = []; $messages = [];
@ -217,6 +241,11 @@ class CuratedRegisterController extends Controller
return redirect()->back()->withErrors(['email' => 'You can only re-send the confirmation email once per 12 hours. Try again later.']); return redirect()->back()->withErrors(['email' => 'You can only re-send the confirmation email once per 12 hours. Try again later.']);
} }
DB::transaction(function () use ($cur) {
$cur->verify_code = Str::random(40);
$cur->created_at = now();
$cur->save();
CuratedRegisterActivity::create([ CuratedRegisterActivity::create([
'register_id' => $cur->id, 'register_id' => $cur->id,
'type' => 'user_resend_email_confirmation', 'type' => 'user_resend_email_confirmation',
@ -227,15 +256,20 @@ class CuratedRegisterController extends Controller
]); ]);
Mail::to($cur->email)->send(new CuratedRegisterConfirmEmail($cur)); Mail::to($cur->email)->send(new CuratedRegisterConfirmEmail($cur));
});
return view('auth.curated-register.resent-confirmation'); return view('auth.curated-register.resent-confirmation');
return $request->all();
} }
public function confirmEmailHandle(Request $request) public function confirmEmailHandle(Request $request)
{ {
if ($request->user()) {
return redirect(route('help.email-confirmation-issues'));
}
$this->preCheck(true);
$rules = [ $rules = [
'sid' => 'required', 'sid' => 'required',
'code' => 'required' 'code' => 'required',
]; ];
$messages = []; $messages = [];
if (config('instance.curated_registration.captcha_enabled')) { if (config('instance.curated_registration.captcha_enabled')) {
@ -245,7 +279,7 @@ class CuratedRegisterController extends Controller
$this->validate($request, $rules, $messages); $this->validate($request, $rules, $messages);
$cr = CuratedRegister::whereNull('email_verified_at') $cr = CuratedRegister::whereNull('email_verified_at')
->where('created_at', '>', now()->subHours(24)) ->where('created_at', '>', now()->subDays(7))
->find($request->input('sid')); ->find($request->input('sid'));
if (! $cr) { if (! $cr) {
return redirect(route('help.email-confirmation-issues')); return redirect(route('help.email-confirmation-issues'));
@ -259,13 +293,18 @@ class CuratedRegisterController extends Controller
if (config('instance.curated_registration.notify.admin.on_verify_email.enabled')) { if (config('instance.curated_registration.notify.admin.on_verify_email.enabled')) {
CuratedOnboardingNotifyAdminNewApplicationPipeline::dispatch($cr); CuratedOnboardingNotifyAdminNewApplicationPipeline::dispatch($cr);
} }
return view('auth.curated-register.email_confirmed'); return view('auth.curated-register.email_confirmed');
} }
public function proceed(Request $request) public function proceed(Request $request)
{ {
if ($request->user()) {
return redirect(route('help.email-confirmation-issues'));
}
$this->preCheck(false);
$this->validate($request, [ $this->validate($request, [
'step' => 'required|integer|in:1,2,3,4' 'step' => 'required|integer|in:1,2,3,4',
]); ]);
$step = $request->input('step'); $step = $request->input('step');
@ -273,6 +312,7 @@ class CuratedRegisterController extends Controller
case 1: case 1:
$step = 2; $step = 2;
$request->session()->put('cur-step', 1); $request->session()->put('cur-step', 1);
return view('auth.curated-register.index', compact('step')); return view('auth.curated-register.index', compact('step'));
break; break;
@ -280,6 +320,7 @@ class CuratedRegisterController extends Controller
$this->stepTwo($request); $this->stepTwo($request);
$step = 3; $step = 3;
$request->session()->put('cur-step', 2); $request->session()->put('cur-step', 2);
return view('auth.curated-register.index', compact('step')); return view('auth.curated-register.index', compact('step'));
break; break;
@ -289,6 +330,7 @@ class CuratedRegisterController extends Controller
$request->session()->put('cur-step', 3); $request->session()->put('cur-step', 3);
$verifiedEmail = true; $verifiedEmail = true;
$request->session()->pull('cur-reg'); $request->session()->pull('cur-reg');
return view('auth.curated-register.index', compact('step', 'verifiedEmail')); return view('auth.curated-register.index', compact('step', 'verifiedEmail'));
break; break;
} }
@ -309,7 +351,7 @@ class CuratedRegisterController extends Controller
'username' => [ 'username' => [
'required', 'required',
'min:2', 'min:2',
'max:15', 'max:30',
'unique:curated_registers', 'unique:curated_registers',
'unique:users', 'unique:users',
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
@ -361,7 +403,7 @@ class CuratedRegisterController extends Controller
'password' => 'required|min:8', 'password' => 'required|min:8',
'password_confirmation' => 'required|same:password', 'password_confirmation' => 'required|same:password',
'reason' => 'required|min:20|max:1000', 'reason' => 'required|min:20|max:1000',
'agree' => 'required|accepted' 'agree' => 'required|accepted',
]); ]);
$request->session()->put('cur-reg.form-email', $request->input('email')); $request->session()->put('cur-reg.form-email', $request->input('email'));
$request->session()->put('cur-reg.form-password', $request->input('password')); $request->session()->put('cur-reg.form-password', $request->input('password'));
@ -383,7 +425,7 @@ class CuratedRegisterController extends Controller
return $fail('Email is invalid.'); return $fail('Email is invalid.');
} }
}, },
] ],
]); ]);
$cr = new CuratedRegister; $cr = new CuratedRegister;
$cr->email = $request->email; $cr->email = $request->email;

@ -10,7 +10,7 @@ Please confirm your email address so we can process your new registration applic
</x-mail::button> </x-mail::button>
<p style="font-size:10pt;">If you did not create this account, please disregard this email. This link expires after 24 hours.</p> <p style="font-size:10pt;">If you did not create this account, please disregard this email. This link expires in 7 days.</p>
<br> <br>
Thanks,<br> Thanks,<br>

Loading…
Cancel
Save