From 5eb1a6a00613e648445ca14fd5cfe8fe9b935c13 Mon Sep 17 00:00:00 2001 From: Constantin Graf Date: Thu, 28 May 2026 20:45:27 +0200 Subject: [PATCH] Add reset pending email endpoint to user controller --- .../Controllers/Api/V1/UserController.php | 21 +++++++ routes/api.php | 1 + .../Unit/Endpoint/Api/V1/UserEndpointTest.php | 61 +++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 95371bed..a3deee36 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -100,6 +100,27 @@ class UserController extends Controller return new UserResource($user); } + /** + * Reset the pending email for a user. + * + * This endpoint is independent of the organization. + * + * @operationId resetUserPendingEmail + * + * @throws AuthorizationException Thrown when the authenticated user does not match the user whose email is pending verification. + */ + public function resetPendingEmail(User $user): JsonResponse + { + if ($user->getKey() !== $this->user()->getKey()) { + throw new AuthorizationException; + } + + $user->pending_email = null; + $user->save(); + + return response()->json(null, 204); + } + /** * Resend the pending email update verification email. * diff --git a/routes/api.php b/routes/api.php index d80795e9..7a275f89 100644 --- a/routes/api.php +++ b/routes/api.php @@ -64,6 +64,7 @@ Route::prefix('v1')->name('v1.')->group(static function (): void { Route::put('/users/{user}', [UserController::class, 'update'])->name('update'); Route::post('/users/{user}/resend-email-verification', [UserController::class, 'resendEmailVerification'])->name('resend-email-verification'); Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('destroy'); + Route::post('/users/{user}/reset-pending-email', [UserController::class, 'resetPendingEmail'])->name('reset-pending-email'); }); // Api token routes diff --git a/tests/Unit/Endpoint/Api/V1/UserEndpointTest.php b/tests/Unit/Endpoint/Api/V1/UserEndpointTest.php index f877d8c1..4f9184e6 100644 --- a/tests/Unit/Endpoint/Api/V1/UserEndpointTest.php +++ b/tests/Unit/Endpoint/Api/V1/UserEndpointTest.php @@ -245,6 +245,67 @@ class UserEndpointTest extends ApiEndpointTestAbstract Mail::assertNotQueued(VerifyUpdatedEmailMail::class); } + public function test_reset_pending_email_clears_pending_email(): void + { + // Arrange + $data = $this->createUserWithPermission(); + $data->user->pending_email = 'new.email@example.com'; + $data->user->save(); + Passport::actingAs($data->user); + + // Act + $response = $this->postJson(route('api.v1.users.reset-pending-email', $data->user->getKey())); + + // Assert + $response->assertNoContent(); + $this->assertNull($data->user->fresh()->pending_email); + } + + public function test_reset_pending_email_fails_if_given_id_is_not_the_authenticated_user(): void + { + // Arrange + $data = $this->createUserWithPermission(); + $data->user->pending_email = 'new.email@example.com'; + $data->user->save(); + $otherData = $this->createUserWithPermission(); + Passport::actingAs($otherData->user); + + // Act + $response = $this->postJson(route('api.v1.users.reset-pending-email', $data->user->getKey())); + + // Assert + $response->assertForbidden(); + $this->assertSame('new.email@example.com', $data->user->fresh()->pending_email); + } + + public function test_reset_pending_email_fails_when_not_authenticated(): void + { + // Arrange + $data = $this->createUserWithPermission(); + $data->user->pending_email = 'new.email@example.com'; + $data->user->save(); + + // Act + $response = $this->postJson(route('api.v1.users.reset-pending-email', $data->user->getKey())); + + // Assert + $response->assertUnauthorized(); + $this->assertSame('new.email@example.com', $data->user->fresh()->pending_email); + } + + public function test_reset_pending_email_fails_if_user_does_not_exist(): void + { + // Arrange + $data = $this->createUserWithPermission(); + Passport::actingAs($data->user); + + // Act + $response = $this->postJson(route('api.v1.users.reset-pending-email', 'not-valid')); + + // Assert + $response->assertNotFound(); + } + public function test_update_changes_user_photo_from_base64_encoded_image(): void { // Arrange