feat: password reset, email change with verification, and password change

Password reset: multi-app support with custom notification linking to correct
frontend (app/portal/admin). Email change: self-service with password
confirmation and admin-initiated, both sending verification to new address
with 24h expiry. Confirmation sent to old email on completion. Password
change: authenticated endpoint revoking other sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 15:38:54 +02:00
parent 53100d4f6d
commit 836cffa232
42 changed files with 2643 additions and 67 deletions

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
final class ResetPasswordNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
private readonly string $token,
private readonly string $frontendUrl,
) {}
public function via(mixed $notifiable): array
{
return ['mail'];
}
public function toMail(mixed $notifiable): MailMessage
{
$resetUrl = $this->frontendUrl . '/reset-password?token=' . $this->token
. '&email=' . urlencode($notifiable->email);
return (new MailMessage)
->subject('Wachtwoord herstellen — Crewli')
->greeting('Hallo ' . $notifiable->first_name . ',')
->line('Je ontvangt deze e-mail omdat we een verzoek hebben ontvangen om je wachtwoord te herstellen.')
->action('Wachtwoord herstellen', $resetUrl)
->line('Deze link is 60 minuten geldig.')
->line('Als je geen wachtwoordherstel hebt aangevraagd, kun je deze e-mail negeren.')
->salutation('Groeten, Crewli');
}
}