From d76f01685ce34bc7119c1220c1a4a6f9146cee3c Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 02:03:39 -0700 Subject: [PATCH 1/9] Update login view, add email prefill logic --- resources/views/auth/login.blade.php | 59 ++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 3403cd6b3..af6c506e1 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -4,8 +4,12 @@
-
-
{{ __('Login') }}
+
+
+

+ Account Login +

+
@@ -14,6 +18,7 @@
+ @if ($errors->has('email')) @@ -27,6 +32,7 @@
+ @if ($errors->has('password')) @@ -34,6 +40,12 @@ {{ $errors->first('password') }} @endif + +

+ + {{ __('Forgot Password') }} + +

@@ -64,14 +76,9 @@
@endif -
-
- - -
-
+ @if( @@ -91,20 +98,38 @@ @endif + @if(config_cache('pixelfed.open_registration'))
-

- @if(config_cache('pixelfed.open_registration')) +

Register - ยท - @endif - - {{ __('Forgot Password') }} -

+ @endif
@endsection + +@push('scripts') + +@endpush From 74423b52ca79611cb99c6dc5fc2e58a8bdd2d4db Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 02:05:01 -0700 Subject: [PATCH 2/9] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06c1a9720..dcedd4896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ - Update FollowerService, add $silent param to remove method to more efficently purge relationships ([1664a5bc](https://github.com/pixelfed/pixelfed/commit/1664a5bc)) - Update AP ProfileTransformer, add published attribute ([adfaa2b1](https://github.com/pixelfed/pixelfed/commit/adfaa2b1)) - Update meta tags, improve descriptions and seo/og tags ([fd44c80c](https://github.com/pixelfed/pixelfed/commit/fd44c80c)) +- Update login view, add email prefill logic ([d76f0168](https://github.com/pixelfed/pixelfed/commit/d76f0168)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9) From 0325e171155c1e91ce946145a88bb3087ab61e98 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 03:06:14 -0700 Subject: [PATCH 3/9] Update LoginController, fix captcha validation error message --- app/Http/Controllers/Auth/LoginController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 3861d3272..627a879cc 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -71,6 +71,7 @@ class LoginController extends Controller $this->username() => 'required|email', 'password' => 'required|string|min:6', ]; + $messages = []; if( config('captcha.enabled') || @@ -82,9 +83,9 @@ class LoginController extends Controller ) ) { $rules['h-captcha-response'] = 'required|filled|captcha|min:5'; + $messages['h-captcha-response.required'] = 'The captcha must be filled'; } - - $this->validate($request, $rules); + $request->validate($rules, $messages); } /** From 67c650b1950151678974798912177d40c1794453 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 05:26:01 -0700 Subject: [PATCH 4/9] Add forgot email feature --- .../Controllers/UserEmailForgotController.php | 132 ++++++++++++++++++ app/Mail/UserEmailForgotReminder.php | 55 ++++++++ app/Models/UserEmailForgot.php | 17 +++ config/security.php | 13 ++ resources/views/auth/email/forgot.blade.php | 127 +++++++++++++++++ resources/views/auth/login.blade.php | 17 ++- .../views/auth/passwords/email.blade.php | 51 +++---- .../views/auth/passwords/reset.blade.php | 22 ++- .../emails/forgot-email/message.blade.php | 33 +++++ routes/web.php | 3 + 10 files changed, 433 insertions(+), 37 deletions(-) create mode 100644 app/Http/Controllers/UserEmailForgotController.php create mode 100644 app/Mail/UserEmailForgotReminder.php create mode 100644 app/Models/UserEmailForgot.php create mode 100644 resources/views/auth/email/forgot.blade.php create mode 100644 resources/views/emails/forgot-email/message.blade.php diff --git a/app/Http/Controllers/UserEmailForgotController.php b/app/Http/Controllers/UserEmailForgotController.php new file mode 100644 index 000000000..c97add2a7 --- /dev/null +++ b/app/Http/Controllers/UserEmailForgotController.php @@ -0,0 +1,132 @@ +middleware('guest'); + abort_unless(config('security.forgot-email.enabled'), 404); + } + + public function index(Request $request) + { + abort_if($request->user(), 404); + return view('auth.email.forgot'); + } + + public function store(Request $request) + { + $rules = [ + 'username' => 'required|min:2|max:15|exists:users' + ]; + + $messages = [ + 'username.exists' => 'This username is no longer active or does not exist!' + ]; + + if(config('captcha.enabled') || config('captcha.active.register')) { + $rules['h-captcha-response'] = 'required|captcha'; + $messages['h-captcha-response.required'] = 'You need to complete the captcha!'; + } + + $randomDelay = random_int(500000, 2000000); + usleep($randomDelay); + + $this->validate($request, $rules, $messages); + $check = self::checkLimits(); + + if(!$check) { + return redirect()->back()->withErrors([ + 'username' => 'Please try again later, we\'ve reached our quota and cannot process any more requests at this time.' + ]); + } + + $user = User::whereUsername($request->input('username')) + ->whereNotNull('email_verified_at') + ->whereNull('status') + ->whereIsAdmin(false) + ->first(); + + if(!$user) { + return redirect()->back()->withErrors([ + 'username' => 'Invalid username or account. It may not exist, or does not have a verified email, is an admin account or is disabled.' + ]); + } + + $exists = UserEmailForgot::whereUserId($user->id) + ->where('email_sent_at', '>', now()->subHours(24)) + ->count(); + + if($exists) { + return redirect()->back()->withErrors([ + 'username' => 'An email reminder was recently sent to this account, please try again after 24 hours!' + ]); + } + + return $this->storeHandle($request, $user); + } + + protected function storeHandle($request, $user) + { + UserEmailForgot::create([ + 'user_id' => $user->id, + 'ip_address' => $request->ip(), + 'user_agent' => $request->userAgent(), + 'referrer' => $request->headers->get('referer'), + 'email_sent_at' => now() + ]); + + Mail::to($user->email)->send(new UserEmailForgotReminder($user)); + self::getLimits(true); + return redirect()->back()->with(['status' => 'Successfully sent an email reminder!']); + } + + public static function checkLimits() + { + $limits = self::getLimits(); + + if( + $limits['current']['hourly'] >= $limits['max']['hourly'] || + $limits['current']['daily'] >= $limits['max']['daily'] || + $limits['current']['weekly'] >= $limits['max']['weekly'] || + $limits['current']['monthly'] >= $limits['max']['monthly'] + ) { + return false; + } + + return true; + } + + public static function getLimits($forget = false) + { + return [ + 'max' => config('security.forgot-email.limits.max'), + 'current' => [ + 'hourly' => self::activeCount(60, $forget), + 'daily' => self::activeCount(1440, $forget), + 'weekly' => self::activeCount(10080, $forget), + 'monthly' => self::activeCount(43800, $forget) + ] + ]; + } + + public static function activeCount($mins, $forget = false) + { + if($forget) { + Cache::forget('pf:auth:forgot-email:active-count:dur-' . $mins); + } + return Cache::remember('pf:auth:forgot-email:active-count:dur-' . $mins, 14200, function() use($mins) { + return UserEmailForgot::where('email_sent_at', '>', now()->subMinutes($mins))->count(); + }); + } +} diff --git a/app/Mail/UserEmailForgotReminder.php b/app/Mail/UserEmailForgotReminder.php new file mode 100644 index 000000000..f7e5dbc10 --- /dev/null +++ b/app/Mail/UserEmailForgotReminder.php @@ -0,0 +1,55 @@ +user = $user; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: '[' . config('pixelfed.domain.app') . '] Pixelfed Account Email Reminder', + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'emails.forgot-email.message', + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/UserEmailForgot.php b/app/Models/UserEmailForgot.php new file mode 100644 index 000000000..9e549aff4 --- /dev/null +++ b/app/Models/UserEmailForgot.php @@ -0,0 +1,17 @@ + 'datetime', + ]; +} diff --git a/config/security.php b/config/security.php index a8f92360d..929c05214 100644 --- a/config/security.php +++ b/config/security.php @@ -5,5 +5,18 @@ return [ 'verify_dns' => env('PF_SECURITY_URL_VERIFY_DNS', false), 'trusted_domains' => env('PF_SECURITY_URL_TRUSTED_DOMAINS', 'pixelfed.social,pixelfed.art,mastodon.social'), + ], + + 'forgot-email' => [ + 'enabled' => env('PF_AUTH_ALLOW_EMAIL_FORGOT', true), + + 'limits' => [ + 'max' => [ + 'hourly' => env('PF_AUTH_FORGOT_EMAIL_MAX_HOURLY', 50), + 'daily' => env('PF_AUTH_FORGOT_EMAIL_MAX_DAILY', 100), + 'weekly' => env('PF_AUTH_FORGOT_EMAIL_MAX_WEEKLY', 200), + 'monthly' => env('PF_AUTH_FORGOT_EMAIL_MAX_MONTHLY', 500), + ] + ] ] ]; diff --git a/resources/views/auth/email/forgot.blade.php b/resources/views/auth/email/forgot.blade.php new file mode 100644 index 000000000..73d01811c --- /dev/null +++ b/resources/views/auth/email/forgot.blade.php @@ -0,0 +1,127 @@ +@extends('layouts.blank') + +@push('styles') + + +@endpush + +@section('content') +
+
+
+
+
+ + + +

Forgot Email

+

Recover your account by sending an email to an associated username

+
+ + @if(session('status')) +
+
+ + + {{ session('status') }} +
+
+ @endif + + @if ($errors->any()) + @foreach ($errors->all() as $error) +
+
+ + {{ $error }} +
+
+ @endforeach + @endif + +
+
{{ __('Recover Email') }}
+ +
+ +
+ @csrf + +
+
+ + + @if ($errors->has('username') ) + + {{ $errors->first('username') }} + + @endif +
+
+ + @if(config('captcha.enabled')) + +
+ {!! Captcha::display(['data-theme' => 'dark']) !!} +
+ @if ($errors->has('h-captcha-response')) +
+ {{ $errors->first('h-captcha-response') }} +
+ @endif + @endif + +
+
+ +
+
+
+
+
+ + +
+
+
+
+@endsection + +@push('scripts') + +@endpush + +@push('styles') + +@endpush diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index af6c506e1..dadd08a4f 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -11,11 +11,18 @@
+ @if ($errors->any()) + @foreach ($errors->all() as $error) +
+ {{ $error }} +
+ @endforeach + @endif
@csrf -
+
@@ -26,10 +33,16 @@ {{ $errors->first('email') }} @endif + +
-
+
diff --git a/resources/views/auth/passwords/email.blade.php b/resources/views/auth/passwords/email.blade.php index d144e142a..4f2825e29 100644 --- a/resources/views/auth/passwords/email.blade.php +++ b/resources/views/auth/passwords/email.blade.php @@ -9,7 +9,7 @@
-
+
@if(session('status') || $errors->has('email')) -
-
- + @@ -86,29 +92,6 @@ @push('scripts') @endpush + +@push('styles') + +@endpush diff --git a/resources/views/auth/passwords/reset.blade.php b/resources/views/auth/passwords/reset.blade.php index efe59ac95..1a740fa7d 100644 --- a/resources/views/auth/passwords/reset.blade.php +++ b/resources/views/auth/passwords/reset.blade.php @@ -9,7 +9,7 @@
-
+
@@ -41,14 +41,14 @@ + style="opacity:.5"> @if ($errors->has('email')) @@ -67,7 +67,7 @@ @endpush + +@push('styles') + +@endpush diff --git a/resources/views/emails/forgot-email/message.blade.php b/resources/views/emails/forgot-email/message.blade.php new file mode 100644 index 000000000..af94df3df --- /dev/null +++ b/resources/views/emails/forgot-email/message.blade.php @@ -0,0 +1,33 @@ +@component('mail::message') +Hello, + +You recently requested to know the email address associated with your username [**{{'@' . $user->username}}**]({{$user->url()}}) on [**{{config('pixelfed.domain.app')}}**]({{config('app.url')}}). + +We're here to assist! Simply tap on the Login button below. + + +Login to my {{'@' . $user->username}} account + + +---- +
+ +The email address linked to your username is: + +

+{{$user->email}} +

+
+ +You can use this email address to log in to your account. + +If needed, you can [reset your password]({{ route('password.request')}}). For security reasons, we recommend keeping your account information, including your email address, updated and secure. If you did not make this request or if you have any other questions or concerns, please feel free to [contact our support team]({{route('site.contact')}}). + +Thank you for being a part of our community! + +Best regards,
+
{{ config('pixelfed.domain.app') }} +
+
+

This is an automated message, please be aware that replies to this email cannot be monitored or responded to.

+@endcomponent diff --git a/routes/web.php b/routes/web.php index e71e6fd9e..6c765ba56 100644 --- a/routes/web.php +++ b/routes/web.php @@ -203,6 +203,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegister'); Route::post('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegisterStore'); + Route::get('auth/forgot/email', 'UserEmailForgotController@index')->name('email.forgot'); + Route::post('auth/forgot/email', 'UserEmailForgotController@store')->middleware('throttle:10,900,forgotEmail'); + Route::get('discover', 'DiscoverController@home')->name('discover'); Route::group(['prefix' => 'api'], function () { From 5afe7abdfbdfbe0b8da78004fe3eadca0eb4ea41 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 05:26:57 -0700 Subject: [PATCH 5/9] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcedd4896..d251b3df3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Added Mutual Followers API endpoint ([33dbbe46](https://github.com/pixelfed/pixelfed/commit/33dbbe46)) - Added User Domain Blocks ([#4834](https://github.com/pixelfed/pixelfed/pull/4834)) ([fa0380ac](https://github.com/pixelfed/pixelfed/commit/fa0380ac)) - Added Parental Controls ([#4862](https://github.com/pixelfed/pixelfed/pull/4862)) ([c91f1c59](https://github.com/pixelfed/pixelfed/commit/c91f1c59)) +- Added Forgot Email Feature ([67c650b1](https://github.com/pixelfed/pixelfed/commit/67c650b1)) ### Federation - Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e)) @@ -87,6 +88,7 @@ - Update AP ProfileTransformer, add published attribute ([adfaa2b1](https://github.com/pixelfed/pixelfed/commit/adfaa2b1)) - Update meta tags, improve descriptions and seo/og tags ([fd44c80c](https://github.com/pixelfed/pixelfed/commit/fd44c80c)) - Update login view, add email prefill logic ([d76f0168](https://github.com/pixelfed/pixelfed/commit/d76f0168)) +- Update LoginController, fix captcha validation error message ([0325e171](https://github.com/pixelfed/pixelfed/commit/0325e171)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9) From c26a3d281742ecceee53b3364491704d20dec123 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 05:35:30 -0700 Subject: [PATCH 6/9] Add migration --- ...090048_create_user_email_forgots_table.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2024_01_22_090048_create_user_email_forgots_table.php diff --git a/database/migrations/2024_01_22_090048_create_user_email_forgots_table.php b/database/migrations/2024_01_22_090048_create_user_email_forgots_table.php new file mode 100644 index 000000000..845b63934 --- /dev/null +++ b/database/migrations/2024_01_22_090048_create_user_email_forgots_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedInteger('user_id')->index(); + $table->string('ip_address')->nullable(); + $table->string('user_agent')->nullable(); + $table->string('referrer')->nullable(); + $table->timestamp('email_sent_at')->nullable()->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_email_forgots'); + } +}; From 96366ab3de86064460bb6114e4dfd9ced97b69fd Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 05:51:50 -0700 Subject: [PATCH 7/9] Update forgot email captcha config --- app/Http/Controllers/UserEmailForgotController.php | 2 +- resources/views/auth/email/forgot.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/UserEmailForgotController.php b/app/Http/Controllers/UserEmailForgotController.php index c97add2a7..4baa4ead4 100644 --- a/app/Http/Controllers/UserEmailForgotController.php +++ b/app/Http/Controllers/UserEmailForgotController.php @@ -34,7 +34,7 @@ class UserEmailForgotController extends Controller 'username.exists' => 'This username is no longer active or does not exist!' ]; - if(config('captcha.enabled') || config('captcha.active.register')) { + if(config('captcha.enabled') || config('captcha.active.login') || config('captcha.active.register')) { $rules['h-captcha-response'] = 'required|captcha'; $messages['h-captcha-response.required'] = 'You need to complete the captcha!'; } diff --git a/resources/views/auth/email/forgot.blade.php b/resources/views/auth/email/forgot.blade.php index 73d01811c..898d19fb5 100644 --- a/resources/views/auth/email/forgot.blade.php +++ b/resources/views/auth/email/forgot.blade.php @@ -65,7 +65,7 @@
- @if(config('captcha.enabled')) + @if(config('captcha.enabled') || config('captcha.active.login') || config('captcha.active.register'))
{!! Captcha::display(['data-theme' => 'dark']) !!} From efe8e890463b038c481a31a3f71e12455e86928f Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 05:57:40 -0700 Subject: [PATCH 8/9] Update forgot mail template, urlencode email --- resources/views/emails/forgot-email/message.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/emails/forgot-email/message.blade.php b/resources/views/emails/forgot-email/message.blade.php index af94df3df..dcae7cfbc 100644 --- a/resources/views/emails/forgot-email/message.blade.php +++ b/resources/views/emails/forgot-email/message.blade.php @@ -5,7 +5,7 @@ You recently requested to know the email address associated with your username [ We're here to assist! Simply tap on the Login button below. - + Login to my {{'@' . $user->username}} account From 6167ebc65482d2a4b598848adfae7e5b802f79eb Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 22 Jan 2024 05:59:37 -0700 Subject: [PATCH 9/9] Update UserEmailForgotController --- app/Http/Controllers/UserEmailForgotController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Http/Controllers/UserEmailForgotController.php b/app/Http/Controllers/UserEmailForgotController.php index 4baa4ead4..33378c4d0 100644 --- a/app/Http/Controllers/UserEmailForgotController.php +++ b/app/Http/Controllers/UserEmailForgotController.php @@ -82,7 +82,6 @@ class UserEmailForgotController extends Controller 'user_id' => $user->id, 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), - 'referrer' => $request->headers->get('referer'), 'email_sent_at' => now() ]);