@ -2,27 +2,28 @@
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 )
{
{
abort_unless((bool) config_cache('instance.curated_registration.enabled'), 404);
if (! $allowWhenDisabled) {
abort_unless((bool) config_cache('instance.curated_registration.enabled'), 404);
if((bool) config_cache('pixelfed.open_registration')) {
abort_if(config('instance.curated_registration.state.only_enabled_on_closed_reg'), 404);
if ((bool) config_cache('pixelfed.open_registration')) {
abort_if(config('instance.curated_registration.state.only_enabled_on_closed_reg'), 404);
} else {
abort_unless(config('instance.curated_registration.state.fallback_on_closed_reg'), 404);
}
} 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);
}
}
@ -31,26 +32,32 @@ class CuratedRegisterController extends Controller
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') & &
@ -58,18 +65,20 @@ class CuratedRegisterController extends Controller
$crid = $request->session()->get('cur-reg-con.cr-id');
$crid = $request->session()->get('cur-reg-con.cr-id');
$arid = $request->session()->get('cur-reg-con.ac-id');
$arid = $request->session()->get('cur-reg-con.ac-id');
$showCaptcha = config('instance.curated_registration.captcha_enabled');
$showCaptcha = config('instance.curated_registration.captcha_enabled');
if($attempts = $request->session()->get('cur-reg-con-attempt')) {
if ($attempts = $request->session()->get('cur-reg-con-attempt')) {
$showCaptcha = $attempts & & $attempts >= 2;
$showCaptcha = $attempts & & $attempts >= 2;
} else {
} else {
$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,9 +89,9 @@ 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';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
}
@ -92,7 +101,7 @@ class CuratedRegisterController extends Controller
abort_if((string) $crid !== $request->input('crid'), 404);
abort_if((string) $crid !== $request->input('crid'), 404);
abort_if((string) $acid !== $request->input('acid'), 404);
abort_if((string) $acid !== $request->input('acid'), 404);
if(CuratedRegisterActivity::whereRegisterId($crid)->whereReplyToId($acid)->exists()) {
if (CuratedRegisterActivity::whereRegisterId($crid)->whereReplyToId($acid)->exists()) {
return redirect()->back()->withErrors(['code' => 'You already replied to this request.']);
return redirect()->back()->withErrors(['code' => 'You already replied to this request.']);
}
}
@ -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',
@ -124,7 +134,7 @@ class CuratedRegisterController extends Controller
'response' => 'required_if:action,message|string|min:20|max:1000',
'response' => 'required_if:action,message|string|min:20|max:1000',
];
];
$messages = [];
$messages = [];
if(config('instance.curated_registration.captcha_enabled')) {
if (config('instance.curated_registration.captcha_enabled')) {
$rules['h-captcha-response'] = 'required|captcha';
$rules['h-captcha-response'] = 'required|captcha';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
}
@ -139,11 +149,11 @@ class CuratedRegisterController extends Controller
$cr = CuratedRegister::whereIsClosed(false)->findOrFail($sid);
$cr = CuratedRegister::whereIsClosed(false)->findOrFail($sid);
$ac = CuratedRegisterActivity::whereRegisterId($cr->id)->whereFromAdmin(true)->findOrFail($id);
$ac = CuratedRegisterActivity::whereRegisterId($cr->id)->whereFromAdmin(true)->findOrFail($id);
if(!hash_equals($ac->secret_code, $code)) {
if (! hash_equals($ac->secret_code, $code)) {
return redirect()->back()->withErrors(['code' => 'Invalid code']);
return redirect()->back()->withErrors(['code' => 'Invalid code']);
}
}
if(!hash_equals($cr->email, $email)) {
if (! hash_equals($cr->email, $email)) {
return redirect()->back()->withErrors(['email' => 'Invalid email']);
return redirect()->back()->withErrors(['email' => 'Invalid email']);
}
}
@ -151,44 +161,58 @@ 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');
}
}
public function confirmEmail(Request $request)
public function confirmEmail(Request $request)
{
{
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');
}
}
public function emailConfirmed(Request $request)
public function emailConfirmed(Request $request)
{
{
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 = [];
if(config('instance.curated_registration.captcha_enabled')) {
if (config('instance.curated_registration.captcha_enabled')) {
$rules['h-captcha-response'] = 'required|captcha';
$rules['h-captcha-response'] = 'required|captcha';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
}
@ -196,7 +220,7 @@ class CuratedRegisterController extends Controller
$this->validate($request, $rules, $messages);
$this->validate($request, $rules, $messages);
$cur = CuratedRegister::whereEmail($request->input('email'))->whereIsClosed(false)->first();
$cur = CuratedRegister::whereEmail($request->input('email'))->whereIsClosed(false)->first();
if(!$cur) {
if (! $cur) {
return redirect()->back()->withErrors(['email' => 'The selected email is invalid.']);
return redirect()->back()->withErrors(['email' => 'The selected email is invalid.']);
}
}
@ -204,7 +228,7 @@ class CuratedRegisterController extends Controller
->whereType('user_resend_email_confirmation')
->whereType('user_resend_email_confirmation')
->count();
->count();
if($totalCount & & $totalCount >= config('instance.curated_registration.resend_confirmation_limit')) {
if ($totalCount & & $totalCount >= config('instance.curated_registration.resend_confirmation_limit')) {
return redirect()->back()->withErrors(['email' => 'You have re-attempted too many times. To proceed with your application, please < a href = "/site/contact" class = "text-white" style = "text-decoration: underline;" > contact the admin team< / a > .']);
return redirect()->back()->withErrors(['email' => 'You have re-attempted too many times. To proceed with your application, please < a href = "/site/contact" class = "text-white" style = "text-decoration: underline;" > contact the admin team< / a > .']);
}
}
@ -213,75 +237,92 @@ class CuratedRegisterController extends Controller
->where('created_at', '>', now()->subHours(12))
->where('created_at', '>', now()->subHours(12))
->count();
->count();
if($count) {
if ($count) {
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.']);
}
}
CuratedRegisterActivity::create([
DB::transaction(function () use ($cur) {
'register_id' => $cur->id,
$cur->verify_code = Str::random(40);
'type' => 'user_resend_email_confirmation',
$cur->created_at = now();
'admin_only_view' => true,
$cur->save();
'from_admin' => false,
'from_user' => false,
CuratedRegisterActivity::create([
'action_required' => false,
'register_id' => $cur->id,
]);
'type' => 'user_resend_email_confirmation',
'admin_only_view' => true,
'from_admin' => false,
'from_user' => false,
'action_required' => false,
]);
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')) {
$rules['h-captcha-response'] = 'required|captcha';
$rules['h-captcha-response'] = 'required|captcha';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
}
$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'));
}
}
if(!hash_equals($cr->verify_code, $request->input('code'))) {
if (! hash_equals($cr->verify_code, $request->input('code'))) {
return redirect(route('help.email-confirmation-issues'));
return redirect(route('help.email-confirmation-issues'));
}
}
$cr->email_verified_at = now();
$cr->email_verified_at = now();
$cr->save();
$cr->save();
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');
switch($step) {
switch ($step) {
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;
case 2:
case 2:
$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;
case 3:
case 3:
$this->stepThree($request);
$this->stepThree($request);
@ -289,27 +330,28 @@ 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;
}
}
}
}
protected function stepTwo($request)
protected function stepTwo($request)
{
{
if($request->filled('reason')) {
if ($request->filled('reason')) {
$request->session()->put('cur-reg.form-reason', $request->input('reason'));
$request->session()->put('cur-reg.form-reason', $request->input('reason'));
}
}
if($request->filled('username')) {
if ($request->filled('username')) {
$request->session()->put('cur-reg.form-username', $request->input('username'));
$request->session()->put('cur-reg.form-username', $request->input('username'));
}
}
if($request->filled('email')) {
if ($request->filled('email')) {
$request->session()->put('cur-reg.form-email', $request->input('email'));
$request->session()->put('cur-reg.form-email', $request->input('email'));
}
}
$this->validate($request, [
$this->validate($request, [
'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) {
@ -317,24 +359,24 @@ class CuratedRegisterController extends Controller
$underscore = substr_count($value, '_');
$underscore = substr_count($value, '_');
$period = substr_count($value, '.');
$period = substr_count($value, '.');
if(ends_with($value, ['.php', '.js', '.css'])) {
if (ends_with($value, ['.php', '.js', '.css'])) {
return $fail('Username is invalid.');
return $fail('Username is invalid.');
}
}
if(($dash + $underscore + $period) > 1) {
if (($dash + $underscore + $period) > 1) {
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
}
}
if (!ctype_alnum($value[0])) {
if (! ctype_alnum($value[0])) {
return $fail('Username is invalid. Must start with a letter or number.');
return $fail('Username is invalid. Must start with a letter or number.');
}
}
if (!ctype_alnum($value[strlen($value) - 1])) {
if (! ctype_alnum($value[strlen($value) - 1])) {
return $fail('Username is invalid. Must end with a letter or number.');
return $fail('Username is invalid. Must end with a letter or number.');
}
}
$val = str_replace(['_', '.', '-'], '', $value);
$val = str_replace(['_', '.', '-'], '', $value);
if(!ctype_alnum($val)) {
if (! ctype_alnum($val)) {
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
}
}
@ -353,7 +395,7 @@ class CuratedRegisterController extends Controller
'unique:curated_registers',
'unique:curated_registers',
function ($attribute, $value, $fail) {
function ($attribute, $value, $fail) {
$banned = EmailService::isBanned($value);
$banned = EmailService::isBanned($value);
if($banned) {
if ($banned) {
return $fail('Email is invalid.');
return $fail('Email is invalid.');
}
}
},
},
@ -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'));
@ -379,11 +421,11 @@ class CuratedRegisterController extends Controller
'unique:curated_registers',
'unique:curated_registers',
function ($attribute, $value, $fail) {
function ($attribute, $value, $fail) {
$banned = EmailService::isBanned($value);
$banned = EmailService::isBanned($value);
if($banned) {
if ($banned) {
return $fail('Email is invalid.');
return $fail('Email is invalid.');
}
}
},
},
]
],
]);
]);
$cr = new CuratedRegister;
$cr = new CuratedRegister;
$cr->email = $request->email;
$cr->email = $request->email;