From 88d37394d3c07828df719eb32725f497eb87c686 Mon Sep 17 00:00:00 2001 From: Bubka <858858+Bubka@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:06:26 +0100 Subject: [PATCH] Add a test email feature to the admin panel - Closes #307 --- app/Http/Controllers/SystemController.php | 18 ++++++ .../TestEmailSettingNotification.php | 64 +++++++++++++++++++ resources/js/icons.js | 4 +- resources/js/services/systemService.js | 8 +++ resources/js/views/admin/AppSetup.vue | 31 +++++++++ resources/lang/en/admin.php | 5 ++ resources/lang/en/commons.php | 3 +- resources/lang/en/notifications.php | 23 +++++++ routes/web.php | 1 + tests/Feature/Http/SystemControllerTest.php | 39 ++++++++++- 10 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 app/Notifications/TestEmailSettingNotification.php create mode 100644 resources/lang/en/notifications.php diff --git a/app/Http/Controllers/SystemController.php b/app/Http/Controllers/SystemController.php index af54e365..2936e650 100644 --- a/app/Http/Controllers/SystemController.php +++ b/app/Http/Controllers/SystemController.php @@ -3,10 +3,12 @@ namespace App\Http\Controllers; use App\Facades\Settings; +use App\Notifications\TestEmailSettingNotification; use App\Services\ReleaseRadarService; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; class SystemController extends Controller { @@ -58,4 +60,20 @@ public function latestRelease(Request $request, ReleaseRadarService $releaseRada return response()->json(['newRelease' => $release]); } + + /** + * Send a test email. + * + * @return \Illuminate\Http\JsonResponse + */ + public function testEmail(Request $request) + { + try { + $request->user()->notify(new TestEmailSettingNotification()); + } catch (\Throwable $th) { + Log::error($th->getMessage()); + } + + return response()->json(['message' => 'Ok']); + } } diff --git a/app/Notifications/TestEmailSettingNotification.php b/app/Notifications/TestEmailSettingNotification.php new file mode 100644 index 00000000..704a7359 --- /dev/null +++ b/app/Notifications/TestEmailSettingNotification.php @@ -0,0 +1,64 @@ +subject(Lang::get('notifications.test_email_settings.subject')) + ->greeting(Lang::get('notifications.hello')) + ->line( + Lang::get('notifications.test_email_settings.reason') + ) + ->line( + Lang::get('notifications.test_email_settings.success') + ); + } +} diff --git a/resources/js/icons.js b/resources/js/icons.js index 8f0aaebb..7711642c 100644 --- a/resources/js/icons.js +++ b/resources/js/icons.js @@ -49,7 +49,8 @@ import { } from '@fortawesome/free-solid-svg-icons' import { - faStar + faStar, + faPaperPlane, } from '@fortawesome/free-regular-svg-icons' import { @@ -105,6 +106,7 @@ library.add( faStar, faChevronRight, faOpenid, + faPaperPlane, ); export default FontAwesomeIcon \ No newline at end of file diff --git a/resources/js/services/systemService.js b/resources/js/services/systemService.js index f3004a44..a1968542 100644 --- a/resources/js/services/systemService.js +++ b/resources/js/services/systemService.js @@ -17,6 +17,14 @@ export default { */ getLastRelease(config = {}) { return webClient.get('latestRelease', { ...config }) + }, + + /** + * + * @returns Promise + */ + sendTestEmail(config = {}) { + return webClient.post('testEmail', { ...config }) } } \ No newline at end of file diff --git a/resources/js/views/admin/AppSetup.vue b/resources/js/views/admin/AppSetup.vue index 8c0d1155..3d9187a0 100644 --- a/resources/js/views/admin/AppSetup.vue +++ b/resources/js/views/admin/AppSetup.vue @@ -4,16 +4,19 @@ import systemService from '@/services/systemService' import { useAppSettingsStore } from '@/stores/appSettings' import { useNotifyStore } from '@/stores/notify' + import { useUserStore } from '@/stores/user' import VersionChecker from '@/components/VersionChecker.vue' import CopyButton from '@/components/CopyButton.vue' const $2fauth = inject('2fauth') + const user = useUserStore() const notify = useNotifyStore() const appSettings = useAppSettingsStore() const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts') const infos = ref() const listInfos = ref(null) + const isSendingTestEmail = ref(false) /** * Saves a setting on the backend @@ -26,6 +29,18 @@ }) } + /** + * Sends a test email + */ + function sendTestEmail() { + isSendingTestEmail.value = true; + + systemService.sendTestEmail() + .finally(() => { + isSendingTestEmail.value = false; + }) + } + onBeforeRouteLeave((to) => { if (! to.name.startsWith('admin.')) { notify.clear() @@ -53,6 +68,22 @@ +
+ +
+
+
+ +
+

{{ $t('settings.security') }}

diff --git a/resources/lang/en/admin.php b/resources/lang/en/admin.php index 1207c139..9c753b94 100644 --- a/resources/lang/en/admin.php +++ b/resources/lang/en/admin.php @@ -79,6 +79,11 @@ 'is_admin' => [ 'label' => 'Is administrator', 'help' => 'Give administrator rights to the user. Administrators have permissions to manage the whole app, i.e. settings and other users, but cannot generate password for a 2FA they don\'t own.' + ], + 'test_email' => [ + 'label' => 'Email configuration test', + 'help' => 'Send a test email to control your instance\'s email configuration. It is important to have a working configuration, otherwise users will not be able to request a reset password.', + 'email_will_be_send_to_x' => 'The email will be send to :email', ] ], diff --git a/resources/lang/en/commons.php b/resources/lang/en/commons.php index 8ee54c7f..3a802b2d 100644 --- a/resources/lang/en/commons.php +++ b/resources/lang/en/commons.php @@ -79,5 +79,6 @@ 'nothing' => 'nothing', 'no_result' => 'No result', 'information' => 'Information', - 'permissions' => 'Permissions' + 'permissions' => 'Permissions', + 'send' => 'Send', ]; diff --git a/resources/lang/en/notifications.php b/resources/lang/en/notifications.php new file mode 100644 index 00000000..9e7f8380 --- /dev/null +++ b/resources/lang/en/notifications.php @@ -0,0 +1,23 @@ + 'Hello', + 'test_email_settings' => [ + 'subject' => '2FAuth test email', + 'reason' => 'You are receiving this email because you requested a test email to validate the email settings of your 2FAuth instance.', + 'success' => 'Good news, it works :)' + ], + +]; \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 6efdc661..6ad0e304 100644 --- a/routes/web.php +++ b/routes/web.php @@ -83,6 +83,7 @@ */ Route::group(['middleware' => ['behind-auth', 'admin']], function () { Route::get('infos', [SystemController::class, 'infos'])->name('system.infos'); + Route::post('testEmail', [SystemController::class, 'testEmail'])->name('system.testEmail'); }); Route::get('latestRelease', [SystemController::class, 'latestRelease'])->name('system.latestRelease'); diff --git a/tests/Feature/Http/SystemControllerTest.php b/tests/Feature/Http/SystemControllerTest.php index 65c2af28..07ba7ac0 100644 --- a/tests/Feature/Http/SystemControllerTest.php +++ b/tests/Feature/Http/SystemControllerTest.php @@ -4,8 +4,9 @@ use App\Http\Controllers\SystemController; use App\Models\User; +use App\Notifications\TestEmailSettingNotification; use App\Services\ReleaseRadarService; -use Illuminate\Foundation\Testing\WithoutMiddleware; +use Illuminate\Support\Facades\Notification; use PHPUnit\Framework\Attributes\CoversClass; use Tests\FeatureTestCase; @@ -15,8 +16,6 @@ #[CoversClass(SystemController::class)] class SystemControllerTest extends FeatureTestCase { - //use WithoutMiddleware; - /** * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable */ @@ -116,4 +115,38 @@ public function test_latestrelease_runs_manual_scan() 'newRelease' => 'new_release', ]); } + + /** + * @test + */ + public function test_testEmail_sends_a_notification() + { + Notification::fake(); + + $response = $this->actingAs($this->admin, 'web-guard') + ->json('POST', '/testEmail', []); + + $response->assertStatus(200); + + Notification::assertSentTo($this->admin, TestEmailSettingNotification::class); + } + + /** + * @test + */ + public function test_testEmail_returns_unauthorized() + { + $response = $this->json('GET', '/infos') + ->assertUnauthorized(); + } + + /** + * @test + */ + public function test_testEmail_returns_forbidden() + { + $response = $this->actingAs($this->user, 'api-guard') + ->json('GET', '/infos') + ->assertForbidden(); + } }